# Copy Button

Copy a value to the clipboard, and say so. (Custom element.)

Reference: https://timeless.build/docs/components/copy-button/

> **Choosing between components.** Use Copy Button whenever a value on the page is meant to be taken somewhere else — an install command, a token, an id. The alternative is a plain button and a click handler, which is the same three lines every time and usually skips the announcement. For a long or changing value reach for `from` rather than `value`: it is read at activation, so nothing has to be kept in sync and nothing is duplicated into an attribute. For anything a string cannot carry — an image, a blob, `text/html` — answer `ui-before-copy` with `respondWith`, and the confirmation still follows your write.

> **Markup you author.** Give the trigger an `aria-label` and mark the `idle` and `copied` labels `aria-hidden="true"`. A button whose accessible name changes while it holds focus is announced inconsistently across screen readers, so the visible confirmation and the name are deliberately separate. Author the `status` region too: it is where the confirmation is announced, and without it the copy is silent.

## Markup

```html
<code class="ui-code" id="install-command">pnpm add @timelessui/components</code>
<ui-copy-button from="install-command" copied-message="Install command copied">
  <button class="ui-button" data-ui-variant="secondary" data-ui-part="trigger" type="button" aria-label="Copy the install command">
    <span data-ui-part="idle" aria-hidden="true">Copy</span>
    <span data-ui-part="copied" aria-hidden="true">Copied</span>
  </button>
  <span data-ui-part="status" role="status"></span>
</ui-copy-button>
```

## Install

```js
import '@timelessui/components/css/tokens.css'
import '@timelessui/components/css/core/button.css'
import '@timelessui/components/css/core/code.css'
import '@timelessui/components/css/core/copy-button.css'
import '@timelessui/components/css/themes/atmosphere/tokens.css'
import '@timelessui/components/css/themes/atmosphere/button.css'
import '@timelessui/components/css/themes/atmosphere/code.css'
import '@timelessui/components/css/themes/atmosphere/copy-button.css'
import '@timelessui/components/register/ui-copy-button'
```

A `register/` import is a side effect: it defines the element as the module evaluates, so it
has to run in code the browser loads. To register explicitly instead — to control the timing,
or to define into another window — call the function from the matching `define/` entry point,
which registers nothing on its own:

```js
import { defineCopyButtonElement } from '@timelessui/components/define/ui-copy-button'
defineCopyButtonElement()
```

## Anatomy

Parts are authored in your own markup and identified by the selector below. Required parts
must be present for the component to work. Private `data-ui-internal-*` hooks are written by
the runtime and must never be authored.

| Part | Required | Selector | Purpose |
| --- | --- | --- | --- |
| `trigger` | Yes | `[data-ui-part~='trigger']` | Native button that copies. Author it as `<button type="button">` with an accessible name; the name must not change on copy, because a button renamed while it holds focus is announced inconsistently across screen readers. |
| `idle` | No | `[data-ui-part~='idle']` | Shown while nothing has been copied. Decorative — hide it from assistive technology with `aria-hidden="true"`. |
| `copied` | No | `[data-ui-part~='copied']` | Shown briefly after a successful copy. Decorative in the same way, and its text is the default announcement. |
| `status` | No | `[data-ui-part~='status']` | A `role="status"` region the confirmation is written into. Author it — Timeless writes text and never creates the element — and without it nothing is announced. It is clipped away by default, because the visible confirmation is the `copied` part; restyle it if you want it seen. |

## Attributes

Every value below is implemented by the stylesheets this component ships. Boolean attributes
are presence-based: author the attribute with no value, or omit it.

| Attribute | Values | Default | Description |
| --- | --- | --- | --- |
| `value` | any `string` | — | The literal text to copy. Wins over `from` on presence rather than content, the way `value` does on an option: an explicit `value=""` is the author saying to copy nothing. |
| `from` | any `string` | — | Id of the element to read instead of `value`. An `input`, `textarea`, or `select` gives its current `value`; anything else gives its text. Read at activation rather than cached, so this is the one to reach for when the text is long or changes: it stays current on its own, and nothing is duplicated into an attribute. Assigning the `value` property works too, but a long string then reflects into the DOM. |
| `feedback-duration` | any `number` | `1800` | Milliseconds the `--copied` state persists after a successful copy, and with it the text in the `status` region — the two clear together, so copying the same value twice is announced twice. `0` clears both on the next task, which is short enough that a screen reader may miss the announcement. |
| `copied-message` | any `string` | — | What the `status` region announces after a successful copy. Falls back to the `copied` part’s text, so a button whose confirmation is a word needs no message at all and an icon-only one does. With neither, nothing is announced. |

