Hooks
useCopyToClipboard
A clipboard-copy hook with an auto-resetting copied state and a non-throwing error channel.
Preview in your theme
Loading preview…
Installation
npx shadcn@latest add https://ui.zyeon.ai/r/use-copy-to-clipboard.jsonPrompt
Build a React + TypeScript "useCopyToClipboard" hook (no dependencies beyond
React; uses the browser Clipboard API only).
Contract
- `useCopyToClipboard(options?: { resetDelay?: number }): { copied: boolean;
copy: (text: string) => Promise<boolean>; error: Error | null }`.
- `resetDelay` defaults to 2000ms.
- `copy(text)` returns a `Promise<boolean>` — `true` on a successful clipboard
write, `false` on any failure. It never throws.
Behavior
- On `copy(text)`: guard for `navigator.clipboard.writeText` existing (SSR,
insecure http origin, unsupported browser). If missing, set `error` and
resolve `false` without touching `copied`.
- On a successful write: set `error` to `null`, set `copied` to `true`, and
(re)start a timer that flips `copied` back to `false` after `resetDelay`.
Calling `copy` again while already `copied` clears and restarts the timer
instead of stacking multiple timers.
- On a rejected write (permission denied, missing user gesture, clipboard
blocked): set `error` to the rejection reason (wrapped in an `Error` if it
wasn't already one) and resolve `false`; `copied` is left untouched — a
failed copy never flips it to true.
- The reset timer is cleared on unmount so no `setState` fires after the
consuming component is gone.
- Every state update happens inside the clipboard promise's resolve/reject
branch. The hook body itself never touches `navigator` synchronously during
render or in a bare mount effect — the browser API only runs inside the
event-triggered `copy` call — so the hook is safe to use from a
server-rendered component.
- Multiple components can call `useCopyToClipboard()` independently in the
same tree; each call owns its own `copied` / `error` / timer, there is no
shared or global copied state.
Rendering & styling
- The hook renders nothing itself. Consumers own all UI: swap an icon/label
when `copied` flips, use semantic tokens (`bg-primary`, `text-destructive`,
`text-muted-foreground`) for any visual feedback, and respect
`prefers-reduced-motion` for any icon-swap transition.
Customization levers
- `resetDelay` — how long the copied feedback stays before falling back to idle.
- Add `onSuccess(text)` / `onError(error)` callbacks if a consumer needs side
effects (analytics, toast) beyond reading `copied` / `error` — intentionally
left out by default to keep the contract minimal.
- A `document.execCommand("copy")` fallback for very old browsers is
deliberately NOT included: it's deprecated, needs a hidden textarea +
selection hack, and every target environment (evergreen browsers, https or
localhost) already ships `navigator.clipboard`. Only add it if you must
support such a browser.Concepts
- Timed state with restart-on-repeat —
copiedisn't a one-shot flag; it's a timer-backed state that a freshcopy()call always restarts, so rapid re-clicks keep extending the "copied" window instead of flickering back to idle mid-read. - Non-throwing error channel — failures (permission denial, insecure origin, unsupported browser) resolve to
falseand populateerror, never throw; callers canif (!(await copy(text)))without a try/catch. - Multi-instance independence — the hook holds no module-level state, so a list of copy buttons each call
useCopyToClipboard()and animate their owncopiedstate without stepping on each other. - SSR-safe browser access —
navigator.clipboardis only read inside thecopycallback (fired by a user event), never during render or in a mount-time effect, so the hook doesn't need atypeof windowguard to be server-render safe.
Wallet Card Stack
A phone-screen wallet: cards overlap so only a tappable strip of each shows, tapping one lifts it to the top safe-area edge and tucks the rest under the bottom, and a downward drag — or Escape — puts it back.
useDebounceValue
A generic hook that returns a value only after it has stopped changing for a delay, with no flash on first render.