Refined UI

An opinionated, customizable superset of shadcn/ui

AgentsPrimitivesSkillsSkins
Guides
  • Overview
  • Get started
  • Shadcn compatibility
  • Architecture
  • Agent surface
Primitives
  • Accordion
  • Alert
  • Alert dialog
  • Aspect ratio
  • Autocomplete
  • Avatar
  • Badge
  • Button
  • Button group
  • Calendar
  • Card
  • Checkbox
  • Checkbox Group
  • Code
  • Code Diff
  • Collapsible
  • Color Picker
  • Combobox
  • Command
  • Date Picker
  • Dialog
  • Drawer
  • Empty
  • Field
  • Form
  • Gradient Editor
  • Hover card
  • Input
  • Input group
  • Input OTP
  • Item
  • Kbd
  • Markdown
  • Menu
  • Menubar
  • Meter
  • Native select
  • Navigation menu
  • Number Field
  • Pagination
  • Popover
  • Progress
  • Radio group
  • Resizable
  • Scroll area
  • Select
  • Sidebar
  • Skeleton
  • Slider
  • Spinner
  • Switch
  • Table
  • Table of contents
  • Tabs
  • Textarea
  • Toast
  • Toggle
  • Toolbar
  • Tooltip
  • Tree
  • Trigger Menu
  • Typography
Hooks
  • Use Chat Message
  • Use Chat Input
  • Use User Ask
  • Use Audio Recorder
  • Use Environment Variables
  • Use Copy To Clipboard
  • Use Tool Call
  • Use Sidebar Resize
Utils
  • cn
  • Skin
  • Contracts
  • Serialize
Extensions
  • Control Effects
  • View Transition
  • Send Aurora
  1. Reference
  2. useCopyToClipboard
Hooks

useCopyToClipboard

Copy-to-clipboard behavior with success state and fallback support.

Installed to components/refined-ui/hooks/use-copy-to-clipboard.ts with copy-enabled components. It is local UI behavior — yours to own and edit.

Used by ActionBarUsed by MarkdownBlockUsed by CodeBlockEditor

Source

Installed hook source

Behavior hook
src/registry/hooks/use-copy-to-clipboard.ts
import { useEffect, useRef, useState } from "react";
​
export type UseCopyToClipboardOptions = {
text?: string;
copiedDuration?: number;
onCopy?: (value: string) => void;
onCopyError?: (error: unknown) => void;
};
​
export type UseCopyToClipboardResult = {
isCopied: boolean;
copyToClipboard: (value: string) => Promise<boolean>;
resetCopied: () => void;
};
​
export type UseCopyToClipboardConfiguredResult = UseCopyToClipboardResult & {
handleCopy: () => Promise<boolean>;
};
​
async function copyViaClipboardApi(value: string) {
if (typeof navigator === "undefined" || !navigator.clipboard?.writeText) return false;
​
try {
await navigator.clipboard.writeText(value);
return true;
} catch {
return false;
}
}
​
function copyViaSelection(value: string) {
if (typeof document === "undefined" || typeof document.execCommand !== "function" || !document.body) return false;
​
const activeElement = document.activeElement instanceof HTMLElement ? document.activeElement : null;
const textarea = document.createElement("textarea");
​
textarea.value = value;
textarea.setAttribute("readonly", "");
textarea.setAttribute("aria-hidden", "true");
textarea.style.position = "fixed";
textarea.style.top = "0";
textarea.style.left = "0";
textarea.style.width = "1px";
textarea.style.height = "1px";
textarea.style.opacity = "0";
textarea.style.pointerEvents = "none";
​
document.body.appendChild(textarea);
​
try {
textarea.focus();
textarea.select();
textarea.setSelectionRange(0, value.length);
return document.execCommand("copy");
} catch {
return false;
} finally {
textarea.remove();
activeElement?.focus({ preventScroll: true });
}
}
​
export async function copyTextToClipboard(value: string) {
if (!value) return false;
if (await copyViaClipboardApi(value)) return true;
return copyViaSelection(value);
}
​
export function useCopyToClipboard(options: UseCopyToClipboardOptions & { text: string }): UseCopyToClipboardConfiguredResult;
export function useCopyToClipboard(options?: UseCopyToClipboardOptions): UseCopyToClipboardResult;
export function useCopyToClipboard({ text, copiedDuration = 1200, onCopy, onCopyError }: UseCopyToClipboardOptions = {}) {
const [isCopied, setIsCopied] = useState(false);
const resetTimeout = useRef<number | null>(null);
​
useEffect(() => {
const timeoutRef = resetTimeout;
​
return () => {
if (timeoutRef.current) window.clearTimeout(timeoutRef.current);
};
}, []);
​
function resetCopied() {
if (resetTimeout.current) window.clearTimeout(resetTimeout.current);
resetTimeout.current = null;
setIsCopied(false);
}
​
async function copyToClipboard(value: string) {
const copied = await copyTextToClipboard(value);
if (!copied) {
const error = new Error("Unable to copy text");
onCopyError?.(error);
setIsCopied(false);
return false;
}
​
try {
onCopy?.(value);
} catch (error) {
onCopyError?.(error);
setIsCopied(false);
return false;
}
​
setIsCopied(true);
if (resetTimeout.current) window.clearTimeout(resetTimeout.current);
resetTimeout.current = window.setTimeout(() => setIsCopied(false), copiedDuration);
return true;
}
​
function handleCopy() {
return text ? copyToClipboard(text) : Promise.resolve(false);
}
​
return text === undefined ? { isCopied, copyToClipboard, resetCopied } : { isCopied, copyToClipboard, handleCopy, resetCopied };
}
​

On this page

  • Installation
  • Source