Add this component

npx zentauri-ui add copy-button

Accessibility notes

Keyboard accessible by default with semantic markup, visible focus treatment, and tokenized states. Add descriptive labels for icon-only or decorative usage.

Dependency notes

Static imports do not pull framer-motion. Use the animated entry only when you want motion and have framer-motion installed.

Actions

Copy button for clipboard actions

Copy button copies any string to the clipboard and transitions from an idle clipboard icon to a copied checkmark, then resets after a configurable timeout. It ships icon-only and labeled layouts, an optional animated icon swap, an onCopy callback, and the full Zentauri appearance and size system.

npm install @zentauri-ui/zentauri-components

Examples

Copy button copies any string to the clipboard and transitions through idle and copied states. Pair it with code blocks, tokens, share links, and CLI snippets.


Nothing copied yet.



Copy Button variants playground

Pick an appearance, size, and icon-only mode to preview the copy button live. Toggle Show output / Show code and the snippet updates to match the selected variant.


All appearances

Every shipped appearance token at a glance. Click any swatch to load it into the playground above.


Motion: ANIMATED ICON SWAP

Copy Button API

Generated from the package prop types and variant definitions.

CopyButton

CopyButtonProps

Variants

PropTypeDefault
appearance
amberaquabluecrimsoncyandefaultdestructiveelectricemeraldforestghostglass+36
default
iconOnlyRender only the icon (default). Pass `false` to show the label text alongside the icon.
falsetrue
none
size
lgmdsm
md

State

PropTypeDefault
value*Text written to the clipboard when the button is pressed.stringnone

Behavior

PropTypeDefault
copiedIconReactNodenone
copiedLabelLabel shown (and used for `aria-label`) after a successful copy.string | undefinednone
copyIconReactNodenone
labelLabel shown (and used for `aria-label`) in the idle state.string | undefinednone
onCopyCalled with `value` after the clipboard write succeeds.((value: string) => void) | undefinednone
timeoutMilliseconds the copied state stays active before resetting. `0` keeps it until re-copied.number | undefinednone
Inherited HTML props
PropTypeDefault
childrenReactNodenone
classNamestring | undefinednone
idstring | undefinednone
onClickMouseEventHandler<HTMLButtonElement> | undefinednone
styleCSSProperties | undefinednone
titlestring | undefinednone

CopyButtonVariant

CopyButtonVariantProps

Behavior

PropTypeDefault
appearance'default' | 'outline' | 'ghost' | 'blue' | 'cyan' | 'green' | 'lime' | 'mint' | 'ocean' | 'sapphire' | 'lavender' | 'ruby' | 'red' | 'slate' | 'zinc' | 'stone' | 'royal' | 'electric' | 'forest' | 'sunset' | 'magenta' | 'crimson' | 'aqua' | 'plum' | 'sky' | 'rose' | 'purple' | 'pink' | 'orange' | 'yellow' | 'teal' | 'indigo' | 'emerald' | 'gradient-blue' | 'gradient-green' | 'gradient-red' | 'gradient-yellow' | 'gradient-purple' | 'gradient-teal' | 'gradient-indigo' | 'gradient-pink' | 'gradient-orange' | 'gray' | 'violet' | 'glass' | 'secondary' | 'destructive' | 'amber' | null | undefinednone
iconOnlyboolean | null | undefinednone
size'md' | 'sm' | 'lg' | null | undefinednone

CopyButtonAnimated

CopyButtonAnimatedProps

animated

State

PropTypeDefault
value*Text written to the clipboard when the button is pressed.stringnone

Behavior

