useCopyToClipboardLoading saved progress…

useCopyToClipboard

"Copy to clipboard" buttons are everywhere — copy a share link, an API key, a code snippet — and they all lean on the same browser API: navigator.clipboard.writeText, which is async and can reject (denied permission, insecure context, no clipboard at all). useCopyToClipboard wraps that into a clean pair: a copy(text) function you call on click, and the last-copied text you can use to flip a button to "Copied!".

Implement useCopyToClipboard(). It returns [copiedText, copy]. copiedText is the last successfully copied string (or null before any copy, and null again after a failure). copy(text) is async: it writes via the Clipboard API, resolves to true and stores the text on success, or false on failure or when the API is unavailable.

Signature

function useCopyToClipboard() {
  // returns [copiedText, copy]
  // copiedText: string | null
  // copy: (text: string) => Promise<boolean>
}

Examples

const [copied, copy] = useCopyToClipboard();
<button onClick={() => copy('https://uiready.dev')}>
  {copied ? 'Copied!' : 'Copy link'}
</button>
const [, copy] = useCopyToClipboard();
const ok = await copy(apiKey);
if (!ok) toast('Copy failed — select and press ⌘C');

Notes

  • It's async and can failwriteText returns a promise that rejects on denied permission or an insecure (non-HTTPS) context. Wrap it in try/catch.
  • Feature-detectnavigator.clipboard is undefined in old or insecure contexts; return false instead of throwing.
  • Track success only — set copiedText when the write resolves; clear it to null on failure so the UI never lies about what's on the clipboard.
  • Stable copy — wrap it in useCallback so it can be a dependency or a memoized prop.
Loading editor…