Components · Actions
CopyButton
Copies a value to the clipboard, confirms it visibly and to screen readers, and reports failures.
Labels and icon-only
import { CopyButton } from '@rookcom/monarch-react';
<CopyButton value="https://example.com/hooks/inbound" target="webhook URL" />
<CopyButton
value="https://example.com/s/q3-plan"
label="Copy link"
copiedLabel="Link copied"
variant="primary"
/>
<CopyButton value="ws_01HZX4" label="Copy workspace ID" iconOnly size="sm" />Usage examples
Reporting a failure
import { useState } from 'react';
import { CopyButton } from '@rookcom/monarch-react';
function WithErrorReport() {
const [problem, setProblem] = useState<string | null>(null);
return (
<div style={{ display: 'grid', gap: 'var(--mn-space-2)', justifyItems: 'start' }}>
<CopyButton
value="https://example.com/invite/7f3a"
label="Copy invite link"
copiedLabel="Link copied"
onCopy={() => setProblem(null)}
onCopyError={(error) => setProblem(error.message)}
/>
{problem ? (
<p style={{ margin: 0, fontSize: 'var(--mn-text-xs)' }}>
Copy failed ({problem}). Select the link and copy it manually.
</p>
) : null}
</div>
);
}
<WithErrorReport />useCopyToClipboard
import { useCopyToClipboard, Button } from '@rookcom/monarch-react';
function HookDemo() {
const { copy, status } = useCopyToClipboard();
return (
<Button size="sm" onClick={() => void copy('ORDER-2024-0193')}>
{status === 'copied' ? 'Reference copied' : 'Copy reference'}
</Button>
);
}
<HookDemo />Purpose
One button that copies a value (a link, a key, a DNS record), shows a short "Copied" state, announces the result in a polite live region and hands errors to the caller. useCopyToClipboard is the same logic for custom controls.
Use when
- The user needs a value elsewhere: an invite link, a webhook URL, an ID, a code snippet.
- You are building your own copy control: use
useCopyToClipboardso the success, error and reset behaviour match.
Avoid when
- The value should also be visible and selectable: use CopyField.
- The value is a secret: use SecretField with
copyable, which copies without revealing it.
Design principles
- Feedback is visible and spoken: the icon becomes a check and the label "Copied", and a polite live region says "Copied to clipboard".
- Never fails silently. The Clipboard API is tried first, then the legacy
execCommandpath; if both fail the button shows "Couldn't copy", announces it, and callsonCopyError. - The button keeps its width: all three labels share one grid cell, so nothing jumps when the state changes.
- Built on Button, so variants, sizes, focus and disabled behaviour are the same as every other action.
API reference
import { CopyButton, useCopyToClipboard, writeToClipboard } from '@rookcom/monarch-react';| Export | Kind | Rendering | Root class |
|---|---|---|---|
CopyButton | component | client | .mn-copy-button |
useCopyToClipboard | hook | client | – |
writeToClipboard | utility | client | – |
CopyButton forwards its ref to the <button>. It renders a visually hidden role="status" sibling for announcements.
useCopyToClipboard({ resetAfter, onCopy, onError }) returns { copy, status, error, reset }. copy(value) resolves true or false and never rejects.
writeToClipboard(text) is the low-level write (Clipboard API, then the legacy fallback); it rejects with an Error when both fail.
Generated from the TypeScript declarations of packages/react/src/components/actions/copy-button.
Properties
All Button props except children, asChild and leadingIcon pass through, including variant and size.
CopyButton
Copies a value to the clipboard, shows a short "Copied" state and announces the result through a polite live region. Errors are reported to the caller through `onCopyError`; the button never fails silently.
| Prop | Type | Description |
|---|---|---|
value required | CopyValue | The text to copy, or a function that produces it when clicked. |
block | boolean | Stretch to the container width. |
brandColor | string | Brand or tenant colour for `variant="brand"`, as `#rrggbb` or `#rgb`. The fill and text are derived per theme by the tokens contrast engine, so the label always meets WCAG AA (7:1 in more-contrast themes); the colour may be darkened or lightened to get there. Ignored by other variants. An invalid or missing colour falls back to the product accent. |
className | string | |
copiedLabel | string | Label while in the copied state. Default "Copied". |
copiedMessage | string | Polite announcement after a successful copy. Default "Copied to clipboard". |
disabled | boolean | |
errorLabel | string | Label after a failed copy. Default "Couldn't copy". |
errorMessage | string | Polite announcement after a failure. Default "Couldn't copy. Select the text and copy it manually." |
icon | ReactNode | Replaces the copy icon. Decorative. |
iconOnly | boolean | Hide the visible text and show only the icon. `label` stays the accessible name, so give it the object: "Copy API key". |
id | string | |
label | string | Visible label, and the accessible name. Default "Copy". |
loading | boolean | Shows a spinner, sets aria-busy and blocks clicks while keeping focus. |
onCopy | ((text: string) => void) | Called with the copied text after a successful copy. |
onCopyError | ((error: Error) => void) | Called when copying fails. Use it to show a toast or a manual-copy fallback. |
resetAfter | number | Milliseconds before the button returns to its idle look. Default 2000. |
size | ButtonSize | |
target | string | What is copied, for screen readers ("webhook URL"). Appended to the visible label as visually hidden text, so several copy buttons on one page have distinct names: "Copy webhook URL", "Copied webhook URL". |
trailingIcon | ReactNode | |
variant | ButtonVariant | Visual weight. Use one `primary` per view region. Default `secondary`. |
Variants
| Prop | Values | Description |
|---|---|---|
variant | 'primary''secondary''ghost''danger''danger-subtle' | From Button. Default secondary, or ghost when iconOnly. |
size | 'xs''sm''md''lg' | From Button. Default md. |
iconOnly | 'true''false' | Square, icon-only button (data-icon-only). label stays the accessible name and tooltip. |
States
| State | Exposed as | Behaviour |
|---|---|---|
| Idle | data-state="idle" | Copy icon and label. |
| Copied | data-state="copied" | Check icon in the success colour and copiedLabel, for resetAfter milliseconds (default 2000). |
| Error | data-state="error" | Warning icon in the danger colour and errorLabel; onCopyError has been called. |
| Disabled | disabled | From Button: nothing is copied. |
Interaction behaviour
- Click, Enter or Space copies
value. A functionvalueis called at that moment and may be async. - Your
onClickruns first; callevent.preventDefault()in it to skip the copy. - Each attempt clears and refills the live region, so copying twice is announced twice.
- The state returns to idle after
resetAfter;0keeps it until the next attempt.
Keyboard support
| Keys | Action |
|---|---|
| Tab | Moves focus to the button. |
| Enter / Space | Copies the value. |
Accessibility
- Role
- button
- Accessible name
- The visible label, plus the visually hidden
target("Copy" + "invite link"). Icon-only:label(+target) asaria-label.
- The result is announced through a
role="status"live region outside the button, so it is not part of the button name. - Icon-only buttons keep a stable
aria-label; the change is announced instead of renamed. - Use
targetwhen several copy buttons share a screen, so each has a distinct name (WCAG 2.4.6). The visible text stays at the start of the name (WCAG 2.5.3). - Icons are decorative (
aria-hidden).
Responsive behaviour
- Same height as Button at every width (
--mn-control-*). - The label never wraps; use
iconOnlyin tight rows such as table cells.
Theme support
- Colours come from Button and the success and danger tokens, so light, dark and more-contrast themes apply automatically.
- On
primaryanddangerbuttons the state icon inherits the on-solid text colour. - The icon colour transition is removed under
prefers-reduced-motion: reduce.
Do and don't
Do
- Name what is copied:
target="API key"orlabel="Copy invite link". - Handle
onCopyError: show a toast or point the user at a selectable copy of the value. - Use CopyField when the user should also see the value.
Don't
- Do not show a toast as the only confirmation; the button already confirms visibly and to screen readers.
- Do not put a secret in a plain CopyButton next to its plain text; use SecretField.
- Do not change
labelwhile the button is showing "Copied"; usecopiedLabel.
Related components
- CopyField: Shows the value, read-only, with this button beside it.
- SecretField: Copies a masked secret without revealing it.
- Button: The control CopyButton is built on.
- Toast: A product-level way to report
onCopyError.
Dependencies
| Kind | Depends on |
|---|---|
| npm packages | react |
| Monarch components | Button |
Testing coverage
- Copies the value, shows the copied state, and announces "Copied to clipboard".
- Copies with Enter and Space.
- A refused write calls
onCopyError, shows the error state and announces it. - Falls back to
execCommandwhen the Clipboard API rejects; rejects when nothing is available. - Returns to idle after
resetAfter. - Accepts sync and async function values.
- Icon-only keeps a stable accessible name.
- Does not copy when disabled or when
onClickprevents default. - Inactive labels are hidden from the accessible name.
useCopyToClipboardexposes status, error and reset.
| Check | Covered |
|---|---|
| Axe in jsdom | Yes |
| Server rendering | Yes |
| Browser suites | Visual baselines: /__gallery; Cross-browser axe: /components/copy-button |
Tests: packages/react/src/components/actions/copy-button