<!-- GENERATED by scripts/build-llms.mjs from llms/utility.md — do not edit this file. -->

# `lr-copy-button`

- **Import** `import '@aceshooting/lyra-ui/components/lr-copy-button.js';` (stable tag alias; registers the tag)
- **Class** `LyraCopyButton`, also available unregistered from `@aceshooting/lyra-ui/components/utility/copy-button/copy-button.class.js`
- **Family** `components/utility/` — see `llms/index.md` for its siblings
- **Status** `stable` since `4.0.0` — see the maturity and deprecation policy in `llms/shared.md`
- **Release history** [CHANGELOG.md](../../CHANGELOG.md); family-wide breaking-change summaries: [llms-full.txt](../../llms-full.txt)
- **Deprecations** none
- **Optional peers** none
- **Themeable via** 13 parts, 2 custom properties — see this component's own `@csspart`/`@cssprop` list below
- **Library-wide behavior** (events, form association, `locale`/`strings`, tokens, TS types): `llms/shared.md`

---

## `lr-copy-button`

A standalone copy-to-clipboard affordance for a plain text `value` or a source element selected by
`from`. Its built-in icon button swaps to a confirmation or failure glyph once the Clipboard API
settles; a consumer-provided default-slot trigger can replace that button. The component takes no
positioning opinion of its own.

The CopyFailure example temporarily supplies a denied clipboard operation during click capture,
including after a held pointer press, then restores the original own property descriptor or removes
its temporary override when the clipboard was inherited. Pointer gestures that end without a click
do not install an override. This example does not change the clipboard contract of ordinary copy
buttons.

**Properties:**

- `value: string = ''` — the plain text to copy.
- `from: string = ''` — source expression that takes precedence over `value`. `id` copies the
  element's `textContent`, `id[attribute]` copies an attribute, and `id.property` copies a property.
  Resolution prefers an element in the button's own root, then falls back to the owner document;
  a duplicate same-root id therefore wins without preventing a shadow-contained button from
  targeting an otherwise valid document-owned source.
- `copyLabel: string = ''` (attribute `copy-label`) — built-in button accessible name and resting
  tooltip text; empty uses localized `copy`.
- `successLabel: string = ''` (attribute `success-label`) — confirmation name/tooltip text; empty
  uses localized `copied`.
- `errorLabel: string = ''` (attribute `error-label`) — failure name/tooltip text; empty uses
  localized `copyFailed`.
- `tooltip: 'full' | 'copy' | 'none' = 'full'` (reflected) — `full` shows the resting tooltip on
  hover/focus and feedback after activation, `copy` shows feedback only, and `none` disables it.
- `tooltipPlacement: 'top' | 'right' | 'bottom' | 'left' = 'top'` (attribute
  `tooltip-placement`, reflected).
- `hoist: boolean = false` (reflected) — uses fixed tooltip positioning to escape clipped
  containers.
- `accessibleLabel: string | null = null` (attribute `aria-label`) — overrides the localized
  built-in button name while leaving tooltip/feedback labels unchanged; retained Lyra alias.
  An empty or whitespace-only value falls back to the localized state label so the built-in
  icon-only trigger always remains named; remove the attribute or assign `null` for the same
  fallback.
- `disabled: boolean = false` (reflected)
- `feedbackDuration: number = 1000` (attribute `feedback-duration`) — milliseconds before the
  confirmation **or** the failure state returns to the copy icon. A non-finite value falls back to
  `1000` rather than leaving the state stuck; a negative one clamps to `0`.

**Methods:** `focus(options?)`, `blur()` and `click()` forward to the active built-in or custom
trigger. `getToolbarActions(): readonly LyraToolbarAction[]` implements the public logical-toolbar
provider protocol with one stable action whose id is `copy`; its focus, roving tab index, disabled
state, and composed-event matching follow the active trigger without exposing that node. Its
`releaseTabIndex()` action method lets a parent stop managing its tab index on disconnect, adoption,
trigger replacement, and parent departure: an untouched author-supplied `tabindex` is restored,
while a newer author change is never overwritten.

**Events:**

- `lr-copy` (`detail: LyraClipboardWriteSuccess`, `{ ok: true; text: string }`) — fires only after
  the owning browsing context's clipboard write fulfills. The detail is a frozen shared outcome.
- `lr-error` (no detail) — bubbling, composed, non-cancelable notification that source resolution
  or clipboard writing failed.
- `lr-copy-error` (`detail: LyraClipboardWriteFailure`,
  `{ ok: false; text: string; reason: LyraCopyErrorReason; error: unknown }`) — the frozen detailed
  failure outcome. `reason` is `'unsupported'` (no Clipboard API), `'denied'`
  (`NotAllowedError`/`SecurityError`) or `'failed'` (including a missing/empty source and other
  platform failures); the error field contains the original platform or component-created error.
- `lr-toolbar-actions-change` (no detail) — bubbling/composed coordination event emitted when the
  logical action's disabled state or backing trigger changes, so an enclosing
  `<lr-message-actions>` can repair its roving tab stop.

The nested tooltip's `lr-show`/`lr-after-show`/`lr-hide`/`lr-after-hide` lifecycle is internal
implementation detail and is contained at the copy-button boundary; it is not part of this event
surface and cannot be canceled through an ancestor of `<lr-copy-button>`.

**Slots:** default custom trigger, plus `copy-icon`, `success-icon`, and `error-icon` overrides for
the built-in button. Exactly one named icon is rendered at a time.

**CSS parts:**

