Skip to content
Component installs need the registry setup— the Base UI shadcn project, the @vegastack namespace and the Cloudflare Access service token.
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

Add Copy Button from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.

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

The same command installs the registry items it composes: @vegastack/button, @vegastack/icon-button, @vegastack/use-announcer.

Usage

import { CopyButton } from "@/components/ui/copy-button";

<CopyButton value="npx shadcn add @vegastack/button" />;

Examples

Copy feedback

A ghost / sm icon 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 control switches from an IconButton (square, sm) to a text-bearing Button at the same size.

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.success("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" but forwards the whole Button variant × tone matrix unchanged.

Sizes

size is forwarded too, from the one xs · sm · md · lg vocabulary. It defaults to sm. An explicit size always wins.

Playground

Try the forwarded variant × tone 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*stringThe text written to the clipboard when the button is pressed.
classNamestring | ((state: ButtonState) => string | undefined)Classes or a Base UI state resolver merged with the button variants.
copiedLabelstring'Copied'Accessible label while the copied confirmation is visible.
copyLabelstring'Copy'Accessible label before the value has been copied.
data-loadingstringLoading-state marker for wrapper components that reflect a host-owned pending state onto a composed Button without its loading visuals (e.g. SplitButton's chevron half). The Button's own loading prop always wins when set.
data-slotstring'button'Slot marker for wrapper components that compose Button through Base UI render and need their own generated registry slot.
loadingbooleanfalseShows a spinner over the label, disables interaction, and sets aria-busy. The label keeps its box at opacity: 0, so the button's width does not move across the flip and its accessible name survives.
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 an IconButton. An explicit size still wins.
sizeButtonSize'md'Control height, from the one xs · sm · md · lg vocabulary the --size-* tokens carry.
timeoutnumber1500How long (in milliseconds) the check icon stays visible before reverting to the copy icon.
toneButtonTone
variant"cta" | "ghost" | "link" | "outline" | "soft" | "solid"

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 default ghost variant shows the design system's global 2px focus ring (outline-ring) on :focus-visible and never sets outline: none. The bordered outline variant additionally re-colors its border with the ring token (focus-visible:border-ring/(--alpha-tint-border)).
  • 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