Actions

Copy Button

A clipboard action with transient success feedback and icon morphing.

⠋

Install

npx shadcn@latest add https://4af.selfsimilar.dev/r/copy-button.json

Usage

import { CopyButton } from "@/components/ui/copy-button";

<CopyButton value="Text to copy" size="md">Copy</CopyButton>

API

Main export: CopyButton

An Action Button that copies text to the clipboard, with visual and screen-reader confirmation.

size accepts xs, sm, md, or lg. With a label these are the standard button sizes; without one they are the matching square icon-xs, icon-sm, icon, or icon-lg sizes. An icon-only button defaults to icon. With a label, the icon gap matches Button (8, 10, or 12px).

Resolve values on press

value takes a function as well as a string:

value: string | (() => string | Promise<string>)

Use the function form when the text is assembled from a selection, read from the DOM, or fetched. It runs on press, so nothing is computed or requested on every render.

<CopyButton value={() => selected.map((row) => row.url).join("\n")} />
<CopyButton value={async () => (await fetchDocument(id)).body} />

The loader appears if resolving the value and writing it to the clipboard takes longer than pendingDelay (50ms by default). A function that throws or rejects shows the error state and writes nothing, so onCopy only reports text that was copied.

icon replaces the resting glyph, for example to tell apart two copy buttons that copy different things. The success check is unchanged.

<CopyButton icon={<Link />} value={() => rows.map((row) => row.url).join("\n")} />

Copying from somewhere else

writeToClipboard(text) is exported for clipboard writes outside the button, such as a menu row. navigator.clipboard only exists in secure contexts, so on plain HTTP it falls back to execCommand. It rejects on failure instead of returning false.

await writeToClipboard(item.url);