PropTypeDefault
animationCopyButtonAnimation | undefinednone
appearance'default' | 'outline' | 'ghost' | 'blue' | 'cyan' | 'green' | 'lime' | 'mint' | 'ocean' | 'sapphire' | 'lavender' | 'ruby' | 'red' | 'slate' | 'zinc' | 'stone' | 'royal' | 'electric' | 'forest' | 'sunset' | 'magenta' | 'crimson' | 'aqua' | 'plum' | 'sky' | 'rose' | 'purple' | 'pink' | 'orange' | 'yellow' | 'teal' | 'indigo' | 'emerald' | 'gradient-blue' | 'gradient-green' | 'gradient-red' | 'gradient-yellow' | 'gradient-purple' | 'gradient-teal' | 'gradient-indigo' | 'gradient-pink' | 'gradient-orange' | 'gray' | 'violet' | 'glass' | 'secondary' | 'destructive' | 'amber' | null | undefinednone
copiedIconReactNodenone
copiedLabelLabel shown (and used for `aria-label`) after a successful copy.string | undefinednone
copyIconReactNodenone
iconOnlyRender only the icon (default). Pass `false` to show the label text alongside the icon.boolean | undefinednone
labelLabel shown (and used for `aria-label`) in the idle state.string | undefinednone
onCopyCalled with `value` after the clipboard write succeeds.((value: string) => void) | undefinednone
size'md' | 'sm' | 'lg' | null | undefinednone
timeoutMilliseconds the copied state stays active before resetting. `0` keeps it until re-copied.number | undefinednone
Inherited HTML props
PropTypeDefault
childrenReactNodenone
classNamestring | undefinednone
idstring | undefinednone
onClickMouseEventHandler<HTMLButtonElement> | undefinednone
styleCSSProperties | undefinednone
titlestring | undefinednone
CSS variable overrides

Copy button CSS variables

Override these copy-button variables on :root, a theme selector, or a component wrapper. Structural variables drive the radius, focus ring, and ring offset; each appearance sets background, foreground, border, shadow, and hover tokens.

375 variables

Pattern: --zui-<component>-<slot?>-<variant?>-<property>-<state?>-dark?

