Clipboard
clipboard ui Copy-to-clipboard button with success/error feedback. Swaps the icon to a check on success, shows a tooltip with configurable feedback text, supports a visible label, disabled state, a custom timeout for feedback reset, and copy/success/error events. Includes a legacy execCommand fallback for non-secure contexts. Composes the tooltip primitive.
Also available for Vue ->Installation
$ pnpm dlx shadcn@latest add https://uipkge.dev/r/react/clipboard.json $ npx shadcn@latest add https://uipkge.dev/r/react/clipboard.json $ yarn dlx shadcn@latest add https://uipkge.dev/r/react/clipboard.json $ bunx shadcn@latest add https://uipkge.dev/r/react/clipboard.json npx shadcn@latest add @uipkge-react/clipboard Installs to: components/ui/clipboard/ Examples
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
text Text to copy to the clipboard. | string | — | optional |
label Optional visible label next to the icon. | string | — | optional |
disabled Disable the button (no copy, no tooltip). | boolean | — | optional |
hideIcon Hide the copy icon (useful when a label is shown). | boolean | — | optional |
tooltip Tooltip text shown on hover before copying. | string | — | optional |
successText Feedback text shown after a successful copy. | string | — | optional |
errorText Feedback text shown after a failed copy. | string | — | optional |
timeout How long (ms) the success/error feedback stays before resetting. | number | — | optional |
feedbackTooltip Show the feedback as a tooltip rather than swapping the icon. | boolean | — | optional |
onCopy Fired before the copy attempt with the text being copied. | (text: string) => void | — | optional |
onSuccess Fired after a successful copy. | (text: string) => void | — | optional |
onError Fired after a failed copy. | (error: Error) => void | — | optional |
children Render-prop children receiving the current state, or static nodes. | React.ReactNode | ((state: ClipboardState) => React.ReactNode) | — | optional |
npm dependencies
Includes
Files installed (2)
-
components/ui/clipboard/Clipboard.tsx 5.1 kB
'use client' import * as React from 'react' import { Check, Copy } from 'lucide-react' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' import { cn } from '@/lib/utils' export type ClipboardState = 'idle' | 'success' | 'error' export interface ClipboardProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> { /** Text to copy to the clipboard. */ text?: string /** Optional visible label next to the icon. */ label?: string /** Disable the button (no copy, no tooltip). */ disabled?: boolean /** Hide the copy icon (useful when a label is shown). */ hideIcon?: boolean /** Tooltip text shown on hover before copying. */ tooltip?: string /** Feedback text shown after a successful copy. */ successText?: string /** Feedback text shown after a failed copy. */ errorText?: string /** How long (ms) the success/error feedback stays before resetting. */ timeout?: number /** Show the feedback as a tooltip rather than swapping the icon. */ feedbackTooltip?: boolean /** Fired before the copy attempt with the text being copied. */ onCopy?: (text: string) => void /** Fired after a successful copy. */ onSuccess?: (text: string) => void /** Fired after a failed copy. */ onError?: (error: Error) => void /** Render-prop children receiving the current state, or static nodes. */ children?: React.ReactNode | ((state: ClipboardState) => React.ReactNode) } const Clipboard = React.forwardRef<HTMLButtonElement, ClipboardProps>( ( { text = '', label = '', disabled = false, hideIcon = false, tooltip = 'Copy', successText = 'Copied!', errorText = 'Failed', timeout = 2000, feedbackTooltip = true, onCopy, onSuccess, onError, className, children, onClick, ...props }, ref, ) => { const [state, setState] = React.useState<ClipboardState>('idle') const resetTimer = React.useRef<ReturnType<typeof setTimeout> | null>(null) const setFeedback = React.useCallback( (nextState: ClipboardState) => { setState(nextState) if (resetTimer.current) clearTimeout(resetTimer.current) resetTimer.current = setTimeout(() => { setState('idle') }, timeout) }, [timeout], ) // Match the Vue onBeforeUnmount fix: clear any pending reset timer on unmount. React.useEffect(() => { return () => { if (resetTimer.current) clearTimeout(resetTimer.current) } }, []) const copy = React.useCallback( async (e: React.MouseEvent<HTMLButtonElement>) => { if (disabled) return onClick?.(e) const value = text onCopy?.(value) try { if (navigator.clipboard?.writeText) { const write = navigator.clipboard.writeText(value) setFeedback('success') await write } else { // Legacy fallback for non-secure contexts. const ta = document.createElement('textarea') ta.value = value ta.style.position = 'fixed' ta.style.opacity = '0' document.body.appendChild(ta) ta.select() document.execCommand('copy') document.body.removeChild(ta) setFeedback('success') } onSuccess?.(value) } catch (err) { setFeedback('error') onError?.(err as Error) } }, [disabled, text, onCopy, onSuccess, onError, onClick, setFeedback], ) const currentTooltip = state === 'success' ? successText : state === 'error' ? errorText : tooltip return ( <TooltipProvider delayDuration={300}> <Tooltip> <TooltipTrigger asChild> <button type="button" ref={ref} data-uipkge="" data-slot="clipboard" data-feedback-state={state} disabled={disabled} aria-label={currentTooltip} onClick={copy} className={cn( 'inline-flex items-center gap-1.5 rounded-md text-sm transition-colors', 'text-muted-foreground hover:text-foreground', 'focus-visible:ring-ring/50 outline-none focus-visible:ring-[3px]', 'disabled:cursor-not-allowed disabled:opacity-50', className, )} {...props} > {!hideIcon && ( <span data-slot="clipboard-icon" className="inline-flex"> {state === 'success' ? <Check className="size-4 text-emerald-500" /> : <Copy className="size-4" />} </span> )} {label && <span data-slot="clipboard-label">{label}</span>} {typeof children === 'function' ? children(state) : children} </button> </TooltipTrigger> {(feedbackTooltip || state === 'idle') && <TooltipContent>{currentTooltip}</TooltipContent>} </Tooltip> </TooltipProvider> ) }, ) Clipboard.displayName = 'Clipboard' export { Clipboard } -
components/ui/clipboard/index.ts 0.1 kB
export { Clipboard, type ClipboardProps, type ClipboardState } from './Clipboard'
Raw manifest: https://uipkge.dev/r/react/clipboard.json