Copy Button

A button that copies text to the clipboard, its icon swapping for a check.

"use client"

import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "cn"
import {
  CopyButton,
  CopyButtonIcon,
} from "@animate-ui/react/primitives/buttons/copy"
import { Copy01Icon, Tick02Icon } from "@hugeicons/core-free-icons"
import { HugeiconsIcon } from "@hugeicons/react"

const copyButtonVariants = cva(
  "focus-visible:border-ring focus-visible:ring-ring/50 inline-flex shrink-0 items-center justify-center rounded-lg transition-[color,background-color,border-color,box-shadow] outline-none focus-visible:ring-3 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        accent: "bg-accent text-accent-foreground hover:bg-accent/90",
        destructive:
          "bg-destructive hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white",
        outline:
          "bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border",
        secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
        ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50",
        link: "text-primary underline-offset-4 hover:underline",
      },
      size: {
        default: "size-9",
        xs: "size-7 [&_svg:not([class*='size-'])]:size-3.5",
        sm: "size-8",
        lg: "size-10",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
)

type CopyButtonVariants = VariantProps<typeof copyButtonVariants>

export default function CopyButtonDemo({
  variant = "default",
  size = "default",
}: CopyButtonVariants) {
  return (
    <CopyButton
      content="Hello world!"
      aria-label="Copy"
      className={cn(copyButtonVariants({ variant, size }))}
    >
      <CopyButtonIcon
        icon={<HugeiconsIcon icon={Copy01Icon} strokeWidth={2} />}
        copiedIcon={<HugeiconsIcon icon={Tick02Icon} strokeWidth={2} />}
      />
    </CopyButton>
  )
}

Installation

npm install @animate-ui/react motion
pnpm add @animate-ui/react motion
yarn add @animate-ui/react motion
bun add @animate-ui/react motion

Usage

import {
  CopyButton,
  CopyButtonIcon,
} from '@animate-ui/react/primitives/buttons/copy';

export function CopyCommand() {
  return (
    <CopyButton content="npx animate-ui init" aria-label="Copy">
      <CopyButtonIcon icon={<CopyIcon />} copiedIcon={<CheckIcon />} />
    </CopyButton>
  );
}

A click writes content to the clipboard (navigator.clipboard.writeText). Once it's written, the button is copied (data-copied) for delay milliseconds, then goes back. Copying again restarts the delay. Read the state in your own parts with useCopyButton().

Animation

  • Once copied, the copy icon shrinks to half its size into a light blur (blur(2px)) as it fades out, while the check grows out of the same blur in its place, over 0.15s with an ease-out (cubic-bezier(0.23, 1, 0.32, 1)). It swaps back the same way after delay. Nothing animates on mount.
  • Pressing scales the button to tapScale (0.97), on Motion's spring for scale (stiffness: 550, damping: 30). It doesn't scale on hover, unless you set hoverScale.
  • <AnimateUIProvider tempo> paces the swap and the press spring (not delay).
  • Under reduced motion, the icons crossfade through their blur without scaling, and the button doesn't scale.

API Reference

CopyButton

PropTypeDefault
content
string-
copied?
boolean-
defaultCopied?
booleanfalse
onCopiedChange?
(copied: boolean, content?: string) => void-
delay?
number3000
hoverScale?
number1
tapScale?
number0.97
transition?
Transition{ type: 'spring', stiffness: 550, damping: 30, restSpeed: 10 }
asChild?
booleanfalse
...props?
HTMLMotionProps<'button'>-

CopyButtonIcon

PropTypeDefault
icon
React.ReactNode-
copiedIcon
React.ReactNode-
transition?
Transition{ duration: 0.15, ease: [0.23, 1, 0.32, 1] }
...props?
React.ComponentProps<'span'>-

Data attributes

PropTypeDefault
data-copied?
present-

Built by Skyleen. The source code is available on GitHub.

Last updated: 10/18/2026

On this page