The Right Way to Copy to Clipboard in React (2024)

Published: (December 18, 2025 at 10:46 PM EST)
4 min read
Source: Dev.to

Source: Dev.to

Cover image for “The Right Way to Copy to Clipboard in React (2024)”

Samitha Widanage

Copying to the clipboard seems simple, right? Click a button, the text goes to the clipboard. Done.

But if you’ve ever tried implementing this in React, you know it’s not that straightforward. Browser APIs have evolved, legacy fallbacks are needed, and handling state properly requires thought.

I built @samithahansaka/clipboard to solve this once and for all. Here’s why, and how you can use it.

The Problem with Existing Solutions

I evaluated the popular options:

Library / ApproachWeekly DownloadsMain Drawbacks
react-copy-to-clipboard500K+• No hooks API (requires a class‑component wrapper)
• No support for rich (HTML) content
• Last meaningful update was years ago
Manual implementation (copy‑paste from Stack Overflow)• Doesn’t handle fallbacks properly
• No state management for “Copied!” feedback
• SSR issues with frameworks like Next.js or Remix
use-clipboard-copy• No HTML content support
• Limited configuration options

Bottom line: I needed something modern, lightweight, and complete.

Introducing @samithahansaka/clipboard

npm install @samithahansaka/clipboard

What makes it different

FeatureThis LibraryOthers
Hooks APISometimes
Component APISometimes
HTML/Rich content
TypeScript✅ First‑classPartial
Bundle size~1 KB2‑5 KB
Dependencies0Varies
SSR‑safeOften breaks

Basic Usage

import { useCopyToClipboard } from '@samithahansaka/clipboard';

function CopyButton() {
  const { copy, copied } = useCopyToClipboard();

  return (
    <button onClick={() => copy('Hello, World!')}>
      {copied ? '✓ Copied!' : 'Copy'}
    </button>
  );
}

That’s it. The copied state automatically resets after 2 seconds.

Real‑World Example: Code Block Component

import { useCopyToClipboard } from '@samithahansaka/clipboard';

function CodeBlock({ code, language }: { code: string; language: string }) {
  const { copy, copied, error } = useCopyToClipboard({
    successDuration: 2000,
  });

  return (
    <div className="code-block">
      <div className="header">
        <span>{language}</span>
        <button
          onClick={() => copy(code)}
          className={copied ? 'success' : ''}
        >
          {copied ? '✓ Copied!' : 'Copy'}
        </button>
      </div>
      <pre>{code}</pre>
      {error && <div className="error">Copy failed</div>}
    </div>
  );
}

/* Additional snippet example */
{/* {error ? 'Failed!' : copied ? 'Copied!' : 'Copy'} */}

The component displays a code snippet with a copy button that shows a success state for 2 seconds and handles any copy errors gracefully.

Copying Rich HTML Content

This is where most libraries fall short. Need to copy formatted text that pastes correctly into Google Docs or email?

const { copy } = useCopyToClipboard();

// Copy both plain text **AND** HTML
copy({
  text: 'Hello, World!',          // Fallback for plain‑text apps
  html: '**Hello, World!**'       // Rich content for compatible apps
});

When pasted into:

  • Notepad / Terminal: Hello, World!
  • Google Docs / Email: Hello, World! (bold)

Integration with Toast Libraries

Works seamlessly with any notification library:

import { useCopyToClipboard } from '@samithahansaka/clipboard';
import { toast } from 'sonner'; // or react-hot-toast, etc.

function ShareButton({ url }: { url: string }) {
  const { copy } = useCopyToClipboard({
    onSuccess: () => toast.success('Link copied!'),
    onError: () => toast.error('Failed to copy'),
  });

  return (
    <button onClick={() => copy(url)}>
      Share
    </button>
  );
}

Component API for Simple Cases

Prefer render props? There’s a component API too:

import { CopyButton } from '@samithahansaka/clipboard';

function App() {
  return (
    <CopyButton>
      {/* children can be a custom button or any element */}
      Copy
    </CopyButton>
  );
}
<CopyButton>
  {({ copy, copied }) => (
    <button onClick={copy}>
      {copied ? 'Done!' : 'Copy'}
    </button>
  )}
</CopyButton>

How It Works Under the Hood

The library uses progressive enhancement:

  1. Modern browsers (Chrome 76+, Firefox 63+, Safari 13.1+):
    Uses navigator.clipboard.write() for full HTML support.
  2. Older modern browsers:
    Falls back to navigator.clipboard.writeText() for plain‑text copying.
  3. Legacy browsers (IE 11, old Safari):
    Falls back to document.execCommand('copy') with a hidden <textarea>.

You don’t have to think about any of this. It just works.

TypeScript Support

Full type definitions are included:

import {
  useCopyToClipboard,
  CopyButton,
  type CopyContent,
  type ClipboardItem,
} from '@samithahansaka/clipboard';

// TypeScript knows exactly what `copy()` accepts
const { copy } = useCopyToClipboard();

copy('string');                         // ✅
copy({ text: 'hi' });                  // ✅
copy({ text: 'hi', html: '**hi**' }); // ✅
copy(123);                             // ❌ Type error

SSR‑Safe

Works out of the box with Next.js, Remix, Gatsby, or any SSR framework:

  • No “window is not defined” errors.
  • No need for dynamic imports.
  • Just use it normally.
import { useCopyToClipboard } from '@samithahansaka/clipboard';

Bundle Size

The entire library is ~1 KB gzipped. For comparison:

LibrarySize (gzipped)
@samithahansaka/clipboard~1 KB
react-copy-to-clipboard~2 KB
use-clipboard-copy~3 KB
Back to Blog

Related posts

Read more »

Ultimate React Hooks Guide

markdown !TenE Organization profile imagehttps://media2.dev.to/dynamic/image/width=50,height=50,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads....

Build Your Own React

Article URL: https://pomb.us/build-your-own-react/ Comments URL: https://news.ycombinator.com/item?id=46332526 Points: 18 Comments: 3...