# Copy Button

> A React copy to clipboard button for IBANs, tax IDs, links and API keys: an icon button with a tooltip, a labelled button and CopyText.

- Type: React component (Buttons & Actions)
- Access: Free (the source is open to everyone)
- Version: 1.0.0
- Install: `npx wingo-ui@latest add copy-button`
- Docs: https://wingo-ui.com/components/copy-button

## Interaction

Click the copy icon next to the IBAN: it shrinks away, a check pops in and draws itself, and the tooltip says Copied without closing. Clicking again restarts the timer; after the timeout the copy icon comes back. With the keyboard, Tab to a button (the tooltip opens on focus) and press Enter or Space. A labelled button swaps its label to the copied text and keeps its width. CopyText makes the value itself the target: hover shows a dotted underline, a copy flashes the text once. A value read from a function that takes longer than 300ms shows the spinner; a failing or empty one shows an X and announces the error. On a phone there is no tooltip: the check, a short vibration and the screen reader announcement carry the feedback, and the small size still has a 44px hit area.

## Installation

```bash
npx wingo-ui@latest add copy-button
```

The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add https://wingo-ui.com/r/copy-button.json`

Files:

- `components/ui/copy-button.tsx`

npm dependencies: `npm i motion lucide-react`

Wingo UI dependencies (installed automatically):

- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))
- [Tooltip](https://wingo-ui.com/components/tooltip) ([Markdown](https://wingo-ui.com/components/tooltip.md))
- [useClipboard](https://wingo-ui.com/components/use-clipboard) ([Markdown](https://wingo-ui.com/components/use-clipboard.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [Tones](https://wingo-ui.com/components/tones) ([Markdown](https://wingo-ui.com/components/tones.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
import { Link2 } from "lucide-react"
import { CopyButton, CopyText } from "@/components/ui/copy-button"

export function PaymentDetails() {
  return (
    <div className="flex flex-col gap-3">
      <div className="flex items-center justify-between gap-3">
        <span className="font-mono text-sm">GB29 NWBK 6016 1331 9268 19</span>
        <CopyButton value="GB29 NWBK 6016 1331 9268 19" copyLabel="Copy IBAN" copiedLabel="Copied" />
      </div>
      <CopyButton value="https://acme.com/i/2026-0412" variant="outline" icon={<Link2 />} copiedLabel="Copied">
        Copy link
      </CopyButton>
      <p className="text-sm">
        Reference: <CopyText mono copiedLabel="Copied">#2026-0412</CopyText>
      </p>
    </div>
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `CopySource (string or () => string \| Promise<string>)` | `"GB29 NWBK 6016 1331 9268 19"` | The text to copy, or a function read at click time (the latest form value, a key fetched on demand). An empty result counts as a failure. |
| `children?` | `ReactNode` | `""` | A label ("Copy IBAN") turns it into a text button whose label swaps to copiedLabel. Without one it is a square icon button labelled by copyLabel. |
| `copyLabel?` | `string` | `"Copy"` | The aria-label of an icon-only button and its tooltip while idle. It never switches to the copied text, so screen readers do not re-read the button. The English defaults of all three labels are exported as DEFAULT_COPY_BUTTON_LABELS. |
| `copiedLabel?` | `string` | `"Copied"` | Shown in the tooltip, in place of the label and announced by the status region after a copy. |
| `errorLabel?` | `string` | `"Copy failed"` | Tooltip and announcement when the copy fails. A labelled button keeps its label; the icon turns into an X. |
| `variant?` | `"solid" \| "soft" \| "outline" \| "ghost" \| "link"` | `"ghost"` | The Button variants. ghost for rows and code headers, outline or soft for a labelled button. |
| `size?` | `"sm" \| "md" \| "lg"` | `"sm"` | 32 / 40 / 48px. Icon-only maps to the square icon-sm / icon / icon-lg sizes; sm keeps a 44px hit area on touch. |
| `radius?` | `"auto" \| "none" \| "sm" \| "md" \| "lg" \| "full"` | `"auto"` | auto is full (a circle) for icon-only buttons and md with a label. The other values are the Button radii. |
| `tone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | The button's color, through the tone tokens. |
| `successTone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"success"` | Color of the check. neutral keeps it the button's own color; on a solid fill the check always uses the fill's text color. |
| `color?` | `string` | `""` | Any CSS color; replaces the tone like on Button. Leave empty to use the tone. |
| `fullWidth?` | `boolean` | `false` | Stretches a labelled button to its container, for share screens on a phone. |
| `loading?` | `boolean` | `false` | Forces the spinner. A value function that takes longer than 300ms shows it on its own, and clicks are ignored while it runs. |
| `disabled?` | `boolean` | `false` | Dims the button and blocks copying. |
| `tooltip?` | `boolean` | `true` | The desktop tooltip on icon-only buttons (a labelled button already says what it does). It opens on hover and focus and stays open to say Copied; touch screens never show it. |
| `timeout?` | `number` | `2000` | How long the check (or the X) stays, in ms. A second copy restarts it. |
| `html?` | `string` | `""` | A rich version copied next to the plain text (pasted into mail or docs as formatted text). It changes nothing on screen; the playground notes when it is set. |
| `icon?` | `ReactNode` |  | Replaces the Copy icon. Unsized svgs follow the button size (14 / 16 / 18px). |
| `copiedIcon?` | `ReactNode` |  | Replaces the check that draws itself after a copy. |
| `errorIcon?` | `ReactNode` |  | Replaces the X shown when the copy fails. |
| `onCopy?` | `(text: string) => void` |  | Called after a successful copy with the copied text. |
| `onError?` | `(error: Error) => void` |  | Called when the copy fails: a rejected value function, an empty value ("Nothing to copy") or a blocked clipboard. |
| `onClick?` | `(event: MouseEvent<HTMLButtonElement>) => void` |  | Runs before copying; call event.preventDefault() to cancel the copy. |
| `classNames?` | `Partial<Record<"icon" \| "label", string>>` |  | Extra classes for the icon stack and the label stack. |
| `className?` | `string` |  | Extra classes for the button, merged last. The other Button props and native button attributes pass through. |

## Parts

### CopyText (component)

An inline value that copies itself on click or Enter: the text (mono for ids and keys, truncated to one line) with a small icon that turns into a check.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value?` | `string \| (() => string \| Promise<string>)` |  | What gets copied; defaults to children when it is a string or a number. |
| `html?` | `string` |  | A rich version copied next to the plain text. |
| `mono?` | `boolean` | `false` | font-mono at 12.5px, for ids, keys and IBANs. |
| `truncate?` | `boolean` | `true` | One line with an ellipsis; the whole value is still copied. |
| `copyLabel?` | `string` | `"Copy"` | The title and accessible hint while idle. |
| `copiedLabel?` | `string` | `"Copied"` | Announced after a successful copy. |
| `timeout?` | `number` | `2000` | How long the copied state stays, in ms. |
| `successTone?` | `Tone` | `"success"` | The tone of the check icon after a copy. |
| `onCopy?` | `(text: string) => void` |  | Fires with the copied text. |
| `classNames?` | `Partial<Record<"value" \| "icon", string>>` |  | Extra classes for the value and the icon. |

### DEFAULT_COPY_BUTTON_LABELS (constant)

The English labels both components fall back to: { copyLabel: "Copy", copiedLabel: "Copied", errorLabel: "Copy failed" }.

## Examples

- **Labelled buttons**: A label turns it into a text button; the label swaps to copiedLabel and the width holds.
- **CopyText inline**: CopyText makes the value itself the button: a dotted underline on hover and focus, a muted icon that becomes the check, a soft flash once per copy. Props: value (defaults to string children), mono (false, font-mono 12.5px), truncate (true), copyLabel, copiedLabel, errorLabel, timeout, icon, copiedIcon, successTone, html, onCopy, onError, classNames { value, icon }.
- **API key fetched on click**: A function value is read at click time; slow ones show the spinner after 300ms.
- **Code snippet header**: The quiet ghost icon next to the language label.
- **Tones and successTone**: Every tone; successTone neutral keeps the check monochrome, solid keeps it in the fill's text color.
- **Failure**: A value that rejects, or an empty one, shows the X and announces errorLabel.
- **Invite link on a phone**: fullWidth lg next to a CopyText field, for the share screen.

## See also

- [useClipboard](https://wingo-ui.com/components/use-clipboard) ([Markdown](https://wingo-ui.com/components/use-clipboard.md))
- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))
- [Code Block](https://wingo-ui.com/components/code-block) ([Markdown](https://wingo-ui.com/components/code-block.md))
- [Tooltip](https://wingo-ui.com/components/tooltip) ([Markdown](https://wingo-ui.com/components/tooltip.md))

---

Canonical: https://wingo-ui.com/components/copy-button
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
