Copying is one action with a state that lasts two seconds, and the state is the whole of the
component: a value is written, the control says it worked, and then it goes back to being a
control. The framework ships it as two halves, `CopyButton`, a button that already says the
right thing, and `Clipboard`, the headless primitive that owns the writing and the timing and
renders whatever a consumer's render function returns.

What separates it from a `Button` with a clipboard call in its handler is that the result is
reported twice. The label changes to `Copied` and the same wording is written into a live region
beside it, because a label that changes under the reader's finger is not announced by a screen
reader; the framework's control supplies both so a consumer does not have to remember the second.

```tsx demo="data-display/clipboard-preview"
```

## Usage

```tsx
import { CopyButton } from "@asheeui/web";

<CopyButton
  value="INV-1042"
  label="Copy invoice number"
  copiedAnnouncement="Invoice number copied"
/>
```

`value` is what is written, and it is the only required prop. `label` and `copiedLabel` are the
two names of the control, registered as `Copy` and `Copied`; `copiedAnnouncement` is the sentence
the live region receives, and without it the announcement is the copied label and the value
together, `Copied: INV-1042`, which is usually too much to hear but always true.

The copied state lasts `timeout` milliseconds, registered as `2000`: long enough to read, short
enough that the control is a copy control again by the time a reader wants it. `onCopy` receives
the text that was written, so a screen can record the copy or offer to undo it, and `onError`
receives whatever the browser threw.

`copyIcon` and `copiedIcon` replace the drawing before the label. Everything else is a button's:
`variant`, `color`, `size`, `radius`, `className`, `isDisabled` and the rest reach the framework's
`Button` unchanged, so a copy control sitting in a table row is a small ghost button and one at
the foot of a dialog is whatever the dialog's actions are.

## Your own control

`Clipboard` is the half that does the work. It renders nothing of its own: it takes the value,
the timing and a render function, and hands that function the state to draw with, `copied`,
`error`, `copy` and `reset`.

```tsx
import { Clipboard } from "@asheeui/web";

<Clipboard value={invoice.id}>
  {({ copied, copy }) => (
    <button type="button" onClick={() => void copy()}>
      {copied ? "Copied" : "Copy invoice number"}
    </button>
  )}
</Clipboard>
```

```tsx demo="data-display/clipboard-own-control"
```

`copy` writes the component's `value`, or a text passed to it, `copy(row.email)` is how one
control copies a different cell of the row it sits in. It resolves to whether the write
succeeded, so a consumer can branch without reading the state:

```tsx
const copied = await copy();
if (!copied) report("The clipboard refused us");
```

`error` is the other half of that answer, and it is a state a consumer usually wants to show,
because a browser may refuse clipboard access outside a user gesture or without permission. The
primitive does not give up silently: when the asynchronous clipboard API is missing it falls back
to a temporary selection and `document.execCommand("copy")`, restoring focus to whatever was
focused before, so a page over plain HTTP still copies. `reset` clears both states without
copying, for a control that is dismissed before its two seconds are up.

**The announcement is the consumer's.** `Clipboard` renders exactly what the render function
returns, so a bare `Clipboard` that draws a button and no status region copies silently. That is
why the preview on this page uses `CopyButton`: it is the same primitive with the live region
already in it. A consumer's own control needs its own `role="status"` element, as the demo above
has.

## Props

```props Clipboard
```

`Clipboard` takes the value, the timing, the two callbacks and the render function, and resolves
`timeout` from the instance, then `components.clipboard.timeout`, then the fallback of `2000`.

```props CopyButton
```

The registered values for the control are `label` `Copy`, `copiedLabel` `Copied`, `variant`
`bordered`, `color` `primary`, `size` `sm` and `timeout` `2000`, a small bordered button with the
product's own accent, because a copy control is a secondary action beside the thing it copies.
`radius` resolves from the instance, then `components.clipboard.radius`, then the application's
`defaultRadius`, then the fallback `sm`, the same cascade the `Button` it renders follows, so a
product that rounds its buttons rounds its copy control with them unless it says otherwise.

`value`, `label`, `copiedLabel`, `copiedAnnouncement`, `errorAnnouncement`, the two icons and the
two callbacks are instance-only. What is being copied, what it is called and what is said about
it are statements about one control in one place; the treatment and the timing are statements
about the design.

## Accessibility

- The result is reported twice: the label becomes `Copied` and a sentence is written into a
  visually hidden `role="status"` region with `aria-live="polite"`. The region is `sr-only`, so it
  stays in the accessibility tree without taking space, and it is the reason a copy is announced
  at all, a label change alone is not.
- The control is the same `<button type="button">` before and after a copy, so focus never moves:
  a reader hears the announcement where they are and can copy again immediately. `type="button"`
  means it never submits a form it happens to sit in.
- The announcement names what was copied, by default the copied label and the value, or
  `copiedAnnouncement` when a product wants a shorter sentence. Because it is a live region, the
  sentence replaces the previous one, so a value with a long URL in it is read out in full unless
  the consumer shortens it.
- A failure is a state rather than silence, and it is announced as one: the write being refused,
  or there being nothing to copy, sets `error`, leaves the label on `Copy` (a control that could
  not copy never claims it did) and writes a sentence into the same live region, `Copy failed`
  unless `errorAnnouncement` says otherwise. The consumer still receives `onError` whether or not
  it also states its own wording.

## Native

`@asheeui/native` ships both halves with the same props, and copies through the platform's own
clipboard module rather than a selection. The package carries no icon set, so the affordances are
the characters `⧉` and `✓` unless `copyIcon` or `copiedIcon` replaces them, the control's name is
its wording rather than the wording plus a glyph, and the status region is a pixel-sized
transparent view that stays in the accessibility tree. See [React Native](/platforms/native).

## Related

```related button, toast, link
```
