Copy button

Copies a value to the clipboard and confirms it in place.

Examples

Fetch a key, then copy it

Helpdesk widgetsk_demo_0000••••••••••••

Inside a well

tkt_8f2c41

Label

  • hiddendefault

  • shown

Styles

  • ghostdefault

  • secondary

States

  • rest

  • hover

  • focus

  • disabled

  • fetching

  • copied

  • failed

Usage

When to use it
Use
The person needs a value somewhere else: an ID, a key, a canned reply, a URL.
Use
Selecting by hand is slow or error-prone: a 40-character key, a token, a multi-line reply.
Not for
The value has to be read and edited, not copied. Use Input.
Not for
The value is a block of code with a copy action in its corner. Use Code block.
Not for
The action shares a link; label it "Copy link". Use Button.

Rules

7 rules
Must
Pass a function that fetches a secret; never the secret itself. Because a secret passed as value sits in the page's props before anyone asks to copy it.
Never
Add a toast on success; the button confirms in place. Because the icon turns to a check and a status region says "Copied", so a toast says it twice.
Must
Pass label on an icon-only button so each one has its own name. Because without label every icon button is named "Copy", so a row of three reads the same.
Must
Set label to the thing copied, such as "conversation ID"; never a sentence or a verb. Because the name is "Copy {label}" and the announcement "{label} copied", so a sentence reads twice.
Never
Swap a Button label between Copy and Copied; use CopyButton. Because the swap changes the width and a screen reader hears nothing, where CopyButton holds its width and announces.
Should
Give each copyable value its own button, up to 3 per row; never one that copies a combination.
Should
Hide the button when the value is empty.

Accessibility

5 checks
The button is named `Copy <label>` and sets aria-busy while fetching.
A fixed, sr-only role="status" region announces `<label> copied` on every copy.
A failure announces "Could not copy. Select the text and copy it by hand."
The glyph and words are aria-hidden; the glyph shape (check, warning) carries the state without color.
Icon only is 28px, past the 24px target.

Edge cases

12 edge cases
2,000-character value
Copies all of it; truncate the value beside the button in the middle.
Empty value, no label
Copies an empty string and says "Copied"; hide the button.
Inside a scroller
The status region is fixed and sr-only, so it never adds width.
Right to left
Glyph and word mirror their order.
Zoom 200%
With showLabel the box grows with the text; icon only stays 28px.
Tap on iOS WebKit with a fetched value
Uses clipboard.write with a ClipboardItem promise in the same tick; falls back to writeText.
Value takes 1.2s; never resolves
Pends in the same box; the caller times out the fetch.
Clipboard denied or fetch rejects
Warning glyph and "Failed" for 1.6s.
Ten presses in a second
Each copies and restarts the timer; the box never moves.
Inside an Input suffix or code block corner
Ghost flips with the surface channels.
Forced colors
Button draws its ButtonText border; glyph shapes switch.
Reduced motion
The glyph swap is a cut; the spinner stands still.

API

7 props
value
string | () => string | Promise<string> default required
label
string
showLabel
boolean default false
timeout
number default 1600
onCopy
(value: string) => void
forceState
'fetching' | 'copied' | 'failed'
variant, size
Button default 'ghost', 'icon-sm' or 'sm'

Tokens

7 tokens
--button-ghost
transparent
--button-ghost-hover
oklch(var(--ink-c) / 0.05)
--line-control
oklch(var(--ink-c) / 0.16)
--control-sm
28px
--line-focus
2px
--border-focus
var(--ink)
--focus-offset
2px

Install

For your coding agent

GitHub

Please star me pookie

141

A new design system every week

Leave your email before you copy the prompt.