Copy button
Free · MITCopy-to-clipboard button whose icon blurs from copy into a drawn check with a popping Copied! tooltip, plus a fallback when the Clipboard API is blocked.
Installation
$ npx shadcn@latest add https://goodui.dev/r/copy-button.jsonThe file lands in components/goodui/copy-button.tsx and is yours to edit. With the @goodui registry added, npx shadcn@latest add @goodui/copy-button works too.
Usage
import { CopyButton } from "@/components/goodui/copy-button"
export function BookingLink({ url }: { url: string }) {
return (
<div className="flex items-center gap-2 rounded-lg border bg-muted/40 py-1.5 ps-3 pe-1.5">
<span className="truncate font-mono text-sm">{url}</span>
<CopyButton value={url} label="Copy link" variant="outline" className="ms-auto" />
</div>
)
}When to use
- Next to API keys, webhook URLs, invite links and booking links that people paste elsewhere.
- In code blocks and docs, as a small icon-only button in the corner.
- Sharing panels ("Copy link") where a visible label and a Copied! confirmation help.
- Sharing to other apps on mobile: use the Web Share API (navigator.share) with a button; copy is the fallback.
- Actions that do more than copy (create a link on the server first, then copy): use stateful-button, or pass an async value function here if the server call is quick.
| If you need | Use |
|---|---|
| Corner of a code block | defaults (variant="ghost", size icon-sm) |
| Visible "Copy link" text | label="Copy link" (size becomes sm) |
| Inside a form row next to an input | variant="outline" size="icon" |
| Text produced on demand | value={async () => …} |
| No floating tooltip | tooltip={false} |
Examples
Icon-only, for a code block
<div className="relative rounded-lg bg-muted p-4 font-mono text-sm">
<CopyButton value="npx shadcn@latest add copy-button" hint="Copy command" className="absolute end-2 top-2" />
npx shadcn@latest add copy-button
</div>Copy a value created on demand
<CopyButton
label="Copy invite link"
value={async () => (await createInvite(teamId)).url}
onCopied={() => track("invite_copied")}
/>API reference
| Prop | Type | Default |
|---|---|---|
| value* Text to copy, or a function that returns it (can be async). | string | (() => string | Promise<string>) | — |
| timeout Milliseconds the "copied" state stays. | number | 1600 |
| onCopied Called with the copied text after a successful copy. | (text: string) => void | — |
| onCopyError Called with the text when both the Clipboard API and the fallback fail, or with "" when the value function throws or rejects. | (text: string) => void | — |
| label Visible text beside the icon; leave empty for an icon-only button. | React.ReactNode | — |
| copiedLabel Text shown in the tooltip (and the label) after copying. | string | — |
| failedLabel Tooltip text when copying is blocked; defaults to a ⌘C / Ctrl+C hint. | string | — |
| hint Hint shown in the tooltip on hover for icon-only buttons. | string | — |
| variant "ghost" | "outline" | "secondary" | "default". Ghost also turns the icon emerald after copying. | "outline" | "default" | "ghost" | "secondary" | "ghost" |
| size "icon-sm" | "icon" | "sm" | "md". Defaults to "icon-sm" without a label and "sm" with one. | "sm" | "md" | "icon" | "icon-sm" | — |
| tooltip Show the floating tooltip. | boolean | true |
| labels Text for built-in controls and screen readers; pass translations here. | Partial<CopyButtonLabels> | — |
| sound Plays "pop" after a copy (and "error" when it fails) when the visitor has sounds on (lib/goodui-sounds); a sound name replaces the pop. Off by default. | boolean | SoundName | — |
Also accepts every <button> attribute.
Accessibility
| Enter / Space | Copies (native <button>). |
| Tab | Focus shows the hint tooltip on keyboard focus (focus-visible) for icon-only buttons. |
- Icon-only buttons get aria-label from hint ("Copy") unless you pass aria-label.
- A polite live region announces "Copied to clipboard" or a failure message; the tooltip itself is aria-hidden.
- If the Clipboard API is blocked, it falls back to a hidden textarea with execCommand("copy") and returns focus to where it was.
- If both fail, the tooltip tells the user to press ⌘C on Apple devices or Ctrl+C elsewhere.
Motion
- The copy icon blurs and scales out while a check scales in and draws itself in 0.25 s; the tooltip pops on a spring (stiffness 600, damping 26).
- Press scales to 0.95. The copied state lasts `timeout` ms (1600); a failure lasts 1.2 s longer.
- Reduced motion: icon and tooltip cross-fade in 0.1 s and the check appears already drawn.
Notes for AI
Coding agents read these rules through MCP and llms.txt, or paste this page with Copy page.
- value is required: a string, or a function returning a string or a promise of one. Do not pass children; use label for visible text.
- The button calls your onClick first; call event.preventDefault() there to cancel the copy.
- Variants are ghost (default), outline, secondary, default; sizes icon-sm, icon, sm, md.
- Built-in text comes from the labels prop (hint, copied, blocked(mac), copiedAnnouncement, failedAnnouncement); the hint, copiedLabel and failedLabel props still override the visible texts.
- If value is an async function that throws or rejects, the button shows the failed state and onCopyError is called with "".
FAQ
Does it work on http:// or inside iframes?
When the Clipboard API is not available (insecure context) or throws, it falls back to a hidden textarea and execCommand("copy"). If that also fails, it shows a ⌘C / Ctrl+C hint.
Can I copy rich text or images?
No. It copies plain text only.
Related
One button with ten variants, six tones, seven shadows, ten animations, three shapes and five sizes that all combine, plus loading and icon slots.
A tooltip springs out of the trigger's side and can lean toward the pointer over avatars; tooltips in a delay group open instantly once one is already showing.
Six field styles with top, floating, inline or hidden labels, focus ring, glow, underline-grow or lift, error shake with sliding messages, icon and text addons, clear button, counter and auto-growing textarea.
Keyboard keys and combos in flat, raised 3D, outline, glass, neo and solid styles. Keycaps depress when the real keys are pressed, Mod maps to ⌘ or Ctrl, and onTrigger turns any combo or G-then-T sequence into a working hotkey.