Skip to content
Component installs need the registry setup
VegaStack Design

Copy Button

Copy a value to the clipboard with transient check feedback — built on Button.

Status
stable
Since
0.1.0
Accessibility pattern
native button + status announcement

Last updated

npx shadcn add @vegastack/button

Install

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-button

Install with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.

pnpm dlx shadcn@4.21.0 add @vegastack/copy-button

Run 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/button

Visible 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.

Idle
Copied (click — live)
Disabled

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.

PropTypeDefaultDescription
value*string—The text written to the clipboard when the button is pressed.
copiedLabelstring'Copied'Accessible label while the copied confirmation is visible.
copyLabelstring'Copy'Accessible label before the value has been copied.
loadingboolean—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.
showLabelbooleanfalseShow 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—
timeoutnumber1500How 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

AttributeValues
data-slot"copy-button-label"

Accessibility

  • Renders a native <button type="button">; keyboard Enter / Space activate it.
  • The accessible name switches from Copy to Copied on success so screen readers announce the result; the icon is decorative (aria-hidden).
  • Inherits the underlying Button's focus treatment: the global 2px :focus-visible outline in the ring ink (FOC-1), never outline: none. No variant adds a ring of its own — design-lint's no-focus-ring-glow rejects one.
  • A failed clipboard write (denied permission or insecure context) leaves the button in its default state instead of showing a false success.
KeyAction
TabMove focus to the button.
Enter / SpaceCopy value to the clipboard.
ContractStates tested
Behaviourdefault, copied, empty, pressed, success
Accessibilitylabeled, status-announcement
Visualdefault, hover, success, empty

Do / Don't

Do
Place the CopyButton next to the value it copies and pass onCopied to confirm the action with a toast.
Don't
Use it for long-form content a user should read or edit — copy buttons are for short, opaque values (keys, links, IDs).

On this page