clipboard-polyfill, copy-text-to-clipboard, copy-to-clipboard, react-copy-to-clipboard, and vue-clipboard2 are npm packages designed to simplify copying text to the user's clipboard across different browsers. They address inconsistencies in browser support for the modern navigator.clipboard API by providing unified interfaces, fallback mechanisms (like execCommand), and framework-specific integrations. These libraries handle cross-browser quirks, user gesture requirements, and permission models so developers can implement "copy" functionality reliably without writing low-level DOM manipulation code.
Copying text to the clipboard is a common requirement across web applications — from sharing links to exporting data. While the native navigator.clipboard API provides a clean interface, browser support and permission models vary. The packages clipboard-polyfill, copy-text-to-clipboard, copy-to-clipboard, react-copy-to-clipboard, and vue-clipboard2 each offer different approaches to abstract this complexity. Let’s examine their technical trade-offs in real-world scenarios.
clipboard-polyfill focuses on providing a faithful polyfill for the standard navigator.clipboard.writeText() API. It emulates the modern API even in older browsers by falling back to legacy techniques like invisible <textarea> manipulation.
// clipboard-polyfill
import { writeText } from 'clipboard-polyfill';
await writeText('Hello, world!');
// Works like native navigator.clipboard.writeText(), even in IE11
copy-text-to-clipboard takes a minimal, promise-based approach that wraps the native API but doesn’t polyfill it. If the native API isn’t available, it throws an error.
// copy-text-to-clipboard
import copy from 'copy-text-to-clipboard';
const success = copy('Hello, world!');
// Returns boolean; no fallback for unsupported environments
copy-to-clipboard offers a hybrid strategy: it uses the modern API when available, but automatically falls back to the legacy execCommand('copy') method without requiring developer intervention.
// copy-to-clipboard
import copy from 'copy-to-clipboard';
const success = copy('Hello, world!');
// Tries navigator.clipboard first, then execCommand if needed
react-copy-to-clipboard is a React-specific wrapper around copy-to-clipboard. It exposes a render prop or component that handles copying on user interaction.
// react-copy-to-clipboard
import { CopyToClipboard } from 'react-copy-to-clipboard';
<CopyToClipboard text="Hello, world!" onCopy={() => console.log('copied')}>
<button>Copy</button>
</CopyToClipboard>
vue-clipboard2 is a Vue 2 plugin that registers a global directive (v-clipboard) and a programmatic method (this.$copyText). It internally uses clipboard-polyfill.
// vue-clipboard2 (Vue 2 only)
import Vue from 'vue';
import VueClipboard from 'vue-clipboard2';
Vue.use(VueClipboard);
// In component template:
// <button v-clipboard:copy="'Hello, world!'">Copy</button>
⚠️ Important:
vue-clipboard2is deprecated as of 2023 and only supports Vue 2. The author recommends migrating to@soerenmartius/vue3-clipboardfor Vue 3 projects. Do not usevue-clipboard2in new projects.
The modern navigator.clipboard API requires user activation (e.g., a click) and may prompt for permissions in some contexts. How each package deals with this varies:
clipboard-polyfill mimics the native behavior as closely as possible. In browsers that support the Permissions API, it will trigger prompts when needed. In older browsers, it silently uses execCommand without prompts.copy-text-to-clipboard delegates entirely to the native API, so it inherits all its permission requirements and potential rejections.copy-to-clipboard avoids permission issues in older browsers by using execCommand, which doesn’t require explicit permissions but only works during user-initiated events.react-copy-to-clipboard inherits the behavior of copy-to-clipboard, so it works reliably in click handlers without extra setup.vue-clipboard2 inherits clipboard-polyfill’s behavior, but its directive system ensures copying only happens on user-triggered events (like clicks), sidestepping most permission errors.If you’re working in a framework, consider whether the added layer simplifies your code or just adds indirection.
For React, react-copy-to-clipboard reduces boilerplate:
// Without helper
<button onClick={() => {
copy('text');
setCopied(true);
}}>Copy</button>
// With react-copy-to-clipboard
<CopyToClipboard text="text" onCopy={() => setCopied(true)}>
<button>Copy</button>
</CopyToClipboard>
But note: it doesn’t support hooks natively (no useCopy), so many teams prefer calling copy-to-clipboard directly inside a custom hook.
For Vue, since vue-clipboard2 is deprecated and Vue 3 lacks an official successor, most developers now use copy-to-clipboard or clipboard-polyfill directly in methods:
// Vue 3 composition API
import copy from 'copy-to-clipboard';
const copyText = () => {
copy('Hello');
};
Reliable clipboard operations require checking success and handling failures gracefully.
clipboard-polyfill returns a promise that rejects on failure (e.g., if the user denies permission).
try {
await writeText('text');
console.log('Success');
} catch (err) {
console.error('Failed to copy', err);
}
copy-text-to-clipboard and copy-to-clipboard return a boolean:
if (copy('text')) {
// success
} else {
// failed (e.g., not in user gesture context)
}
react-copy-to-clipboard passes the boolean result to onCopy:
<CopyToClipboard text="x" onCopy={(text, result) => {
if (result) alert('Copied!');
}}>
<button>Copy</button>
</CopyToClipboard>
vue-clipboard2 emits events like @success and @error:
<button v-clipboard:copy="text" @success="onCopySuccess"></button>
clipboard-polyfill or copy-to-clipboard are your only viable options.copy-text-to-clipboard is sufficient and has zero fallback overhead.react-copy-to-clipboard and vue-clipboard2 inherit the browser support of their underlying libraries.| Use Case | Recommended Package |
|---|---|
| Need IE11 support or maximum compatibility | clipboard-polyfill or copy-to-clipboard |
| Modern browsers only, minimal footprint | copy-text-to-clipboard |
| React app with simple copy buttons | react-copy-to-clipboard (or roll your own hook with copy-to-clipboard) |
| Vue 2 legacy project | vue-clipboard2 (but plan to migrate) |
| Vue 3 or framework-agnostic | copy-to-clipboard (best balance of size, compatibility, and simplicity) |
vue-clipboard2 for new projects — it’s deprecated and Vue 2–only.copy-to-clipboard for most general-purpose use cases: it’s tiny, handles fallbacks automatically, and returns clear success/failure feedback.clipboard-polyfill only if you specifically need promise-based async/await syntax and full API compliance with the standard navigator.clipboard interface.copy-text-to-clipboard unless you’re certain your users are on modern browsers and you want to avoid any legacy code paths.react-copy-to-clipboard saves enough boilerplate to justify the dependency — often, a 5-line custom hook is cleaner.Choose clipboard-polyfill if you need a standards-compliant polyfill that closely mimics the native navigator.clipboard.writeText() API, including promise-based async/await usage, and must support very old browsers like IE11. It’s ideal when you want your code to look and behave like modern browser APIs regardless of the runtime environment.
Choose copy-text-to-clipboard if you're targeting only modern browsers (Chrome 66+, Firefox 63+, Safari 13.1+) and want the smallest possible dependency with zero fallback logic. It simply wraps the native API and returns a boolean, making it fast and predictable—but it will fail silently or throw in unsupported environments.
Choose copy-to-clipboard for the best balance of compatibility, simplicity, and reliability in most projects. It automatically uses the modern API when available and falls back to execCommand in older browsers, returning a clear success/failure boolean. It works well in both vanilla JS and framework-based apps without extra abstraction layers.
Choose react-copy-to-clipboard if you're building a React application and prefer a declarative, component-based API for copy actions. It reduces boilerplate for simple use cases like copy buttons, but consider whether a lightweight custom hook using copy-to-clipboard might offer more flexibility with less dependency overhead.
Do not choose vue-clipboard2 for new projects—it is officially deprecated and only supports Vue 2. If maintaining a legacy Vue 2 codebase, it provides directive-based copying via v-clipboard, but plan to migrate to a modern alternative like copy-to-clipboard used directly in Vue methods or composables.
clipboard-polyfillclipboard-polyfill to copy text! ⚠️Note: As of 2020, you can use navigator.clipboard.writeText(...) in the stable versions of all major browsers. This library will only be useful to you if you want to:
text/html in Firefox ≤126,ClipboardItem API in Firefox ≤126, orjsdom).See the Compatibility section below for more details.
Makes copying on the web as easy as:
clipboard.writeText("hello world");
This library is a ponyfill/polyfill for the modern Promise-based asynchronous clipboard API.
If you use npm, install:
npm install clipboard-polyfill
Sample app that copies text to the clipboard:
import * as clipboard from "clipboard-polyfill";
function handler() {
clipboard.writeText("This text is plain.").then(
() => { console.log("success!"); },
() => { console.log("error!"); }
);
}
window.addEventListener("DOMContentLoaded", function () {
const button = document.body.appendChild(document.createElement("button"));
button.textContent = "Copy";
button.addEventListener("click", handler);
});
Notes:
button click).
async/await syntaximport * as clipboard from "clipboard-polyfill";
async function handler() {
console.log("Previous clipboard text:", await clipboard.readText());
await clipboard.writeText("This text is plain.");
}
window.addEventListener("DOMContentLoaded", function () {
const button = document.body.appendChild(document.createElement("button"));
button.textContent = "Copy";
button.addEventListener("click", handler);
});
import * as clipboard from "clipboard-polyfill";
async function handler() {
console.log("Previous clipboard contents:", await clipboard.read());
const item = new clipboard.ClipboardItem({
"text/html": new Blob(
["<i>Markup</i> <b>text</b>. Paste me into a rich text editor."],
{ type: "text/html" }
),
"text/plain": new Blob(
["Fallback markup text. Paste me into a rich text editor."],
{ type: "text/plain" }
),
});
await clipboard.write([item]);
}
window.addEventListener("DOMContentLoaded", function () {
const button = document.body.appendChild(document.createElement("button"));
button.textContent = "Copy";
button.addEventListener("click", handler);
});
Check the Clipboard API specification for more details.
Notes:
async functions for the await syntax.text/plain and text/html are the only data types that can be written to the clipboard across most browsers.read() may only return a subset of supported data types, even if the clipboard contains more data types. There is no way to tell if there were more data types.overwrite-globals versionIf you want the library to overwrite the global clipboard API with its implementations, import clipboard-polyfill/overwrite-globals. This will turn the library from a ponyfill into a proper polyfill, so you can write code as if the async clipboard API were already implemented in your browser:
import "clipboard-polyfill/overwrite-globals";
async function handler() {
const item = new window.ClipboardItem({
"text/html": new Blob(
["<i>Markup</i> <b>text</b>. Paste me into a rich text editor."],
{ type: "text/html" }
),
"text/plain": new Blob(
["Fallback markup text. Paste me into a rich text editor."],
{ type: "text/plain" }
),
});
navigator.clipboard.write([item]);
}
window.addEventListener("DOMContentLoaded", function () {
const button = document.body.appendChild(document.createElement("button"));
button.textContent = "Copy";
button.addEventListener("click", handler);
});
This approach is not recommended, because it may break any other code that interacts with the clipboard API globals, and may be incompatible with future browser implementations.
Promise includedIf you need to grab a version that "just works", download clipboard-polyfill.window-var.promise.es5.js and include it using a <script> tag:
<script src="./clipboard-polyfill.window-var.promise.es5.js"></script>
<button onclick="copy()">Copy text!</button>
<script>
// `clipboard` is defined on the global `window` object.
function copy() {
clipboard.writeText("hello world!");
}
</script>
Thanks to the conveniences of the modern JS ecosystem, we do not provide tree shaken, minified, or CommonJS builds anymore. To get such builds without losing compatibility, pass clipboard-polyfill builds through esbuild. For example:
mkdir temp && cd temp && npm install clipboard-polyfill esbuild
# Minify the ES6 build:
echo 'export * from "clipboard-polyfill";' | npx esbuild --format=esm --target=es6 --bundle --minify
# Include just the `writeText()` export and minify:
echo 'export { writeText } from "clipboard-polyfill";' | npx esbuild --format=esm --target=es6 --bundle --minify
# Minify an ES5 build:
cat node_modules/clipboard-polyfill/dist/es5/window-var/clipboard-polyfill.window-var.promise.es5.js | npx esbuild --format=esm --target=es5 --bundle --minify
# Get a CommonJS build:
echo 'export * from "clipboard-polyfill";' | npx esbuild --format=cjs --target=es6 --bundle
clipboard-polyfill?Browsers have implemented several clipboard APIs over time, and writing to the clipboard without triggering bugs in various old and current browsers is fairly tricky. In every browser that supports copying to the clipboard in some way, clipboard-polyfill attempts to act as close as possible to the async clipboard API. (See above for disclaimers and limitations.)
See this presentation for for a longer history of clipboard access on the web.
clipboard-polyfill adds support.Write support by earliest browser version:
| Browser | writeText() | write() (HTML) | write() (other formats) |
|---|---|---|---|
| Safari 13.1 | ☑️ | ☑️ | ☑️ (image/uri-list, image/png) |
| Chrome 86ᵃ / Edge 86 | ☑️ | ☑️ | ☑️ (image/png) |
| Chrome 76ᵃ / Edge 79 | ☑️ | ✅ | ☑️ (image/png) |
| Chrome 66ᵃ / Firefox 63 | ☑️ | ✅ | ❌ |
| Safari 10 / Chrome 42ᵃ / Edgeᵈ / Firefox 41 | ✅ | ✅ᵇ | ❌ |
| IE 9 | ✅ᶜ | ❌ | ❌ |
Read support:
| Browser | readText() | read() (HTML) | read() (other formats) |
|---|---|---|---|
| Safari 13.1 | ☑️ | ☑️ | ☑️ (image/uri-list, image/png) |
| Chrome 76 ᵃ / Edge 79 | ☑️ | ❌ | ☑️ (image/png) |
| Chrome 66ᵃ | ☑️ | ❌ | ❌ |
| IE 9 | ✅ᶜ | ❌ | ❌ |
| Firefox | ❌ | ❌ | ❌ |
window.Promise if you want the library to work.clipboard-polyfill will always report success in this case.ClipboardItem constructor.text/html data type is not written using the expected CF_HTML format. clipboard-polyfill does not try to work around this, since 1) it would require fragile browser version sniffing, 2) users of Edge are not generally stuck on version < 17, and 3) the failure mode for other browsers would be that invalid clipboard HTML is copied. (Edge Bug #14372529, #73)clipboard-polyfill uses a variety of heuristics to work around compatibility bugs. Please let us know if you are running into compatibility issues with any of the browsers listed above.
| Browser | First version supportingnavigator.clipboard.writeText(...) | Release Date |
|---|---|---|
| Chrome | 66+ | April 2018 |
| Firefox | 53+ | October 2018 |
| Edge | 79+ (first Chromium-based release) | January 2020 |
| Safari | 13.1+ | March 2020 |
This project dates from a time when clipboard access in JS was barely becoming possible, and ergonomic clipboard API efforts were stalling. (See this presentation for a bit more context.) Fortunately, an ergonomic API with the same functionality is now available in all modern browsers since 2020:
document.execCommand("copy") call (with many, many issues).clipboard.js (half a year before @zenorocha picked the same name 😛).crbug.com/593475).clipboard-polyfill to reflect a v2 API overhaul aligned with the draft spec.navigator.clipboard.writeText().navigator.clipboard.write() (including text/html support).Thanks to Gary Kacmarcik, Hallvord Steen, and others for helping to bring the async clipboard API to life!
If you only need to copy text in modern browsers, consider using navigator.clipboard.writeText() directly: https://caniuse.com/mdn-api_clipboard_writetext
If you need copy text in older browsers as well, you could also try this gist for a simple hacky solution.