Copy button

Copy a value with immediate confirmation.

import { CopyButton } from "@sagui/ui";
Markdown
Loading demo

When to use

  • Next to API keys, install commands, links, or code snippets.
  • Icon-only copy controls in dense rows, with an accessible label.
  • Any copy action that should confirm Copied or Could not copy in place.

When not to use

  • Use split-button when copying has alternatives, such as Copy link and Copy as Markdown.
  • Use code-block for full code samples, which include their own copy control.

Installation

Install the package and import the styles once. The installation guide covers the Tailwind setup.

$ npm install @sagui/ui

Usage

example.tsx
import { CopyButton } from "@sagui/ui";

export function ApiKeyRow({ apiKey }: { apiKey: string }) {
  return (
    <div className="row">
      <code>{apiKey}</code>
      <CopyButton value={apiKey} label="Copy key" iconOnly />
    </div>
  );
}

Examples

Plain

The plain variant has no border, for use inside a code block or a row of actions.

Loading demo

Icon only

Loading demo

Inside a code block

Loading demo

API reference

CopyButton

Copies a string to the clipboard and morphs its icon and label to Copied or Failed.

Prop Type Default Description
value (required) string – The text to copy.
label string "Copy" Idle label and accessible name.
iconOnly boolean false Hides the text and shows only the icon.
variant "outline" | "plain" "outline" Bordered or borderless.
disabled boolean – Disables the button.
onCopied () => void – Called after a successful copy.
className string – Extra class on the button.

Keyboard interactions

Keys Action
EnterorSpace Copies the value.

Accessibility

  • Native button named by label, including when iconOnly.
  • A polite role="status" region announces ": Copied" or ": Could not copy".
  • The label cell reserves the widest of its three states, so the layout never shifts.

Motion

  • Icons trade places through a blur on a slow, nearly critically damped spring, and the check draws its stroke in.
  • Only the changed letters of the label move; the same motion plays in reverse on reset.
  • Reduced motion swaps icon and text with an instant fade and skips the stroke draw.

Responsive behavior

  • The label reserves the width of its widest state, so the button never shifts its neighbours when it changes.
  • It sizes to max-content with max-width 100%, so it stays compact in narrow rows.

Performance

  • Only changed letters animate; the icon swap and stroke draw are a single short spring per copy.
  • Clipboard access is async and needs a secure context; failures show the Failed state rather than throwing.

Notes

  • Use next to any copyable value: API keys, install commands, links, code. For a copy action that also has alternatives use split-button.
  • Named export only; there is no default export.
  • Clipboard state and reset timing come from lib/use-copy-feedback, so copy that file along with the component.

Also in buttons