Copy Button
The one copy affordance: a quiet icon button that writes value to the
clipboard, swaps its glyph to a check for a moment, and says Copied to
assistive tech through a polite live region. Ghost at rest so a mono value
stays the loudest thing on its line. LogList, DescriptionList, and
CodeBlock use it; put it after any id or key a user will paste somewhere.
Give it the value as children and the value is a click target too.
Import
import { CopyButton } from "@photon-ai/pho-ui/components/copy-button";With the value
children is the view of the value, rendered before the button. A click
anywhere on it copies, the way a <label> presses its control, and the
wash lifts over the button while the pointer is on either. With a view the
glyph rests in description ink, so it is never louder than the value it
copies. The button
stays the one tab stop and the accessible name; the view reads as text.
<CopyButton value={project.id} label="project id" size="xs">
{project.id}
</CopyButton>Custom view
Put anything in the view. It sets a default type (the mono face, xs,
primary ink, one line that truncates), and what you put in sets its own:
a number in the body face, a masked key in secondary ink. value is
still what goes to the clipboard, so the view can show less than it
copies. A link inside keeps its own click, and dragging across the text
selects it instead of copying.
<CopyButton value={number} label="number" size="xs">
<span className="font-sans text-base font-medium">{formatted}</span>
</CopyButton>
<CopyButton value={key.secret} label="API key" size="xs">
<span className="text-pho-secondary">{key.masked}</span>
</CopyButton>Sizes
Three square sizes: xs (14px icon, 24px hover wash that overflows the
line) inline after a value, sm (28px, the default) in a row cell or a
panel header, md (32px) in a toolbar.
<CopyButton value={project.id} label="project id" size="xs" />Props
- value —
string— what goes to the clipboard - children —
ReactNode— the view of the value, before the button; a click on it copies too - label —
string(defaultto clipboard) — what is being copied; read asCopy {label}, then{label} copied - size —
xs/sm/md(defaultsm) - onCopied —
(value: string) => void— after the write lands - …plus any
<button>attribute
The state behind it is useCopy() from @photon-ai/pho-ui/utils: copy(text)
resolves to whether the write landed, and copied holds for
COPY_FEEDBACK_MS (1.5 s) so a control of your own can swap its glyph.
Accessibility
- Named for the thing.
Copy request id, neverCopy; the label is the only thing a screen reader hears. - Feedback in two channels. The glyph turns to a check, and a polite
live region says
Copied. - The view is a pointer shortcut. Like a
<label>, it is not a tab stop; keyboard and screen reader users reach the button, named for the thing, and hear the value as the text before it. - Refusal is quiet. When the clipboard refuses (no permission, an
insecure page), nothing changes;
onCopiedis not called.
Best practices
- One per value, right after it. A row with three copy buttons is a table.
- Pass the value as
childrenrather than setting it beside the button, so the whole value is the target, not only the 14px glyph. - Copy the whole value even when the display is masked or truncated; that is what the button is for.