:root {
  --zui-copy-button-radius: 0.75rem;
  --zui-copy-button-ring-offset: #f8fafc;
  --zui-copy-button-focus-ring: #475569;
  --zui-copy-button-default-bg: #0f172a;
  --zui-copy-button-default-fg: #f8fafc;
  --zui-copy-button-default-shadow: 0 1px 2px #0f172a14;
  --zui-copy-button-default-bg-hover: #000000;
  --zui-copy-button-secondary-bg: #e2e8f0;
  --zui-copy-button-secondary-fg: #0f172a;
  --zui-copy-button-secondary-bg-hover: #cbd5e1;
  --zui-copy-button-destructive-bg: #f43f5e;
  --zui-copy-button-destructive-fg: #ffffff;
  --zui-copy-button-destructive-bg-hover: #f43f5e;
  --zui-copy-button-outline-border: #0000001a;
  --zui-copy-button-outline-bg: #0000000d;
  --zui-copy-button-outline-fg: #0f172a;
  --zui-copy-button-outline-bg-hover: #0000001a;
  --zui-copy-button-ghost-fg: #334155;
  --zui-copy-button-ghost-bg-hover: #0000000d;
  --zui-copy-button-glass-border: #00000026;
  --zui-copy-button-glass-bg: #0000001a;
  --zui-copy-button-glass-fg: #0f172a;
  --zui-copy-button-glass-bg-hover: #00000026;
  --zui-copy-button-emerald-bg: #10b981;
  --zui-copy-button-emerald-fg: #ffffff;
  --zui-copy-button-emerald-bg-hover: #10b981;
  --zui-copy-button-indigo-bg: #3730a3;
  --zui-copy-button-indigo-fg: #ffffff;
  --zui-copy-button-indigo-bg-hover: #3730a3;
  --zui-copy-button-purple-bg: #6b21a8;
  --zui-copy-button-purple-fg: #ffffff;
  --zui-copy-button-purple-bg-hover: #6b21a8;
  --zui-copy-button-pink-bg: #9d174d;
  --zui-copy-button-pink-fg: #ffffff;
  --zui-copy-button-pink-bg-hover: #9d174d;
  --zui-copy-button-rose-bg: #9f1239;
  --zui-copy-button-rose-fg: #ffffff;
  --zui-copy-button-rose-bg-hover: #9f1239;
  --zui-copy-button-sky-bg: #0ea5e9;
  --zui-copy-button-sky-fg: #ffffff;
  --zui-copy-button-sky-bg-hover: #0ea5e9;
  --zui-copy-button-teal-bg: #14b8a6;
  --zui-copy-button-teal-fg: #ffffff;
  --zui-copy-button-teal-bg-hover: #14b8a6;
  --zui-copy-button-yellow-bg: #eab308;
  --zui-copy-button-yellow-fg: #ffffff;
  --zui-copy-button-yellow-bg-hover: #eab308;
  --zui-copy-button-orange-bg: #f97316;
  --zui-copy-button-orange-fg: #ffffff;
  --zui-copy-button-orange-bg-hover: #f97316;
  --zui-copy-button-gray-bg: #6b7280;
  --zui-copy-button-gray-fg: #ffffff;
  --zui-copy-button-gray-bg-hover: #6b7280;
  --zui-copy-button-amber-bg: #f59e0b;
  --zui-copy-button-amber-fg: #ffffff;
  --zui-copy-button-amber-bg-hover: #f59e0b;
  --zui-copy-button-violet-bg: #5b21b6;
  --zui-copy-button-violet-fg: #ffffff;
  --zui-copy-button-violet-bg-hover: #5b21b6;
  --zui-copy-button-gradient-blue-from: #1e40af;
  --zui-copy-button-gradient-blue-to: #6b21a8;
  --zui-copy-button-gradient-blue-fg: #ffffff;
  --zui-copy-button-gradient-blue-from-hover: #1e40af;
  --zui-copy-button-gradient-blue-to-hover: #6b21a8;
  --zui-copy-button-gradient-green-from: #166534;
  --zui-copy-button-gradient-green-to: #3f6212;
  --zui-copy-button-gradient-green-fg: #ffffff;
  --zui-copy-button-gradient-green-from-hover: #166534;
  --zui-copy-button-gradient-green-to-hover: #3f6212;
  --zui-copy-button-gradient-red-from: #991b1b;
  --zui-copy-button-gradient-red-to: #9d174d;
  --zui-copy-button-gradient-red-fg: #ffffff;
  --zui-copy-button-gradient-red-from-hover: #991b1b;
  --zui-copy-button-gradient-red-to-hover: #9d174d;
  --zui-copy-button-gradient-yellow-from: #854d0e;
  --zui-copy-button-gradient-yellow-to: #9a3412;
  --zui-copy-button-gradient-yellow-fg: #ffffff;
  --zui-copy-button-gradient-yellow-from-hover: #854d0e;
  --zui-copy-button-gradient-yellow-to-hover: #9a3412;
  --zui-copy-button-gradient-purple-from: #6b21a8;
  --zui-copy-button-gradient-purple-to: #9d174d;
  --zui-copy-button-gradient-purple-fg: #ffffff;
  --zui-copy-button-gradient-purple-from-hover: #6b21a8;
  --zui-copy-button-gradient-purple-to-hover: #9d174d;
  --zui-copy-button-gradient-teal-from: #115e59;
  --zui-copy-button-gradient-teal-to: #155e75;
  --zui-copy-button-gradient-teal-fg: #ffffff;
  --zui-copy-button-gradient-teal-from-hover: #115e59;
  --zui-copy-button-gradient-teal-to-hover: #155e75;
  --zui-copy-button-gradient-indigo-from: #3730a3;
  --zui-copy-button-gradient-indigo-to: #6b21a8;
  --zui-copy-button-gradient-indigo-fg: #ffffff;
  --zui-copy-button-gradient-indigo-from-hover: #3730a3;
  --zui-copy-button-gradient-indigo-to-hover: #6b21a8;
  --zui-copy-button-gradient-pink-from: #9d174d;
  --zui-copy-button-gradient-pink-to: #9f1239;
  --zui-copy-button-gradient-pink-fg: #ffffff;
  --zui-copy-button-gradient-pink-from-hover: #9d174d;
  --zui-copy-button-gradient-pink-to-hover: #9f1239;
  --zui-copy-button-gradient-orange-from: #9a3412;
  --zui-copy-button-gradient-orange-to: #991b1b;
  --zui-copy-button-gradient-orange-fg: #ffffff;
  --zui-copy-button-gradient-orange-from-hover: #9a3412;
  --zui-copy-button-gradient-orange-to-hover: #991b1b;
  --zui-copy-button-blue-border: #2563eb;
  --zui-copy-button-blue-bg: #2563eb;
  --zui-copy-button-blue-fg: #ffffff;
  --zui-copy-button-blue-bg-hover: #2563eb;
  --zui-copy-button-cyan-border: #0891b2;
  --zui-copy-button-cyan-bg: #0891b2;
  --zui-copy-button-cyan-fg: #083344;
  --zui-copy-button-cyan-bg-hover: #0891b2;
  --zui-copy-button-green-border: #16a34a;
  --zui-copy-button-green-bg: #16a34a;
  --zui-copy-button-green-fg: #ffffff;
  --zui-copy-button-green-bg-hover: #16a34a;
  --zui-copy-button-lime-border: #65a30d;
  --zui-copy-button-lime-bg: #65a30d;
  --zui-copy-button-lime-fg: #1a2e05;
  --zui-copy-button-lime-bg-hover: #65a30d;
  --zui-copy-button-mint-border: #10b981;
  --zui-copy-button-mint-bg: #10b981;
  --zui-copy-button-mint-fg: #064e3b;
  --zui-copy-button-mint-bg-hover: #10b981;
  --zui-copy-button-ocean-border: #0284c7;
  --zui-copy-button-ocean-bg: #0284c7;
  --zui-copy-button-ocean-fg: #082f49;
  --zui-copy-button-ocean-bg-hover: #0284c7;
  --zui-copy-button-sapphire-border: #1d4ed8;
  --zui-copy-button-sapphire-bg: #1d4ed8;
  --zui-copy-button-sapphire-fg: #ffffff;
  --zui-copy-button-sapphire-bg-hover: #1d4ed8;
  --zui-copy-button-lavender-border: #8b5cf6;
  --zui-copy-button-lavender-bg: #8b5cf6;
  --zui-copy-button-lavender-fg: #ffffff;
  --zui-copy-button-lavender-bg-hover: #8b5cf6;
  --zui-copy-button-ruby-border: #be123c;
  --zui-copy-button-ruby-bg: #be123c;
  --zui-copy-button-ruby-fg: #ffffff;
  --zui-copy-button-ruby-bg-hover: #be123c;
  --zui-copy-button-red-border: #dc2626;
  --zui-copy-button-red-bg: #dc2626;
  --zui-copy-button-red-fg: #ffffff;
  --zui-copy-button-red-bg-hover: #dc2626;
  --zui-copy-button-slate-border: #475569;
  --zui-copy-button-slate-bg: #475569;
  --zui-copy-button-slate-fg: #ffffff;
  --zui-copy-button-slate-bg-hover: #475569;
  --zui-copy-button-zinc-border: #52525b;
  --zui-copy-button-zinc-bg: #52525b;
  --zui-copy-button-zinc-fg: #ffffff;
  --zui-copy-button-zinc-bg-hover: #52525b;
  --zui-copy-button-stone-border: #57534e;
  --zui-copy-button-stone-bg: #57534e;
  --zui-copy-button-stone-fg: #ffffff;
  --zui-copy-button-stone-bg-hover: #57534e;
  --zui-copy-button-royal-border: #4338ca;
  --zui-copy-button-royal-bg: #4338ca;
  --zui-copy-button-royal-fg: #ffffff;
  --zui-copy-button-royal-bg-hover: #4338ca;
  --zui-copy-button-electric-border: #0ea5e9;
  --zui-copy-button-electric-bg: #0ea5e9;
  --zui-copy-button-electric-fg: #ffffff;
  --zui-copy-button-electric-bg-hover: #0ea5e9;
  --zui-copy-button-forest-border: #166534;
  --zui-copy-button-forest-bg: #166534;
  --zui-copy-button-forest-fg: #ffffff;
  --zui-copy-button-forest-bg-hover: #166534;
  --zui-copy-button-sunset-border: #ea580c;
  --zui-copy-button-sunset-bg: #ea580c;
  --zui-copy-button-sunset-fg: #ffffff;
  --zui-copy-button-sunset-bg-hover: #ea580c;
  --zui-copy-button-magenta-border: #c026d3;
  --zui-copy-button-magenta-bg: #c026d3;
  --zui-copy-button-magenta-fg: #ffffff;
  --zui-copy-button-magenta-bg-hover: #c026d3;
  --zui-copy-button-crimson-border: #b91c1c;
  --zui-copy-button-crimson-bg: #b91c1c;
  --zui-copy-button-crimson-fg: #ffffff;
  --zui-copy-button-crimson-bg-hover: #b91c1c;
  --zui-copy-button-aqua-border: #0f766e;
  --zui-copy-button-aqua-bg: #0f766e;
  --zui-copy-button-aqua-fg: #ffffff;
  --zui-copy-button-aqua-bg-hover: #0f766e;
  --zui-copy-button-plum-border: #7e22ce;
  --zui-copy-button-plum-bg: #7e22ce;
  --zui-copy-button-plum-fg: #ffffff;
  --zui-copy-button-plum-bg-hover: #7e22ce;
}

