useCopyToClipboard

Previous

A React hook for copying text to the clipboard with fallback support and feedback state management.

About

The useCopyToClipboard hook provides a simple and reliable way to copy text to the clipboard with automatic fallback support and user feedback state management.

Features

  • Cross-browser Compatibility: Uses modern Clipboard API when available, falls back to document.execCommand for older browsers
  • User Feedback: Built-in isCopied state for showing copy confirmation
  • Customizable Delay: Configure how long the "copied" state persists
  • Safe Implementation: Handles errors gracefully and cleans up temporary elements
  • Zero Dependencies: No external dependencies required
  • TypeScript Support: Fully typed with excellent developer experience

Preview

Installation

pnpm dlx shadcn@latest add https://ui.nowts.app/r/use-copy-to-clipboard.json

Usage

Basic copy button

import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
import { Button } from "@/components/ui/button"
 
export function BasicCopyButton() {
  const { isCopied, copyToClipboard } = useCopyToClipboard()
 
  return (
    <Button
      onClick={() => copyToClipboard("Hello, World!")}
      variant={isCopied ? "default" : "outline"}
    >
      {isCopied ? "Copied!" : "Copy Text"}
    </Button>
  )
}

Copy with custom delay

import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
import { Button } from "@/components/ui/button"
 
export function CustomDelayExample() {
  // Reset feedback state after 3 seconds
  const { isCopied, copyToClipboard } = useCopyToClipboard(3000)
 
  return (
    <Button onClick={() => copyToClipboard("Text to copy")}>
      {isCopied ? "Copied!" : "Copy"}
    </Button>
  )
}

Copy code block with icon

import { Check, Copy } from "lucide-react"
 
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
import { Button } from "@/components/ui/button"
 
export function CopyCodeBlock() {
  const { isCopied, copyToClipboard } = useCopyToClipboard()
 
  const code = `const greeting = "Hello, World!";
console.log(greeting);`
 
  return (
    <div className="relative">
      <pre className="bg-muted overflow-x-auto rounded-md p-4">
        <code>{code}</code>
      </pre>
      <Button
        size="sm"
        variant="ghost"
        onClick={() => copyToClipboard(code)}
        className="absolute top-2 right-2 h-8 w-8 p-0"
      >
        {isCopied ? (
          <Check className="h-4 w-4 text-green-600" />
        ) : (
          <Copy className="h-4 w-4" />
        )}
      </Button>
    </div>
  )
}

Copy URL or sharing

import { Check, Share2 } from "lucide-react"
 
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
import { Button } from "@/components/ui/button"
 
export function ShareButton() {
  const { isCopied, copyToClipboard } = useCopyToClipboard(2000)
 
  const handleShare = () => {
    const url = window.location.href
    copyToClipboard(url)
  }
 
  return (
    <Button onClick={handleShare} variant="outline" size="sm">
      {isCopied ? (
        <>
          <Check className="mr-2 h-4 w-4" />
          Link Copied!
        </>
      ) : (
        <>
          <Share2 className="mr-2 h-4 w-4" />
          Share Link
        </>
      )}
    </Button>
  )
}

Copy form data

import { useState } from "react"
 
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
 
export function CopyFormData() {
  const { isCopied, copyToClipboard } = useCopyToClipboard()
  const [email, setEmail] = useState("user@example.com")
  const [apiKey, setApiKey] = useState("sk-1234567890abcdef")
 
  const copyCredentials = () => {
    const credentials = `Email: ${email}
API Key: ${apiKey}
Environment: Production`
    copyToClipboard(credentials)
  }
 
  return (
    <div className="space-y-4 rounded-lg border p-4">
      <div className="space-y-2">
        <Label htmlFor="email">Email</Label>
        <Input
          id="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          readOnly
        />
      </div>
 
      <div className="space-y-2">
        <Label htmlFor="api-key">API Key</Label>
        <Input
          id="api-key"
          type="password"
          value={apiKey}
          onChange={(e) => setApiKey(e.target.value)}
          readOnly
        />
      </div>
 
      <Button onClick={copyCredentials} className="w-full">
        {isCopied ? "Credentials Copied!" : "Copy Credentials"}
      </Button>
    </div>
  )
}

Copy with toast notification

import { useEffect } from "react"
import { toast } from "sonner"
 
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
import { Button } from "@/components/ui/button"
 
export function CopyWithToast() {
  const { isCopied, copyToClipboard } = useCopyToClipboard()
 
  useEffect(() => {
    if (isCopied) {
      toast.success("Text copied to clipboard!")
    }
  }, [isCopied])
 
  const handleCopy = () => {
    copyToClipboard("This text will show a toast notification when copied!")
  }
 
  return <Button onClick={handleCopy}>Copy with Toast</Button>
}

API

Parameters

ParameterTypeDefaultDescription
delaynumber5000Duration in milliseconds to keep the isCopied state as true

Returns

PropertyTypeDescription
isCopiedbooleanWhether text was recently copied (resets after delay)
copyToClipboard(text: string) => voidFunction to copy text to clipboard

Browser Support

The hook automatically handles browser compatibility:

  • Modern browsers: Uses the Clipboard API (navigator.clipboard.writeText)
  • Older browsers: Falls back to document.execCommand('copy') with temporary textarea
  • Error handling: Logs errors to console and continues gracefully

TypeScript

The hook is fully typed and provides excellent IntelliSense support:

// The hook is typed like this:
declare const useCopyToClipboard: (delay?: number) => {
  isCopied: boolean
  copyToClipboard: (text: string) => void
}
 
// Usage with TypeScript
const { isCopied, copyToClipboard } = useCopyToClipboard(3000)
 
// isCopied is inferred as boolean
// copyToClipboard is inferred as (text: string) => void