Documentation
Templates
Hooks
useCopyToClipboard

useCopyToClipboard

Copy text to clipboard with success/error state.

Use Cases

  • Copy code snippets
  • Share links
  • Copy referral codes
  • Copy API keys or tokens

Code

import { useState, useCallback } from 'react';
 
interface UseCopyToClipboardReturn {
  copied: boolean;
  error: Error | null;
  copy: (text: string) => Promise<boolean>;
  reset: () => void;
}
 
export function useCopyToClipboard(
  resetDelay: number = 2000
): UseCopyToClipboardReturn {
  const [copied, setCopied] = useState(false);
  const [error, setError] = useState<Error | null>(null);
 
  const copy = useCallback(
    async (text: string): Promise<boolean> => {
      if (!navigator?.clipboard) {
        const error = new Error('Clipboard API not supported');
        setError(error);
        return false;
      }
 
      try {
        await navigator.clipboard.writeText(text);
        setCopied(true);
        setError(null);
 
        if (resetDelay > 0) {
          setTimeout(() => {
            setCopied(false);
          }, resetDelay);
        }
 
        return true;
      } catch (err) {
        const error = err instanceof Error ? err : new Error('Copy failed');
        setError(error);
        setCopied(false);
        return false;
      }
    },
    [resetDelay]
  );
 
  const reset = useCallback(() => {
    setCopied(false);
    setError(null);
  }, []);
 
  return { copied, error, copy, reset };
}

Usage

Copy Button

import { Check, Copy } from 'lucide-react';
import { useCopyToClipboard } from '@/hooks/useCopyToClipboard';
import { Button } from '@/components/ui/button';
 
function CopyButton({ text }: { text: string }) {
  const { copied, copy } = useCopyToClipboard();
 
  return (
    <Button
      variant="outline"
      size="sm"
      onClick={() => copy(text)}
    >
      {copied ? (
        <>
          <Check className="mr-2 h-4 w-4" />
          Copied!
        </>
      ) : (
        <>
          <Copy className="mr-2 h-4 w-4" />
          Copy
        </>
      )}
    </Button>
  );
}

Code Block with Copy

function CodeBlock({ code, language }: { code: string; language: string }) {
  const { copied, copy } = useCopyToClipboard();
 
  return (
    <div className="relative rounded-lg bg-gray-900 p-4">
      <button
        onClick={() => copy(code)}
        className="absolute right-2 top-2 rounded bg-gray-700 px-2 py-1 text-xs text-white hover:bg-gray-600"
      >
        {copied ? 'Copied!' : 'Copy'}
      </button>
      <pre className="text-gray-100">
        <code>{code}</code>
      </pre>
    </div>
  );
}

Share Link

import { toast } from 'sonner';
 
function ShareButton({ url }: { url: string }) {
  const { copy } = useCopyToClipboard();
 
  const handleShare = async () => {
    const success = await copy(url);
    if (success) {
      toast.success('Link copied to clipboard');
    } else {
      toast.error('Failed to copy link');
    }
  };
 
  return (
    <button onClick={handleShare}>
      Share
    </button>
  );
}

Copy with Icon Toggle

function CopyIconButton({ text }: { text: string }) {
  const { copied, copy } = useCopyToClipboard(1500);
 
  return (
    <button
      onClick={() => copy(text)}
      className="rounded p-2 hover:bg-gray-100"
      title={copied ? 'Copied!' : 'Copy to clipboard'}
    >
      {copied ? (
        <Check className="h-4 w-4 text-green-500" />
      ) : (
        <Copy className="h-4 w-4 text-gray-500" />
      )}
    </button>
  );
}

With Toast Notification

function useCopyWithToast() {
  const { copy } = useCopyToClipboard(0); // No auto-reset needed
 
  return async (text: string, label?: string) => {
    const success = await copy(text);
 
    if (success) {
      toast.success(label ? `${label} copied` : 'Copied to clipboard');
    } else {
      toast.error('Failed to copy');
    }
 
    return success;
  };
}
 
// Usage
const copyWithToast = useCopyWithToast();
copyWithToast(user.email, 'Email');