Copy Button
A click-to-copy button with animated clipboard-to-checkmark feedback, tooltip, and multiple size variants.
Basic
A compact icon button that copies text to the clipboard with animated clipboard-to-checkmark feedback and an informative tooltip.
Installation
Install the component via the CLI in one command.
npx @work-rjkashyap/unified-ui add copy-buttonpnpm dlx @work-rjkashyap/unified-ui add copy-buttonnpx @work-rjkashyap/unified-ui add copy-buttonbunx @work-rjkashyap/unified-ui add copy-buttonIf you haven't initialized your project yet, run npx @work-rjkashyap/unified-ui init first. See the CLI docs for details.
Or install the full package
Use this approach if you prefer to install the entire design system as a dependency instead of copying individual components.
npm install @work-rjkashyap/unified-uipnpm add @work-rjkashyap/unified-uiyarn add @work-rjkashyap/unified-uibun add @work-rjkashyap/unified-uiAnatomy
import {
CopyButton,
InputGroup,
} from "@work-rjkashyap/unified-ui";Basic Usage
Pass the text prop with the string to copy to clipboard. The button shows a checkmark for 2000ms after a successful copy.
<CopyButton text="Hello, world!" />Sizes
| Size | Dimensions | Icon Size |
|---|---|---|
sm | 28×28px | 12px |
md | 32×32px | 14px |
lg | 36×36px | 16px |
Variants
| Variant | Background | Border | Use Case |
|---|---|---|---|
default | --background | Visible | Standalone copy buttons, API key fields |
ghost | Transparent | None | Inside code blocks, toolbars, inline cells |
Custom Tooltip
Set tooltip to customize the label shown on hover. After a successful copy, the tooltip automatically shows "Copied!".
Success Duration
Control how long the checkmark state is shown before reverting to the copy icon. Defaults to 2000ms.
{/* Short flash — 1 second */}
<CopyButton text="quick copy" successDuration={1000} />
{/* Long confirmation — 4 seconds */}
<CopyButton text="important key" successDuration={4000} tooltip="Copy key" />Callbacks
Use onCopy and onCopyError for side effects after copy success or failure.
<CopyButton
text={apiKey}
tooltip="Copy API key"
onCopy={(text) => {
analytics.track("api_key_copied");
console.log("Copied:", text);
}}
onCopyError={(error) => {
toast.error("Failed to copy. Please copy manually.");
console.error(error);
}}
/>Inside a Code Block
The most common use case — a ghost copy button overlaid in the top-right corner of a code block.
npm install @work-rjkashyap/unified-uiIn an API Key Field
A common pattern for displaying masked API keys with a copy action.
<div className="flex items-center gap-2 p-2 rounded-md border border-border bg-muted/30">
<code className="flex-1 text-xs font-mono text-muted-foreground truncate">
sk_live_••••••••••••••••••••••4242
</code>
<CopyButton
text={actualApiKey}
tooltip="Copy API key"
size="sm"
onCopy={() => toast.success("API key copied to clipboard.")}
/>
</div>In a Table Cell
function ApiKeyRow({ row }: { row: ApiKey }) {
return (
<tr>
<td className="font-mono text-sm">{row.name}</td>
<td className="text-muted-foreground text-sm">
{row.key.slice(0, 8)}••••••••
</td>
<td>
<CopyButton
text={row.key}
tooltip="Copy full key"
variant="ghost"
size="sm"
/>
</td>
</tr>
);
}With InputGroup
A clean combination with InputGroup for shareable link fields.
import { InputGroup, CopyButton } from "@work-rjkashyap/unified-ui";
import { Link } from "lucide-react";
<InputGroup
prefix={<Link className="size-4" />}
inputProps={{
value: shareUrl,
readOnly: true,
className: "text-sm",
}}
suffix={
<CopyButton
text={shareUrl}
tooltip="Copy link"
variant="ghost"
size="sm"
/>
}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
text | string | — | Required. The text string to copy to the clipboard. |
variant | "default" | "ghost" | "default" | Visual style — bordered or transparent. |
size | "sm" | "md" | "lg" | "md" | Button dimensions. |
tooltip | string | "Copy" | Tooltip text shown on hover. Automatically switches to "Copied!" after copy. |
successDuration | number | 2000 | How long (in ms) the checkmark state is shown before reverting. |
onCopy | (text: string) => void | — | Callback fired after a successful copy with the copied text. |
onCopyError | (error: Error) => void | — | Callback fired if the clipboard write fails. |
className | string | — | Additional CSS classes on the root wrapper. |
Standard <button> HTML attributes are not forwarded — the component manages its own click handler via the text prop.
Motion
- Icon transition —
AnimatePresence mode="wait"swaps the copy icon for the checkmark with thefadeInFastpreset (opacity 0 → 1, 120ms) in both directions. - Checkmark — Appears with a
popscale animation (scale: 0.85 → 1, springstiffness: 400, damping: 20) to provide satisfying tactile feedback. - Tooltip — Fades in/out with
fadeInFaston mouse enter/leave and focus/blur. - All animations respect
prefers-reduced-motionviauseReducedMotion().
Accessibility
- The button has
aria-labelthat updates dynamically:"Copy"by default,"Copied!"after a successful copy. - Focus ring is always visible via
focus-visiblestyles for keyboard users. - The copy icon and checkmark icon are both
aria-hidden="true". - The tooltip is rendered as
pointer-events-noneand is purely decorative — thearia-labelprovides the screen reader context. - The component uses the native Clipboard API (
navigator.clipboard.writeText) — failures are caught and surfaced viaonCopyError.
Design Tokens
| Token | Usage |
|---|---|
--background | Default variant background |
--border | Default variant border |
--accent | Hover background |
--foreground | Tooltip background |
--background | Tooltip text color |
--success | Checkmark icon color |
--radius-md | Button border radius |
--duration-fast | Hover/focus transition speed |