Copy Button
Copy a value to the clipboard with transient check feedback — built on Button.
- Status
- Since
0.1.0- Accessibility pattern
- native button + status announcement
Last updated
npx shadcn add @vegastack/buttonInstall
Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Copy Button before installation. Retain the digest printed by this command.
pnpm exec vegastack-design verify copy-buttonInstall with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.
pnpm dlx shadcn@4.21.0 add @vegastack/copy-buttonRun the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.
The same command installs the registry items it composes: @vegastack/button, @vegastack/use-announcer.
Usage
import { CopyButton } from "@/components/ui/copy-button";
<CopyButton value="npx shadcn add @vegastack/button" />;Examples
Copy feedback
A ghost / icon-sm button that writes value to the clipboard and swaps the
Copy icon for a Check for ~1.5s. Pair it with the value it copies — an API
key, an invite link, a code snippet — and use onCopied to surface a toast.
npx shadcn add @vegastack/buttonVisible label
Set showLabel when the action needs stronger discoverability. The current
copyLabel or copiedLabel appears beside the icon, and the one Button switches from the
square size="icon-sm" to the text-bearing size="sm".
With a toast
onCopied fires after a successful clipboard write — use it to confirm the action
with a toast or to log an analytics event. The transient check feedback is handled
internally, so onCopied is purely for your side effects.
vsk_live_3f9a…<CopyButton
value={apiKey}
onCopied={() => toast.add({ type: "success", title: "API key copied" })}
/>States
Idle shows the Copy icon. After a successful copy the icon swaps to a Check
tinted text-primary and the accessible label flips from Copy to Copied
for timeout ms (default 1500), with data-copied set for that window. disabled
is forwarded straight to the underlying button. The copied state is owned internally
and only appears after a real write, so the middle cell below is live — click it.
Variants
CopyButton defaults to variant="ghost" and forwards every
Button variant unchanged — default, outline, secondary,
ghost, destructive and link — upstream's set, and the whole set.
Sizes
size is forwarded too. Without a visible label the control is icon-only, so it takes an ICON
tier — icon-xs · icon-sm · icon · icon-lg — and defaults to icon-sm; with showLabel it
defaults to the text tier sm. An explicit size always wins. Upstream's xs / sm / lg are
text tiers with horizontal padding, so they would render a wide box around a lone glyph.
Playground
Try the forwarded variant and size axes, the label toggle, and the disabled state — the copy action is live — then copy the generated JSX.
<CopyButton value="pnpm dlx shadcn@latest add @vegastack/button" />API Reference
CopyButton forwards supported Button presentation props
(variant, size, className, disabled, …) and owns the clipboard action, button
type, accessible labels, and icon children.
| Prop | Type | Default | Description |
|---|---|---|---|
value* | string | — | The text written to the clipboard when the button is pressed. |
copiedLabel | string | 'Copied' | Accessible label while the copied confirmation is visible. |
copyLabel | string | 'Copy' | Accessible label before the value has been copied. |
loading | boolean | — | Shows a spinner over the label, blocks activation and sets aria-busy. The label keeps its
box at opacity: 0, so the button's width does not move and its accessible name survives
(API-5, A11Y-12). |
onCopied | ((value: string) => void) | — | Fired after value is successfully copied to the clipboard. Use it to show a
toast or analytics event — the transient check feedback is handled internally. |
onPress | ((event: React.MouseEvent<HTMLElement>) => void) | — | Called when the copy button is pressed before the clipboard write runs.
Calling event.preventDefault() cancels the write. |
showLabel | boolean | false | Show the current copy status as visible text beside the icon. With a label the control is a
text Button; without one it is the same Button at an icon size. An explicit size still
wins. |
size | "default" | "icon" | "icon-lg" | "icon-sm" | "icon-xs" | "lg" | "sm" | "xs" | null | — | |
timeout | number | 1500 | How long (in milliseconds) the check icon stays visible before reverting to the copy icon. |
variant | "default" | "destructive" | "ghost" | "link" | "outline" | "secondary" | null | — |
Data attributes and CSS variables on CopyButton
| Attribute | Values |
|---|---|
data-slot | "copy-button-label" |
Accessibility
- Renders a native
<button type="button">; keyboard Enter / Space activate it. - The accessible name switches from
CopytoCopiedon success so screen readers announce the result; the icon is decorative (aria-hidden). - Inherits the underlying
Button's focus treatment: the global 2px:focus-visibleoutline in theringink (FOC-1), neveroutline: none. No variant adds a ring of its own —design-lint'sno-focus-ring-glowrejects one. - A failed clipboard write (denied permission or insecure context) leaves the button in its default state instead of showing a false success.
| Key | Action |
|---|---|
| Tab | Move focus to the button. |
| Enter / Space | Copy value to the clipboard. |
| Contract | States tested |
|---|---|
| Behaviour | default, copied, empty, pressed, success |
| Accessibility | labeled, status-announcement |
| Visual | default, hover, success, empty |