Unified UI

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-button
pnpm dlx @work-rjkashyap/unified-ui add copy-button
npx @work-rjkashyap/unified-ui add copy-button
bunx @work-rjkashyap/unified-ui add copy-button

If 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-ui
pnpm add @work-rjkashyap/unified-ui
yarn add @work-rjkashyap/unified-ui
bun add @work-rjkashyap/unified-ui

Anatomy

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

sm
md
lg
SizeDimensionsIcon Size
sm28×28px12px
md32×32px14px
lg36×36px16px

Variants

default
ghost
VariantBackgroundBorderUse Case
default--backgroundVisibleStandalone copy buttons, API key fields
ghostTransparentNoneInside 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.

bash
npm install @work-rjkashyap/unified-ui

In 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

PropTypeDefaultDescription
textstringRequired. The text string to copy to the clipboard.
variant"default" | "ghost""default"Visual style — bordered or transparent.
size"sm" | "md" | "lg""md"Button dimensions.
tooltipstring"Copy"Tooltip text shown on hover. Automatically switches to "Copied!" after copy.
successDurationnumber2000How long (in ms) the checkmark state is shown before reverting.
onCopy(text: string) => voidCallback fired after a successful copy with the copied text.
onCopyError(error: Error) => voidCallback fired if the clipboard write fails.
classNamestringAdditional 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 transitionAnimatePresence mode="wait" swaps the copy icon for the checkmark with the fadeInFast preset (opacity 0 → 1, 120ms) in both directions.
  • Checkmark — Appears with a pop scale animation (scale: 0.85 → 1, spring stiffness: 400, damping: 20) to provide satisfying tactile feedback.
  • Tooltip — Fades in/out with fadeInFast on mouse enter/leave and focus/blur.
  • All animations respect prefers-reduced-motion via useReducedMotion().

Accessibility

  • The button has aria-label that updates dynamically: "Copy" by default, "Copied!" after a successful copy.
  • Focus ring is always visible via focus-visible styles for keyboard users.
  • The copy icon and checkmark icon are both aria-hidden="true".
  • The tooltip is rendered as pointer-events-none and is purely decorative — the aria-label provides the screen reader context.
  • The component uses the native Clipboard API (navigator.clipboard.writeText) — failures are caught and surfaced via onCopyError.

Design Tokens

TokenUsage
--backgroundDefault variant background
--borderDefault variant border
--accentHover background
--foregroundTooltip background
--backgroundTooltip text color
--successCheckmark icon color
--radius-mdButton border radius
--duration-fastHover/focus transition speed

On this page