Documentation

Copy Button

A button that copies text to the clipboard, then swaps its icon for a check mark that draws itself.

Preview

Usage

import { CopyButton } from '@vitnode/core/components/ui/copy-button'
<CopyButton content="pnpm create vitnode-app@canary" />

The check stays for delay (2000ms). Each new copy restarts the timer.

With a label

Children show next to the icon and become the accessible name.

<CopyButton content={inviteLink} variant="secondary">
  Copy invite link
</CopyButton>

Icon only

Without children it's labelled "Copy" / "Copied". With several on a screen, give each its own aria-label.

<CopyButton
  aria-label="Copy install command"
  content={command}
  size="icon-sm"
  variant="ghost"
/>

Controlled

Use copied and onCopiedChange when the page should react.

const [copied, setCopied] = React.useState(false)

<CopyButton content={inviteLink} copied={copied} onCopiedChange={setCopied} />

Async content

Text that has to be fetched first? Pass a function instead of a string. It runs on click, the button is aria-busy while it waits, and extra clicks are ignored until it settles.

<CopyButton
  content={async () => {
    const res = await fetch('/docs/dev.md')
    if (!res.ok) throw new Error(`Failed to load ${res.url}`)

    return await res.text()
  }}
  size="sm"
  variant="secondary"
>
  Copy Markdown
</CopyButton>

Safari only lets a page write to the clipboard during the click itself, so the pending text goes in as a ClipboardItem promise. Browsers without ClipboardItem wait for the text, then copy it. A rejected promise shows the same error toast as a blocked clipboard.

Accessibility

  • A polite live region announces "Copied".
  • With reduced motion, icons just fade and the check appears fully drawn.
  • A blocked clipboard (say, on plain HTTP) shows an error toast.
  • event.preventDefault() in your own onClick skips the copy.

Props

Prop

Type

API Reference

Base UI - Button