# CopyableCode

> One command people copy and run, on a code surface with a copy button that confirms with a check.

Source: https://raft-design.botiverse.build/components/copyable-code. Generated from the current release; where it differs from what you remember, this page is right.

One line that people copy and run elsewhere: the command that connects a computer, installs the CLI or signs in. The command sits on a code surface with a copy button beside it; after a copy, the button shows a check for a moment.

```tsx
import { CopyableCode, CopyableCodeAction, CopyableCodeRoot } from "@botiverse/raft-ui";

const COMMAND = "curl https://api.raft.build/v1/setup --machine workstation-2";

export function CompactDetailCommandContent() {
  return (
    <CopyableCodeRoot size="sm">
      <CopyableCode truncate>{COMMAND}</CopyableCode>
      <CopyableCodeAction aria-label="Copy computer setup command" />
    </CopyableCodeRoot>
  );
}
```

## When to use

| Situation                          | Use                                         |
| ---------------------------------- | ------------------------------------------- |
| One command people copy and run    | CopyableCode                                |
| A name from code inside a sentence | [InlineCode](https://raft-design.botiverse.build/components/inline-code.md) |
| Several lines of code, or a log    | [CodeBlock](https://raft-design.botiverse.build/components/code-block.md)    |

## Anatomy

- `CopyableCodeRoot`: holds the command and the button, and handles the copy. Required.
- `CopyableCode`: the command. Its text is what gets copied. Required.
- `CopyableCodeAction`: the copy button, an icon [Button](https://raft-design.botiverse.build/components/button.md). Required.

## Variants

| Variant     | Use for                                                            |
| ----------- | ------------------------------------------------------------------ |
| `size="md"` | The main step of a dialog or a page, such as setting up a computer |
| `size="sm"` | A command among other details, such as in a panel or a list        |

## States

| State       | Look                                                                        |
| ----------- | --------------------------------------------------------------------------- |
| Copied      | The copy icon turns into a check, then back after a moment                  |
| Copy failed | Nothing changes in the component; `onError` runs so you can tell the person |

```tsx
import { useCallback, useState } from "react";
import { CopyableCode, CopyableCodeAction, CopyableCodeRoot } from "@botiverse/raft-ui";

const COMMAND = "raft login --token $RAFT_TOKEN";

export function CopyFailureCompositionContent() {
  const [copyFailed, setCopyFailed] = useState(false);

  const handleCopy = useCallback(() => {
    setCopyFailed(false);
  }, []);

  const handleCopyError = useCallback(() => {
    setCopyFailed(true);
  }, []);

  return (
    <div className="grid w-full gap-2">
      <CopyableCodeRoot size="sm" onCopy={handleCopy} onError={handleCopyError}>
        <CopyableCode truncate>{COMMAND}</CopyableCode>
        <CopyableCodeAction aria-label="Copy login command" />
      </CopyableCodeRoot>
      {copyFailed ? (
        <p role="status" className="text-xs text-foreground-muted">
          Copy failed — select the command and copy it manually.
        </p>
      ) : null}
    </div>
  );
}
```

```tsx
import { useCallback, useEffect, useState } from "react";
import {
  Checkbox,
  CopyableCode,
  CopyableCodeAction,
  CopyableCodeRoot,
  createToastManager,
  ToastProvider,
} from "@botiverse/raft-ui";

const COMMAND = "raft login --token $RAFT_TOKEN";

/**
 * The failure path, made clickable: with the switch on, the demo denies the
 * clipboard for the duration so the copy actually fails and `onError` runs.
 */
export function CopyErrorToastCompositionContent() {
  const [toastManager] = useState(() => createToastManager());
  const [copyDenied, setCopyDenied] = useState(false);

  useEffect(() => {
    if (!copyDenied) return;
    const clipboard = navigator.clipboard;
    const originalWrite = clipboard?.writeText?.bind(clipboard);
    const originalExecCommand = document.execCommand.bind(document);
    if (clipboard) {
      clipboard.writeText = () =>
        Promise.reject(new DOMException("Clipboard denied", "NotAllowedError"));
    }
    document.execCommand = () => false;
    return () => {
      if (clipboard && originalWrite) clipboard.writeText = originalWrite;
      document.execCommand = originalExecCommand;
    };
  }, [copyDenied]);

  const handleCopyError = useCallback(() => {
    toastManager.add({
      title: "Couldn't copy the command",
      description: "Copy the text manually, or check the browser's clipboard permission.",
      type: "error",
    });
  }, [toastManager]);

  return (
    <ToastProvider
      toastManager={toastManager}
      timeout={2600}
      limit={2}
      viewportPlacement="top-center"
    >
      <div className="grid w-full gap-3">
        <CopyableCodeRoot size="sm" onError={handleCopyError}>
          <CopyableCode truncate>{COMMAND}</CopyableCode>
          <CopyableCodeAction aria-label="Copy login command" />
        </CopyableCodeRoot>
        <label className="flex items-start gap-2 text-xs leading-5 text-foreground-muted">
          <Checkbox
            checked={copyDenied}
            onCheckedChange={setCopyDenied}
            aria-label="Simulate a denied clipboard"
          />
          Simulate a denied clipboard, then press copy — the action fails and the toast reports it
        </label>
      </div>
    </ToastProvider>
  );
}
```

## Behavior

- The button copies the text content of `CopyableCode`. `onCopy` runs only after the copy succeeded.
- Without `truncate`, a long command wraps; with it, the command stays on one line and scrolls sideways.

## Content

- Show the command exactly as people run it.
- When a copy fails, say so and say what to do: "Couldn't copy the command. Copy the text manually, or check the browser's clipboard permission."

## Accessibility

- `CopyableCodeAction` is labeled "Copy code" and "Copied code" by default, and announces the change (`aria-live="polite"`). Give it an `aria-label` that names what is copied: "Copy login command".
- Tell the person about a failed copy in words, through `onError` (a [Toast](https://raft-design.botiverse.build/components/toast.md) or a status line). The icon alone doesn't say it failed.
- The command is a `code` element, so screen readers read it as text.

## Rules

- Set the command in Space Mono, `text-mono-code`, on `layer-code` with a `line-hairline` border.
- Keep a command on one line: use `truncate`, don't let it wrap.
- Don't copy the `$ ` prompt. Keep the prompt out of `CopyableCode`, because everything in it is copied.

## API

### CopyableCode

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `truncate` | `boolean` |  |  |

Also takes the attributes of its element.

### CopyableCodeAction

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `buttonClassName` | `string` |  |  |
| `children` | `ReactNode \| ((state: { copied: boolean; }) => ReactNode)` |  |  |
| `loading` | `boolean` |  |  |
| `loadingLabel` | `string` |  |  |
| `size` | `"md" \| "lg" \| "xl" \| "icon-md" \| "icon-lg" \| DeprecatedButtonSize` | `"md"` | `md` (32px) and `icon-md` for rows, cards, banners, toolbars and panel headers; `lg` and `icon-lg` (the control height) for dialog footers, forms and beside a field; `xl` for a full-width call to action. The size follows the place: see Button › Size by place. |
| `variant` | `"primary" \| "accent" \| "warning" \| "danger" \| "default" \| "outline" \| "information" \| "muted" \| "success" \| "danger-secondary" \| "danger-outline" \| "ghost" \| "on-frame"` |  |  |
| `focusableWhenDisabled` | `boolean` | `false` | Whether the button should be focusable when disabled. |
| `nativeButton` | `boolean` | `true` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### CopyableCodeRoot

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `copied` | `boolean` |  |  |
| `onCopy` | `(value: string) => void \| Promise<void>` |  | Called with the copied text only after a write that actually landed. |
| `onError` | `(error: Error) => void` |  | Called when every copy strategy failed, so the consumer can tell the user. |
| `size` | `"sm" \| "md"` | `"md"` |  |

Also takes the attributes of its element.

## Changes

- 0.4.0 (Breaking Changes): **`CopyableCodeAction` label props removed.** `ariaLabel` and `copiedAriaLabel` are gone; use the standard `aria-label` prop instead. The default label still swaps between "Copy code" and "Copied code". ([b3786da](https://github.com/botiverse/rui/commit/b3786da))
- 0.1.0 (Breaking Changes): Flattened `CopyableCode`: the preset and `CopyableCode.Code` compound API are removed. Compose `CopyableCodeRoot`, `CopyableCode`, and `CopyableCodeAction`; `CopyableCodeCodeProps` is gone and data-slots were renamed.
