The Right Way to Copy to Clipboard in React (2024)
Source: Dev.to

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 / Approach | Weekly Downloads | Main Drawbacks |
|---|---|---|
react-copy-to-clipboard | 500K+ | • 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
| Feature | This Library | Others |
|---|---|---|
| Hooks API | ✅ | Sometimes |
| Component API | ✅ | Sometimes |
| HTML/Rich content | ✅ | ❌ |
| TypeScript | ✅ First‑class | Partial |
| Bundle size | ~1 KB | 2‑5 KB |
| Dependencies | 0 | Varies |
| SSR‑safe | ✅ | Often 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:
- Modern browsers (Chrome 76+, Firefox 63+, Safari 13.1+):
Usesnavigator.clipboard.write()for full HTML support. - Older modern browsers:
Falls back tonavigator.clipboard.writeText()for plain‑text copying. - Legacy browsers (IE 11, old Safari):
Falls back todocument.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:
| Library | Size (gzipped) |
|---|---|
@samithahansaka/clipboard | ~1 KB |
react-copy-to-clipboard | ~2 KB |
use-clipboard-copy | ~3 KB |