/* Dark theme variables follow the same names with -dark appended. */
.dark {
  --zui-copy-button-ring-offset-dark: #020617;
  --zui-copy-button-focus-ring-dark: #cbd5e1;
}

What it does

Copy button wraps the clipboard write in a single accessible control with built-in idle and copied states. It uses the library's useClipboard hook, so the copied flag is managed for you and resets on the timeout you choose.

The static entry swaps the icon instantly, while the animated entry transitions the clipboard and checkmark icons through a spring scale and rotation.

Composition and API

Import CopyButton from the copy-button entrypoint, or CopyButtonAnimated from copy-button/animated for the motion variant. Set value with the text to copy, and tune the control with timeout, iconOnly, label, copiedLabel, copyIcon, copiedIcon, onCopy, appearance, and size.

Common use cases

Reach for Copy button anywhere a user benefits from one-click copying: install commands and code samples in docs, API keys and tokens in dashboards, share links, invoice or order IDs in tables, and onboarding steps that surface copyable values.

Accessibility

The control renders a real button with an aria-label that reflects the current state, and a visually hidden live region announces the copied label to assistive technology. Provide your own aria-label when the surrounding context already names the value.

Next.js integration notes

Copy button is a client component because it reads navigator.clipboard in the browser. Render it inside client components or interactive islands; the clipboard write is a no-op during SSR and resolves only once the component is hydrated.

FAQ

How long does the copied state last?

It resets after the timeout prop in milliseconds (2000 by default). Pass timeout={0} to keep the copied state until the button is pressed again.

Can I show a text label instead of just an icon?

Yes. Pass iconOnly={false} to render the label and copiedLabel text next to the icon. The icon-only layout still exposes an accessible label.

How do I react to a successful copy?

Pass an onCopy callback. It receives the copied value after the clipboard write resolves, so you can toast, log analytics, or update local state.