uniclip
v0.0.1
Published
The universal clipboard library for browser, Node, Bun, Deno, Electron, Tauri, and React Native.
Downloads
34
Maintainers
Readme
uniclip
The universal clipboard library for the browser, Node, Bun, Deno, Electron, Tauri, and React Native
How? import { clipboard } from "uniclip" once and the same code reads, writes, and watches the OS clipboard everywhere. The package.json exports map picks the right backend for each runtime, so there's no if (typeof navigator …) branching.
It replaces a stack of single-purpose libraries (clipboardy, clipboard.js, copy-to-clipboard, arboard) with one tiny package and a typed multi-format API.
Install
npm install uniclipUsage
import { clipboard } from "uniclip";
await clipboard.writeText("hi");
const text = await clipboard.readText();The same import works in every supported runtime. Writing multiple formats at once is just as easy:
await clipboard.write({
"text/plain": "hi",
"text/html": "<b>hi</b>",
"application/x-react-grab": JSON.stringify(payload),
});All formats land on the OS clipboard in a single transaction.
Per-runtime examples
If you're using a specific runtime, the snippets below are the practical starting points.
Browser
import { clipboard } from "uniclip";
await clipboard.writeText("hello");
const text = await clipboard.readText();For multi-format writes from a keyboard shortcut, use writeSync so the user-gesture token survives:
document.addEventListener("keydown", (event) => {
if ((event.metaKey || event.ctrlKey) && event.key === "c") {
clipboard.writeSync({
"text/plain": "fallback for terminals",
"text/html": "<b>fallback for editors</b>",
});
}
});Node / Bun / Deno
import { clipboard } from "uniclip";
await clipboard.writeText("from the cli");
const text = await clipboard.readText();You'll need one of the platform helpers installed:
- macOS ships
osascriptby default. - Windows ships
powershell.exeby default. - Linux needs
xclip(X11) orwl-copy/wl-paste(Wayland).
If a helper is missing, uniclip throws a MissingDependencyError with a .hint telling you exactly what to install.
Electron
uniclip's exports map auto-routes to the Electron build, which uses clipboard from the Electron API in the main process and falls back to the browser path in the renderer.
import { clipboard } from "uniclip";
await clipboard.write({ "text/plain": "hi", "text/html": "<b>hi</b>" });If you proxy the clipboard through ipcRenderer, see Custom backends below.
Tauri v2
Add the official plugin to your Tauri app:
npm install @tauri-apps/plugin-clipboard-managerThen use uniclip the same way. The Tauri build delegates to the plugin under the hood.
import { clipboard } from "uniclip";
await clipboard.writeText("hi from tauri");React Native
Add one of the supported clipboard modules:
npm install @react-native-clipboard/clipboard
# or
npm install expo-clipboardThen:
import { clipboard } from "uniclip";
await clipboard.writeText("hi from rn");writeImage works when the loaded module exposes setImage(base64). writeFiles and writeRTF are not available on RN.
Watching the clipboard
import { clipboard } from "uniclip";
const off = clipboard.watch((snapshot) => {
console.log("clipboard changed:", snapshot.text);
});
// later:
off();watch polls every 500 ms by default and dedupes identical states. On iOS and Android (RN), it uses native event subscriptions when the underlying module exposes them. Errors go to options.onError (default console.error); transient errors keep the loop alive, non-recoverable errors halt it.
const off = clipboard.watch((snapshot) => console.log(snapshot.text), {
intervalMs: 250,
signal: abortController.signal,
onError: (error) => console.warn("[uniclip]", error),
});Drag and drop
dataTransfer is a sibling export for the drag-and-drop side of the same MIME-keyed transfer model. Browser and Electron renderer only.
import { dataTransfer } from "uniclip";
draggable.addEventListener("dragstart", (event) => {
dataTransfer.set(event, {
"text/plain": "drag me",
"text/html": "<em>drag me</em>",
});
});
const cleanup = dataTransfer.dropZone(document.querySelector("#zone"), (snap) => {
console.log("dropped:", snap.text, snap.files.length);
});
// later:
cleanup();Has-checks (no privacy banner)
iOS 14+, macOS 15.4+, and Android 12+ pop a paste banner when you actually read the clipboard. Use the has* family to ask "is there content of kind X?" without triggering the banner:
if (await clipboard.hasURL()) {
const url = await clipboard.readURL();
}Available: clipboard.types, has, hasText, hasHTML, hasRTF, hasImage, hasFiles, hasURL.
Custom backends
Override the active backend for Tauri apps with custom Rust IPC clipboards, Electron renderers proxying through the main process, or test fixtures that mock the clipboard layer entirely.
import { setClipboardImplementation } from "uniclip";
setClipboardImplementation({
read: async () => stubSnapshot,
write: async (record) => mockServer.write(record),
watch: (callback) => mockServer.subscribe(callback),
});
// reset to default:
setClipboardImplementation(null);Errors
import {
ClipboardError,
ClipboardReadError,
ClipboardWriteError,
MissingDependencyError,
PermissionDeniedError,
UnsupportedFormatError,
UnsupportedRuntimeError,
} from "uniclip";Every error carries .hint (an actionable installation, permission, or configuration tip), .cause (the underlying platform error), and .recoverable (used by clipboard.watch to decide whether to halt the loop).
API at a glance
clipboard.writeText(text, options?);
clipboard.writeHTML(html, options?);
clipboard.writeRTF(rtf, options?);
clipboard.writeImage(data, options?);
clipboard.writeFiles(paths, options?);
clipboard.writeURL(url, options?);
clipboard.write(record, options?); // multi-format atomic write
clipboard.writeSync(record, options?); // browser-only synchronous write
clipboard.readText(options?);
clipboard.readHTML(options?);
clipboard.readRTF(options?);
clipboard.readImage(options?);
clipboard.readFiles(options?);
clipboard.readURL(options?);
clipboard.read(options?); // returns a ClipboardSnapshot
clipboard.read(mimeType, options?); // returns a single value
clipboard.types(options?);
clipboard.has(mime, options?);
clipboard.hasText | hasHTML | hasRTF | hasImage | hasFiles | hasURL(options?);
clipboard.clear(options?);
clipboard.watch(callback, options?);
dataTransfer.set(event, record, options?);
dataTransfer.from(event);
dataTransfer.dropZone(element, callback);
setClipboardImplementation(impl | null);See packages/uniclip/src/types.ts for the full ClipboardSnapshot, ClipboardWriteRecord, WriteOptions, ReadOptions, WatchOptions, and error class definitions.
Resources & Contributing Back
Looking to contribute back? Check out the Contributing Guide.
Find a bug? Head over to our issue tracker and we'll do our best to help. We love pull requests, too!
→ Start contributing on GitHub
License
uniclip is MIT-licensed open-source software.
