◉RORK LABJP
●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…
Articles/App Dev
◇ App Dev/2026-06-29Intermediate

Clipboard UX in Expo apps — copy and paste without flooding users with iOS's paste banner

When you wire up copy and paste with expo-clipboard, iOS's paste permission banner can fire constantly and quietly erode trust. Here's exactly when the banner appears, and how hasStringAsync lets you gate a Paste button without ever reading the contents.

Rork585Expo219ClipboardiOS116UX Design9

✦ Premium Article

When I added an invite-code redemption screen to one of my wallpaper apps, a small banner reading "Pasted from [other app]" appeared at the top of the screen every single time the screen opened. Testers kept asking me, "Is this actually safe?" The cause was obvious in hindsight: trying to be helpful, I was reading the clipboard the moment the screen mounted and auto-filling the code field. The instant you read, that banner fires.

Clipboard integration looks like a trivial feature — copy one line of text — but without understanding iOS's behavior, it quietly turns your app into a noisy one. Let me walk through how to use expo-clipboard so the paste banner only shows when it makes sense, while copy and paste still feel smooth.

Where the paste banner actually comes from

This trips people up: copying (writing) and pasting (reading) are treated completely differently.

Writing — Clipboard.setStringAsync() — produces no notification at all. It happens because the user pressed a copy button, so that's expected. The reading side is where the trouble is. Since iOS 16, reading content another app copied via Clipboard.getStringAsync() shows a "Pasted from [app]" banner. It exists to tell the user "this app just peeked at your clipboard," and you cannot turn it off.

This is why when you read matters so much. My mistake was reading before the user had done anything — on mount. A read that happens right after the user taps "Paste" produces a banner that matches the context, so it doesn't feel intrusive. A read on open, or on a polling interval, fires banners the user can't account for, and that breeds suspicion.

ActionAPIiOS banner
Copy (write)setStringAsync()No
Read contents (paste)getStringAsync()Yes (iOS 16+)
Check presence onlyhasStringAsync()No

Use hasStringAsync to gate the button without reading

That last row is the key. hasStringAsync() returns a boolean for "is there a string on the clipboard" without ever reading the contents — so no banner. With it, you can disable the Paste button when the clipboard is empty and enable it only when something is there. You read the actual value only at the moment the user taps that button.

import { useEffect, useState } from "react";
import * as Clipboard from "expo-clipboard";
 
function RedeemCodeField({ onPaste }: { onPaste: (value: string) => void }) {
  const [canPaste, setCanPaste] = useState(false);
 
  // Checks presence only — never reads contents, so no banner
  useEffect(() => {
    let mounted = true;
    Clipboard.hasStringAsync().then((has) => {
      if (mounted) setCanPaste(has);
    });
    return () => {
      mounted = false;
    };
  }, []);
 
  // The real read happens only when the user taps the button
  const handlePaste = async () => {
    const text = await Clipboard.getStringAsync();
    if (text) onPaste(text.trim());
  };
 
  return (
    <PasteButton disabled={!canPaste} onPress={handlePaste} />
  );
}

Calling hasStringAsync() once on mount produces no banner. If you also want to re-check when the app returns to the foreground, call hasStringAsync() again from the AppState change event when state becomes active. You're still only checking presence, so it stays safe.

For something with a fixed shape like an invite code, don't drop the pasted string in verbatim. Run text.trim() to strip surrounding whitespace, then do a light validation against the expected format before applying it. That way the field doesn't break when a user accidentally copies extra characters along with the code.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦The exact conditions that trigger iOS's paste permission banner, and how to offer paste without multiplying it
✦An implementation pattern that uses hasStringAsync to enable a Paste button without reading the clipboard
✦Reliable copy feedback, plus how to clean up after putting sensitive data on the clipboard
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

◇ App Dev2026-10-15
When adjustsFontSizeToFit Shrinks Just One Row, Suspect the Box Height, Not the String
Only the 10th row of a list drops to 4pt on iOS, though the data and strings are fine. I read the Yoga rounding and font-fit sources, then counted how often the box comes up short in Node.
◇ App Dev2026-09-18
The three lines I check when prebuild stops on the standard SDK 57 Swift AppDelegate error
Turning on ios.enableSceneSupport for iOS 27 can stop prebuild cold. The cause was not my SDK version but the shape of AppDelegate.swift. Here are the three lines to check, and why copying the SDK 58 migration steps by hand will break your build.
◇ App Dev2026-09-16
The Token I Deleted Came Back After a Restart — Detecting Failed SecureStore Deletes
On Android, getItemAsync can report null right after sign-out while the value is still sitting on disk. Here is how I rewrote a sign-out path that was verifying deletion by reading instead of by the delete result.