- `base` / `button` — the built-in trigger, a composed `<lr-icon-button>` as of 16.0.0. It owns the
  accessible name, the activation and every state part below; its background, radius, hover/press
  mixes, focus ring and hit-area floor come from `--lr-icon-button-*`.
- `base__control` — the composed `<lr-icon-button>`'s own native `<button>`. A rule that set
  `background`/`border`/`padding` through `::part(base)` moves here, or onto the token.
- `base-success` — added to the button's part list while the confirmation shows
  (`part="base button base-success"`).
- `base-error` — the same while the failure state shows (`part="base button base-error"`).
- `copy-icon`, `success-icon`, `error-icon` — the resting, confirmation and failure glyphs. Exactly
  one is rendered at a time; all three are `aria-hidden`.
- `feedback` — the visually hidden, `aria-hidden` mirror of the outcome text. Empty at rest, so
  nothing is announced before a real outcome. It carries no live-region role of its own: the
  announcement goes to the library's shared **light-DOM** polite region, appended to the consumer's
  `<body>` and marked `data-lr-live-region="polite"`, because a live region inside a shadow root is
  not reliably announced (JAWS with Firefox ignores one outright). Assert against that
  document-level region rather than `::part(feedback)`; the part is a styling and inspection
  surface, and still tells you what the button last announced.
- `tooltip__base`, `tooltip__base__popup`, `tooltip__base__arrow`, `tooltip__body` — exported nested
  tooltip parts.

**CSS custom states:** `success` and `error`. The retained `base-success`/`base-error` part names
remain available for shadow-part styling.

**Themeable custom properties:** `--success-color` (default `var(--lr-color-success)`) and
`--error-color` (default `var(--lr-color-danger)`), plus shared hit-area, text, focus, transition,
and disabled-opacity tokens.

**Optional peer deps:** none.

```html
<code id="install-command">npm install @aceshooting/lyra-ui</code>
<lr-copy-button
  from="install-command"
  copy-label="Copy install command"
  success-label="Install command copied"
  tooltip-placement="right"
  hoist
></lr-copy-button>
```

Handling the failure path — the button already shows and announces it, so a listener is only needed
for an application-level fallback:

```html
<lr-copy-button
  id="copy"
  value="npm install @aceshooting/lyra-ui"
></lr-copy-button>
<script type="module">
  import "@aceshooting/lyra-ui/components/utility/copy-button/copy-button.js";

  const button = document.getElementById("copy");
  button.addEventListener("lr-copy", (event) =>
    trackCopySuccess(event.detail.text)
  );
  button.addEventListener("lr-error", () => showCopyFallback());
  button.addEventListener("lr-copy-error", (event) => {
    // event.detail.reason is 'unsupported' | 'denied' | 'failed'
    if (event.detail.reason === "unsupported")
      selectTextForManualCopy(event.detail.text);
  });
</script>
```

The closed-set and error-reason types are exported alongside the class:

```ts
import type {
  LyraClipboardWriteFailure,
  LyraClipboardWriteOutcome,
  LyraClipboardWriteSuccess,
  LyraCopyButtonTooltip,
  LyraCopyButtonTooltipPlacement,
  LyraCopyErrorReason,
} from "@aceshooting/lyra-ui/components/utility/copy-button/copy-button.class.js";
```

**Known gotchas:**

- **Changed in 8.0.0:** the button used to enter the "Copied" confirmation on activation whether or
  not the clipboard write succeeded. It now waits for `navigator.clipboard.writeText()` to settle: a
  rejection renders the failure glyph instead, announces the localized failure text through the
  shared polite region mirrored by `[part="feedback"]`, and emits `lr-error` plus `lr-copy-error`.
  `lr-copy` is now the success-only fulfilled outcome; code that tracked activation attempts from
  that event should instead track the initiating click separately.
- An empty `value`, missing `from` target/member, or empty resolved source is an error; no clipboard
  write is attempted. `from` always wins over `value`, including when it is invalid.
- `navigator.clipboard` is absent in insecure contexts/older browsers, and some engines throw
  synchronously rather than rejecting. Both arrive at the same failure path (`unsupported` for the
  missing API, `denied`/`failed` for a real rejection) — there is no silent success left.
- Changing `value`/`from`, or disconnecting, clears any in-progress confirmation/failure
  immediately. A write that settles after that change is discarded.
- A custom default-slot trigger supplies its own semantics and accessible name; built-in icon
  slots, `button`/`base` parts, and `aria-label` forwarding apply only to the built-in button.
- The failure is signalled on four channels — a different glyph, a different accessible name, the
  live-region announcement, and only then colour — so it survives a monochrome or high-contrast
  rendering.
- Copy affordance strings are localizable: `copy` (`'Copy'`), `copied` (`'Copied!'`) and
  `copyFailed` (`'Copy failed'`), overridable per instance through `.strings` or app-wide through
  `registerLyraLocale()` (see `llms/shared.md`). Explicit `*-label` properties take precedence.
- Native `dir`/`lang` remain inherited global attributes. The component is not form-associated.
- No `size`/`compact` property: the built-in trigger's hit area is `<lr-icon-button>`'s shared
  `--lr-icon-button-size` floor (2.5rem/40px). For a dense action row, lower
  `--lr-theme-icon-button-size` (not `--lr-icon-button-size`, which every `LyraElement` re-declares
  on its own `:host` and so never reaches a composed child) on this element or an ancestor, or reach
  the composed native control directly through `::part(base__control)`. A coarse-pointer/no-hover
  media rule then floors the rendered hit area at 2.75rem/44px regardless of how far a dense-row
  override lowered it, so the shrink is safe on a touch device.

---