## Element API

Attributes above are the authoring surface. These are the DOM properties and events the
registered element adds once it upgrades.

### `<ui-copy-button>`

| Property | Type | Notes |
| --- | --- | --- |
| `value` | `string` | Reflects the `value` attribute |
| `from` | `string` | Reflects the `from` attribute |
| `feedbackDuration` | `string` | Reflects the `feedback-duration` attribute |
| `copiedMessage` | `string` | Reflects the `copied-message` attribute |

| Event | Detail | Description |
| --- | --- | --- |
| `ui-before-copy` | `CustomEvent<CopyProposalDetail>` | Cancelable proposal dispatched before anything is written, carrying the resolved `value`. Call `preventDefault()` to reject the copy, and no `ui-copy` follows. Or call `detail.respondWith(promise)` to perform the write yourself — which is how you copy an image, a blob, or `text/html`, since `writeText` carries a string and nothing else. The element then awaits your promise and drives `--copied`, the announcement, and `ui-copy` from its outcome, so a confirmation never claims a copy that did not happen. Call it synchronously, and call your own clipboard method synchronously too — the click’s transient user activation is the same one the element depends on. `ClipboardItem` accepts a promised blob, so `new ClipboardItem({ 'image/png': blobPromise })` starts the write immediately while the data resolves. |
| `ui-copy` | `CustomEvent<CopyDetail>` | Dispatched once per activation, on success and on every failure, unless a listener cancelled the proposal. The detail carries `status`, the resolved `value`, and a `reason` naming what went wrong: `empty` when nothing resolved, `unsupported` when there is no Clipboard API, `denied` when the browser refused the write, and `rejected` when a `respondWith` promise failed. |

## State

Native attributes, ARIA, and platform pseudo-classes are authoritative. Style state through
the selectors below rather than adding your own state classes.

| State | Source | How to express it |
| --- | --- | --- |
| `--copied` | custom-state | Set for `feedback-duration` after a successful copy. Public: style `ui-copy-button:state(--copied)` yourself. |

## Styling

Root identity: `ui-copy-button`.
Required stylesheets: `tokens.css`, `core/button.css`, `core/code.css`, `core/copy-button.css`, `themes/atmosphere/tokens.css`, `themes/atmosphere/button.css`, `themes/atmosphere/code.css`, `themes/atmosphere/copy-button.css`.

This component adds no custom properties of its own. Restyle it through the design tokens at https://timeless.build/docs/styling/theming/ or your own CSS.

Design tokens this component reads (26), global and set at the theme level: `--ui-accent`, `--ui-bg-accent`, `--ui-bg-accent-active`, `--ui-bg-accent-hover`, `--ui-bg-control`, `--ui-bg-control-active`, `--ui-bg-control-hover`, `--ui-bg-control-muted`, `--ui-bg-danger`, `--ui-bg-danger-active`, `--ui-bg-danger-hover`, `--ui-bg-surface`, `--ui-duration-fast`, `--ui-ease-standard`, `--ui-fg`, `--ui-focus`, `--ui-font-mono`, `--ui-line`, `--ui-radius-control`, `--ui-radius-sm`, `--ui-shadow-control-accent`, `--ui-shadow-control-active`, `--ui-shadow-control-danger`, `--ui-shadow-outline-control`, `--ui-space-2`, `--ui-space-4`.

## Accessibility

Keep the native elements, roles, and relationships shown in the markup. Timeless adds only the
state and keyboard coordination the platform does not already provide, and it never supplies
your accessible names — those depend on your content.

Follows the [Button pattern](https://www.w3.org/WAI/ARIA/apg/patterns/button/) from the ARIA Authoring Practices Guide.

The trigger is a native button, so activation, focus, and the accessible name are the platform’s. Timeless adds only what the platform cannot: the `idle`/`copied` swap is decorative and stays hidden from assistive technology, and the confirmation reaches a screen reader through the authored `status` region rather than by renaming the button. Copying needs `navigator.clipboard`, which is absent outside a secure context; a trigger authored `hidden` is revealed only once the API is there, so a control that cannot work is never shown.

## Before JavaScript runs

The markup above is complete and usable on its own: native controls submit, labels associate, and authored ARIA is already correct. Registration adds state synchronisation, focus management, and keyboard coordination on top of it.
