Common Use Cases for the Clipboard API
Practical patterns for integrating clipboard operations into a Tauri v2 app with React, covering copy buttons, share features, and productivity workflows.
The clipboard plugin in Tauri v2 gives your desktop application direct, unrestricted access to the system clipboard. You are not limited by the browser's security model — reading and writing can happen on any user interaction, timer, or background task. This opens up possibilities that go far beyond a simple "Copy" button.
This page walks through three real-world patterns: building a reliable copy button, creating a share feature that puts a link on the clipboard, and using the clipboard to speed up repetitive tasks. Every example uses the official @tauri-apps/plugin-clipboard package with React and Vite.
Check your plugin setup:
The code examples assume the clipboard plugin is already added to your Tauri app. If you skipped that step, add the crate with cargo add tauri-plugin-clipboard, register it in main.rs with .plugin(tauri_plugin_clipboard::init()), and install the frontend package via npm install @tauri-apps/plugin-clipboard — or follow the Clipboard API Introduction. Also confirm the clipboard:default capability is enabled in your capability file, or the read/write permissions are explicitly granted.
Implementing a Copy Button
A copy button is the most common clipboard interaction. The user clicks, and the app places a value onto the system clipboard. With the Tauri plugin, this is a single async call.
The component below copies a static message. In a real app, the text might come from state, a form input, or a generated report.
import { useState } from "react";
import { writeText } from "@tauri-apps/plugin-clipboard";
export default function CopyMessageButton() {
const [copied, setCopied] = useState(false);
const handleCopy = async () => {
try {
await writeText("Hello from Tauri!");
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (error) {
console.error("Failed to copy:", error);
}
};
return (
<button onClick={handleCopy}>
{copied ? "Copied!" : "Copy to clipboard"}
</button>
);
}
writeText returns a promise, so the handler must be async. The try/catch guards against rare but possible failures — for instance, if another process locks the clipboard at exactly the wrong moment. The temporary "Copied!" state gives the user feedback without blocking further interaction.
Don't forget to await:
Calling writeText without await is a silent failure. The promise will reject, but you won't see the error unless you catch it. Always use await (or .then().catch()) inside an async click handler.
A copy button often needs to copy dynamic content, like the current value of an input field. The pattern is identical: pass the dynamic value to writeText inside the handler.
import { useState } from "react";
import { writeText } from "@tauri-apps/plugin-clipboard";
export default function CopyInputValue() {
const [text, setText] = useState("");
const handleCopy = async () => {
try {
await writeText(text);
// optionally show a toast
} catch (error) {
console.error("Failed to copy:", error);
}
};
return (
<div>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Type something…"
/>
<button onClick={handleCopy}>Copy</button>
</div>
);
}
The key insight here is that the clipboard API does not care where the string came from. It can be static, from state, from a Redux store, or computed — the call is always writeText(value).
Building a Share Feature
Many desktop apps include a "Share" or "Copy link" action that puts a URL on the clipboard. The operation itself is a writeText call, but the surrounding UX matters: you typically want to confirm the action and, in some cases, write both the link and a plain-text description.
Below is a share button that copies the current page URL (or a hardcoded link) and displays a brief success message.
import { useState } from "react";
import { writeText } from "@tauri-apps/plugin-clipboard";
interface ShareLinkButtonProps {
url: string;
}
export default function ShareLinkButton({ url }: ShareLinkButtonProps) {
const [shared, setShared] = useState(false);
const handleShare = async () => {
try {
await writeText(url);
setShared(true);
setTimeout(() => setShared(false), 3000);
} catch (error) {
console.error("Could not copy link:", error);
}
};
return (
<button onClick={handleShare}>
{shared ? "Link copied!" : "Copy link"}
</button>
);
}
If you want the clipboard to contain both a rich HTML representation and a plain-text fallback, use writeHtml together with writeText. Some applications prefer to read the HTML version when pasting into rich-text editors, while others only read plain text. Tauri's plugin lets you write both in sequence.
import { useState } from "react";
import { writeText, writeHtml } from "@tauri-apps/plugin-clipboard";
export default function ShareRichLinkButton() {
const [shared, setShared] = useState(false);
const articleUrl = "https://example.com/article";
const articleTitle = "Why Tauri Desktop Apps Are Different";
const handleShare = async () => {
const plainText = `${articleTitle} — ${articleUrl}`;
const htmlContent = `<a href="${articleUrl}">${articleTitle}</a>`;
try {
await writeText(plainText);
await writeHtml(htmlContent);
setShared(true);
setTimeout(() => setShared(false), 3000);
} catch (error) {
console.error("Failed to share:", error);
}
};
return (
<button onClick={handleShare}>
{shared ? "Link copied!" : "Share article"}
</button>
);
}
The order matters less than you might think, because both representations land on the clipboard independently. The pasting application decides which representation to use. Writing HTML without a plain-text fallback can confuse plain-text editors — they will see an empty clipboard. That is why this example always writes text first.
Rich clipboard is fully supported:
If your paste target supports HTML (e.g., a rich-text editor, email client, or word processor), it will automatically pick up the text/html representation. Plain-text fields will ignore it and use the plain text. You don't need to guess what the user will paste into.
Productivity Applications
Beyond one-off copy actions, the clipboard can become a productivity tool inside your Tauri app. Because the desktop environment places no restrictions on clipboard access (no user gesture requirement, no permission prompts at runtime), you can build features that feel like a natural part of the operating system.
Reading the Clipboard on Demand
A utility that reads whatever is currently on the clipboard and processes it — for example, a tool that extracts URLs or counts words — is straightforward.
import { useState } from "react";
import { readText } from "@tauri-apps/plugin-clipboard";
export default function ClipboardInspector() {
const [content, setContent] = useState<string | null>(null);
const handleRead = async () => {
try {
const text = await readText();
setContent(text || "(Clipboard is empty or not text)");
} catch (error) {
console.error("Read failed:", error);
setContent("Error reading clipboard");
}
};
return (
<div>
<button onClick={handleRead}>Read clipboard text</button>
{content !== null && (
<pre style={{ whiteSpace: "pre-wrap", marginTop: "1rem" }}>
{content}
</pre>
)}
</div>
);
}
readText returns a string. If the clipboard contains no text (e.g., an image or file), it returns an empty string rather than throwing. The button triggers the read explicitly, so the user is always in control — no background polling.
Clipboard content can be anything:
Never assume the clipboard contains exactly what your last copy operation put there. Another application might have overwritten it. Always validate or sanitise the data before using it for anything critical, and handle the empty-string case gracefully.
Using the Clipboard as a Temporary Scratchpad
Some productivity workflows involve copying several pieces of text in succession, then pasting them in a specific order. You can build a small clipboard stack inside your app: each time the user presses a "Collect" button, the current clipboard content is saved to an array, and later they can paste them one by one.
import { useState } from "react";
import { writeText, readText } from "@tauri-apps/plugin-clipboard";
export default function ClipboardStack() {
const [stack, setStack] = useState<string[]>([]);
const handleCollect = async () => {
try {
const text = await readText();
if (text) {
setStack((prev) => [...prev, text]);
}
} catch (error) {
console.error("Collect failed:", error);
}
};
const handlePasteNext = async () => {
if (stack.length === 0) return;
const [next, ...rest] = stack;
try {
await writeText(next);
setStack(rest);
} catch (error) {
console.error("Paste failed:", error);
}
};
return (
<div>
<button onClick={handleCollect}>Collect from clipboard</button>
<button onClick={handlePasteNext} disabled={stack.length === 0}>
Paste next ({stack.length} remaining)
</button>
</div>
);
}
The component stores snippets in React state — no file system, no database. This makes the feature ephemeral and safe: closing the app discards the stack. It also illustrates how readText and writeText can be combined to create a mini clipboard manager without any backend code.
Clearing the Clipboard After Sensitive Data
If your app copies a password, API key, or other secret, it is good practice to clear the clipboard after a short timeout or when the user navigates away. The plugin's clear function overwrites the clipboard content.
import { useState, useEffect } from "react";
import { writeText, clear } from "@tauri-apps/plugin-clipboard";
interface SafeCopyButtonProps {
secret: string;
}
export default function SafeCopyButton({ secret }: SafeCopyButtonProps) {
const [copied, setCopied] = useState(false);
const handleCopy = async () => {
try {
await writeText(secret);
setCopied(true);
} catch (error) {
console.error("Copy failed:", error);
}
};
useEffect(() => {
if (!copied) return;
const timer = setTimeout(async () => {
await clear();
setCopied(false);
}, 15000); // clear after 15 seconds
return () => clearTimeout(timer);
}, [copied]);
return (
<button onClick={handleCopy}>
{copied ? "Secret copied (will be cleared)" : "Copy secret"}
</button>
);
}
When the user clicks the button, the secret lands on the clipboard. Fifteen seconds later, clear wipes it. The timeout cleanup in the effect prevents the clear call if the component unmounts early. This pattern significantly reduces the risk of a secret lingering on the clipboard and being accidentally pasted later.
Clearing the clipboard is best-effort:
clear works by overwriting the clipboard with empty content. It reliably removes the data from the system clipboard, but it cannot retroactively delete data from any clipboard history tools the user might be running. Treat it as a safety net, not a guarantee.
Across all these examples, the pattern is the same: your React code calls functions from @tauri-apps/plugin-clipboard, the plugin forwards the request to the Tauri core written in Rust, and the core performs the actual system call. No browser permissions, no user prompts — just direct access. That directness is what makes the clipboard a building block for desktop-grade features, not just an afterthought. If you need clipboard monitoring (reacting to changes from other apps), a community plugin fills that gap, but the three use cases here cover the vast majority of what desktop applications need.