# Copy Controls

Agents: start with [Workspace for Agents](https://workspace.socra.com/solutions/agents.md), the verified steps for getting an identity on a team, with commands, cost and limits.

Socra: Give your agents an identity on your team, and they become AI teammates you can delegate to.

Canonical: https://socra.design/components/copy-controls

## Governing rules

[Building with Socra components](https://socra.design/agents.md) explains setup, consumption rules and verification.

Semantic owner: Flash Module `ui-component-family-action` in Socra Cortex when available. It supplies current intent and prerequisite rules; the public API below supplies exact code names and values.

Use published property names, not inspector labels. Composition and preview settings are not component props. Public styling escape hatches do not permit product-local presentation overrides.

CopyButton, CopyIconButton and useCopyFeedback: truthful feedback for clipboard actions.

Use CopyButton when the action needs a visible label, or CopyIconButton beside already identified content. Use CopyId when the identifier itself belongs in the control. Keep the value available for manual selection when copying fails.

Status: ready. The specimen uses public copy controls and real clipboard operations; the portable example also demonstrates the public feedback hook.

Library: @socra/ui-web-action, @socra/ui-web-hooks

## Anatomy

- A visible label or a specifically named icon identifies the action.
- Shared success and failure feedback replaces the resting feedback without moving the control.

## States

- **Rest:** The resting label identifies what will be copied. CopyButton stays disabled until its client readiness check has completed.
- **Hover:** The shared action treatment responds to pointer hover; the icon also reveals its label as a tooltip.
- **Focus:** Enabled controls retain shared visible focus and native Enter or Space activation.
- **Pressed:** Native activation starts the configured operation. Pressed appearance alone never means copying succeeded.
- **Loading:** A pending operation does not show Copied. A newer request supersedes older feedback; controls do not expose a loading prop or automatically prevent another request.
- **Success:** Only a successful current operation shows Copied. Changing a text prop clears feedback and invalidates pending results for the previous text. Recreating a custom operation callback does not identify changed content.
- **Disabled:** The disabled control ignores activation and leaves ordinary keyboard navigation.
- **Error:** A current rejection shows Copy failed and calls onError. The control remains available for retry, and CopyButton retains its configured action variant.

## Motion

Feedback settles after the shared confirmation interval. CopyButton reserves the actual resting, success and error labels so feedback does not move adjacent controls; reduced motion keeps the same meaning and geometry.

## Usage

- Keep text as the source when its identity matters. The component can then discard a late completion after the displayed content changes.
- Use onCopied for work that requires successful copying. onCopy performs a custom operation; it is not a success event.
- Provide room for every feedback label. small, medium and large are fixed single-line sizes. fullWidth fills the host but does not enable wrapping. Use the renderer Button content size with a suitable composition when bounded multiline content is required.

## Avoid

- Do not recreate clipboard timers in products. The shared controls and hook keep completion, retry and superseded-request behavior consistent.
- Do not treat copied as proof for changing hook input. The hook receives text only when copy(text) runs; it cannot observe a changing text prop. Prefer a shared copy control for that contract.

## Properties

[Complete public API, including inherited properties and event signatures](https://socra.design/components/copy-controls/api.md)

### Setup

- Use compatible published @socra/ui-web-action, @socra/ui-web-hooks and renderer dependencies under the application theme provider. Import @socra/web-ui/styles.css once. Clipboard access follows browser permissions and secure-context requirements.

### CopyButton

Import: `import { CopyButton } from '@socra/ui-web-action';`

| Property | Type or permitted values | Default / omission | Required | Meaning and behavior | Inspector |
| --- | --- | --- | --- | --- | --- |
| `copiedLabel` | `string \| undefined` | `"Copied"` | no | Visible and announced success label; its full width is reserved alongside the other feedback labels. | Copied label (when control is button) |
| `disabled` | `false`, `true` | Omitted; no declared default | no | Prevents activation and removes the control from ordinary keyboard navigation. | Disabled |
| `errorLabel` | `string \| undefined` | `"Copy failed"` | no | Visible and announced failure label; the action variant remains unchanged. | Error label (when control is button) |
| `fullWidth` | `false`, `true` | Omitted; no declared default | no | Fills the host width. It does not enable label wrapping. | Full width (when control is button) |
| `label` | `string \| undefined` | `"Copy"` | no | Meaningful action name at rest; name what is being copied. | Label |
| `onCopied` | `(() => void) \| undefined` | Omitted; no declared default | no | Called after the latest operation succeeds for the current text, never after failure or a stale result. | Direct API |
| `onCopy` | `undefined \| CopyOperation` | Omitted; no declared default | no | Custom operation that performs the copy and returns void or Promise<void>; mutually exclusive with text. A thrown or rejected operation reports failure. Callback identity changes do not invalidate content; use text when value identity matters. | Direct API |
| `onError` | `((error: unknown) => void) \| undefined` | Omitted; no declared default | no | Called only when the clipboard operation rejects or throws. Only a current operation can report failure. Notification-handler exceptions are distinct from clipboard failures. | Direct API |
| `size` | `"small"`, `"medium"`, `"large"` | Omitted; no declared default | no | small, medium and large map to fixed single-line renderer sizes. The renderer Button content size is not exposed by CopyButton. | Size |
| `stopPropagation` | `false`, `true` | `false` | no | Prevents a copy control inside another interactive surface from activating its parent. | Stop propagation |
| `text` | `string \| undefined` | Omitted; no declared default | no | Exact text to copy. Supply text or onCopy, never both. Changing text clears completed feedback and invalidates unresolved results for the previous text. | Direct API |
| `variant` | `"text"`, `"outlined"`, `"contained"` | Omitted; no declared default | no |  | Variant (when control is button) |

### CopyIconButton

Import: `import { CopyIconButton } from '@socra/ui-web-action';`

| Property | Type or permitted values | Default / omission | Required | Meaning and behavior | Inspector |
| --- | --- | --- | --- | --- | --- |
| `disabled` | `false`, `true` | Omitted; no declared default | no | Prevents activation and removes the control from ordinary keyboard navigation. | Disabled |
| `glassy` | `false`, `true` | Omitted; no declared default | no | Standardized frosted action treatment. | Glassy (when control is icon) |
| `label` | `string \| undefined` | `"Copy"` | no | Resting accessible name and tooltip. Success uses Copied and failure uses Copy failed; these feedback labels are not configurable. | Label |
| `onCopied` | `(() => void) \| undefined` | Omitted; no declared default | no | Called after the latest operation succeeds for the current text, never after failure or a stale result. | Direct API |
| `onCopy` | `undefined \| CopyOperation` | Omitted; no declared default | no | Custom operation that performs the copy and returns void or Promise<void>; mutually exclusive with text. A thrown or rejected operation reports failure. Callback identity changes do not invalidate content; use text when value identity matters. | Direct API |
| `onError` | `((error: unknown) => void) \| undefined` | Omitted; no declared default | no | Called only when the clipboard operation rejects or throws. Only a current operation can report failure. Notification-handler exceptions are distinct from clipboard failures. | Direct API |
| `size` | `"small"`, `"medium"`, `"large"` | Omitted; no declared default | no |  | Size |
| `stopPropagation` | `false`, `true` | `false` | no | Prevents a copy control inside another interactive surface from activating its parent. | Stop propagation |
| `text` | `string \| undefined` | Omitted; no declared default | no | Exact text to copy. Supply text or onCopy, never both. Changing text clears completed feedback and invalidates unresolved results for the previous text. | Direct API |
| `tooltipPlacement` | `"bottom"`, `"left"`, `"right"`, `"top"` | `'bottom'` | no | Where the hint sits relative to the button. | Tooltip placement (when control is icon) |

### useCopyFeedback

Import: `import { useCopyFeedback } from '@socra/ui-web-hooks';`

Call as a React hook with optional { onError?: (error: unknown) => void, onCopied?: () => void }. Returns { copied: boolean, copy: (text: string) => void }. copy starts clipboard work and returns void; copied reports the shared success beat. The host supplies accessible feedback. Text is received only when copy(text) runs; the hook cannot observe a changing text prop.

| Parameter | Type or permitted values | Default / omission | Required | Meaning and behavior | Inspector |
| --- | --- | --- | --- | --- | --- |
| `Argument 1` | `CopyFeedbackOptions` | `{}` | no |  | Direct API |

Returns `CopyFeedbackState`.

### Inspector mappings

| Setting | Initial preview value | Consumed code |
| --- | --- | --- |
| Control | `button` | composition: Choose one public CopyButton or CopyIconButton from @socra/ui-web-action. |
| Source | `text` | composition: Pass text, or onCopy that writes { value: text } as JSON; never pass both. |
| Text | `project_demo` | composition: The text source is passed directly; the custom operation serializes it as { value: text } before copying. |
| Label | `Copy` | CopyButton.label, CopyIconButton.label |
| Copied label (when control is button) | `Copied` | CopyButton.copiedLabel |
| Error label (when control is button) | `Copy failed` | CopyButton.errorLabel |
| Variant (when control is button) | `text` | CopyButton.variant |
| Size | `medium` | CopyButton.size, CopyIconButton.size |
| Full width (when control is button) | `false` | CopyButton.fullWidth |
| Glassy (when control is icon) | `false` | CopyIconButton.glassy |
| Tooltip placement (when control is icon) | `bottom` | CopyIconButton.tooltipPlacement |
| Disabled | `false` | CopyButton.disabled, CopyIconButton.disabled |
| Stop propagation | `false` | CopyButton.stopPropagation, CopyIconButton.stopPropagation |

## Example

```tsx
import { useState } from 'react';
import { CopyButton, CopyIconButton } from '@socra/ui-web-action';
import { useCopyFeedback } from '@socra/ui-web-hooks';
import { Button } from '@socra/web-ui/button';
import '@socra/web-ui/styles.css';

export function CopyProject({ value }: { value: string }) {
  return <>
    <CopyButton text={value} label="Copy project ID" />
    <CopyIconButton text={value} label="Copy project ID" />
    <CopyButton label="Copy project as JSON"
      text={JSON.stringify({ id: value })}
      />
    <p>{value}</p>
  </>;
}

export function CopyWithExistingAction() {
  const value = 'project_demo'; // This hook example has a fixed source.
  const [error, setError] = useState(false);
  const { copied, copy } = useCopyFeedback({
    onCopied: () => setError(false),
    onError: () => setError(true),
  });
  return <>
    <Button onClick={() => { setError(false); copy(value); }}>Copy value</Button>
    <p role="status">{error ? 'Copy failed. Select the value below and copy it manually.' : copied ? 'Copied' : ''}</p>
    <p>{value}</p>
  </>;
}
```
