Menu

Site and documentation links

Search documentation

Type to filter pages and sections. Use the arrow keys to move and Enter to open.

    Copy Button

    A button that copies text to the clipboard and announces that it did.

    npm install @syncclouds/ui
    View code
    'use client'
    
    import { CopyButton } from '@syncclouds/ui'
    
    export function CopyButtonDemo() {
      return (
        <div className="flex items-center gap-3">
          <code className="font-mono text-sm">npm install @syncclouds/ui</code>
          <CopyButton value="npm install @syncclouds/ui">
            {({ copied }) => (copied ? 'Copied' : 'Copy')}
          </CopyButton>
        </div>
      )
    }

    Installation

    npm install @syncclouds/ui

    Usage

    import { CopyButton } from '@syncclouds/ui'
    <CopyButton value="npm install @syncclouds/ui">
      {({ copied }) => (copied ? 'Copied' : 'Copy')}
    </CopyButton>

    Anatomy

    CopyButton
    └── (live region, rendered for you)

    Icon only

    A button with no visible text needs an aria-label.

    View code
    'use client'
    
    import { CopyButton } from '@syncclouds/ui'
    
    const CopyIcon = () => (
      <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
        <rect x="9" y="9" width="11" height="11" rx="2" />
        <path d="M5 15V6a2 2 0 0 1 2-2h9" />
      </svg>
    )
    
    const CheckIcon = () => (
      <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
        <path d="M20 6 9 17l-5-5" />
      </svg>
    )
    
    /* With no visible text, the button needs an aria-label. */
    export function CopyButtonIcon() {
      return (
        <CopyButton value="https://ui.syncclouds.com" aria-label="Copy link">
          {({ copied }) => (copied ? <CheckIcon /> : <CopyIcon />)}
        </CopyButton>
      )
    }

    Custom announcement

    Say what was copied, so a screen reader user does not have to guess.

    View code
    'use client'
    
    import { CopyButton } from '@syncclouds/ui'
    
    export function CopyButtonAnnouncement() {
      return (
        <CopyButton value="INV-2026-0142" announcement="Invoice number copied">
          {({ copied }) => (copied ? 'Copied' : 'Copy invoice number')}
        </CopyButton>
      )
    }

    Without the theme

    View code
    'use client'
    
    import { CopyButton } from '@syncclouds/ui'
    
    export function CopyButtonUnstyled() {
      return (
        <CopyButton
          value="sk_test_4eC39HqLyjWDarjtT1zdp7dc"
          className="rounded-md bg-zinc-900 px-3 py-1.5 text-sm font-medium text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 data-[state=copied]:bg-emerald-700 dark:bg-zinc-100 dark:text-zinc-900 dark:data-[state=copied]:bg-emerald-300"
        >
          {({ copied }) => (copied ? 'Key copied' : 'Copy test key')}
        </CopyButton>
      )
    }

    Accessibility

    KeysWhat they do
    Enter, SpaceCopies the text.
    • An icon-only button needs an aria-label, such as "Copy command".
    • Where the async clipboard is not available, it falls back to a hidden text field and the copy command.

    API reference

    Every part also accepts the props of the element it renders, including className and ref.

    CopyButtonrenders <button>

    Copies text, then carries data-state="copied" for two seconds. A polite live region next to it announces the copy.

    PropTypeDefaultDescription
    valuestring | (() => string)noneRequired. The text to copy, or a function that returns it at the moment of the click.
    onCopied() => voidnoneCalled after the text is copied.
    announcementstring"Copied"What the live region says after a copy.
    childrenReactNode | ((state: { copied: boolean }) => ReactNode)noneThe label, or a function that swaps it while copied.
    Data attributeValues
    data-scope"copy-button"
    data-state"idle" | "copied"
    Built by Syncclouds. Syncclouds UI is in development.