The one-click "copy this value" control that acknowledges itself: click copies, the icon swaps to a checkmark, the tooltip flips to "Copied!", and both revert after a moment. Built entirely on Button (copy-button.tsx:74-91), not a second button implementation; see button.md for every prop it forwards.
The canon behind it. docs/design-language/10-overlays.md §A12 (10-overlays.md:56, copy belongs on values that name the thing, never on values reporting how work is going) and docs/design-language/02-buttons-and-actions.md §A8 (02-buttons-and-actions.md:40, icons come from the shared registry, never a callsite import).
When to reach for it#
Anywhere a value needs a clipboard affordance: an id, a SKU, a generated payload, a table cell. It is described in the source as the single highest-reuse micro-interaction in a data-heavy app (copy-button.tsx:33-36), and the kit's own Table composes it internally for copyCells (table.tsx:371-376), so any table with that feature on is already shipping one per data cell.
Contract#
value: the exact text placed on the clipboard.children: optional label beside the icon. Omit it for an icon-only button;circlethen defaults totrue(circle ?? children == null,copy-button.tsx:79).copyLabel/copiedLabel: the tooltip (and, whenchildrenis given, the visible label) before and after a successful copy. Default"Copy"/"Copied!".timeout: how long the checkmark/"Copied!" acknowledgement lasts, in ms. Default1200.onCopied(value): value-first, fires after a successful write, described in the type doc as "the place to also toast" (copy-button.types.ts:21).size/variant/color/circle/iconSize/disabled/shade/tooltipProps: passed straight through to the underlyingButton(Pick<ButtonProps, ...>,copy-button.types.ts:10-13). Defaultsize="sm" variant="ghost" color="neutral". While the acknowledgement is showing, the button's color is forced to"success"regardless of thecolorprop (copy-button.tsx:78).Icon: the idle glyph, for when the thing copied is not "text" (a prompt, a link). Default"Copy"; the checkmark still replaces it after a copy.tooltipPropsis how an icon-only copy button gets the rich two-line tip (tooltip.md, "The rich preset"):tooltipProps={{ title: "Copy path" }}puts the label the icon replaced above thecopyLabelbody, and the title stays through the "Copied!" acknowledgement. When the title is a string it is also the button's accessible name, ahead ofcopyLabel(copy-button.tsx:76-80).- Slot classnames per
CopyButtonClassNames:classNamethe button,_classNametheTooltipwrapper,iconClassNamethe icon.
Mechanics#
Writing to the clipboard prefers navigator.clipboard.writeText, falling back to a hidden off-screen <textarea> plus document.execCommand("copy") for insecure contexts or older browsers (copy-button.tsx:7-30). The click handler calls event.stopPropagation() first, because a copy button commonly sits inside a clickable row or card and must never also trigger that row's own click (copy-button.tsx:62-63).
Sanctioned combinations#
| Combination | Produces | Where used | Why |
|---|---|---|---|
No children, default circle | An icon-only copy button beside a mono id | walmart-mvp/frontend/src/components/JobNameCell.tsx:43, speedway/app/routes/admin/scrape-requests.tsx:176-181 | The id text is already visible next to it; the button only needs to act, not re-label the value |
variant="text" circle, wrapped in a parent with opacity-0 group-hover:opacity-100 | A copy control that only appears on row/definition hover | walmart-mvp/frontend/src/features/parts/PartPreviewModal.tsx:93-99 | Keeps a dense key/value list quiet until the reader's cursor is actually there |
size="xs" variant="ghost" iconSize={12}, composed inside a cell's own hover-reveal wrapper | The table copyCells per-cell copy affordance | table.tsx:371-376, consumed by every table with copyCells on (table.md, 14+ call sites) | The kit's own reference implementation of the hover-reveal pattern above |
No children, variant="text", Icon="Contract", tooltipProps={{ title: "Copy prompt" }} | An icon-only header action with a two-line tip: the label above, what it does below | apps/playground/src/app/showcase-title.tsx:55-64 (every showcase page's Copy prompt and Copy path) | The icon replaced the label, so the tip carries the label as its title and the help as its body; the reader never has to guess what a circle does |
Banned combinations#
None found; there is nothing shipped that misuses this component. One naming collision is worth knowing about: walmart-mvp/frontend/src/features/catalog/WalmartSubmit.tsx:511 declares its own local CopyButton function, unrelated to the kit's, for a listing title bar's copy control (WalmartSubmit.tsx:479). Check the import, @versable-git/ui versus a local declaration, before reading or extending a CopyButton call site in that file.
Before you adopt this#
Five questions to answer before reaching for CopyButton.
- Does the shell, a parent layout, or a global provider already render this? Not applicable, CopyButton is a local control; no shell already renders it.
- Does this app already ship a local implementation of the same thing? Walmart's
WalmartSubmit.tsxdeclares its own unrelated localCopyButton; check the import. - Does this app's kit pin reach the version this component or prop landed in? Not applicable, no version-landed CopyButton prop is noted in this doc.
- Does the component derive its own accessible name and keyboard path, or must the call site supply them?
tooltipProps.titledoubles as thearia-labelwhen there is no visiblechildren. - Which canon §A rules bind this surface, and which does the composition break? §A12 reserves copy for values naming the thing, never status.
Travels with#
Button, which it wraps entirely; every appearance prop isButton's own.Tooltip, viaButton's own unconditional tooltip wrapper.
Snippet#
// walmart-mvp/frontend/src/components/JobNameCell.tsx:43<CopyButton value={jobName} size="xs" variant="ghost" iconSize={12} copyLabel="Copy job name" />