Copy Button
ericts-ui/copy-buttonFreeComponent
A shadcn Button-based clipboard control with animated copy and success states.
Live preview
live · rendered by the registry
Rendered by ericts-ui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://ui.ericts.com/r/copy-button.jsonSource
1"use client";23import * as React from "react";4import { AnimatePresence, motion, useReducedMotion } from "motion/react";5import { Check, Copy } from "lucide-react";67import { Button } from "@/components/ui/button";89const variants = {10 hidden: { opacity: 0, scale: 0.5 },11 visible: { opacity: 1, scale: 1 },12};1314type ButtonProps = React.ComponentPropsWithoutRef<typeof Button>;15type ButtonClickEvent = Parameters<NonNullable<ButtonProps["onClick"]>>[0];1617export type CopyButtonProps = Omit<18 ButtonProps,19 "children" | "value" | "onCopy"20> & {21 /** Text written to the clipboard when the button is pressed. */22 value: string;23 /** How long, in ms, the copied state is shown before reverting. */24 timeout?: number;25 /** Called with the copied value after a successful write. */26 onCopy?: (value: string) => void;27};2829export function CopyButton({30 value,31 timeout = 1000,32 onCopy,33 onClick,34 className,35 variant = "outline",36 size = "icon",37 type = "button",38 "aria-label": ariaLabel = "Copy to clipboard",39 ...props40}: CopyButtonProps) {41 const [copied, setCopied] = React.useState(false);42 const shouldReduceMotion = useReducedMotion();43 const timer = React.useRef<ReturnType<typeof setTimeout> | null>(null);4445 React.useEffect(() => {46 return () => {47 if (timer.current) clearTimeout(timer.current);48 };49 }, []);5051 const handleCopy = React.useCallback(52 async (event: ButtonClickEvent) => {53 onClick?.(event);5455 if (event.defaultPrevented) return;5657 try {58 await navigator.clipboard.writeText(value);59 } catch {60 // Clipboard access can be denied (insecure context, no permission) —61 // bail out without flipping into the copied state.62 return;63 }6465 onCopy?.(value);66 setCopied(true);6768 if (timer.current) clearTimeout(timer.current);69 timer.current = setTimeout(() => setCopied(false), timeout);70 },71 [onClick, onCopy, timeout, value],72 );7374 // An icon swap is a tiny state change, so keep it snappy: ease-out, well75 // under 150ms. Motion is removed entirely under prefers-reduced-motion.76 const transition = shouldReduceMotion77 ? { duration: 0 }78 : ({ duration: 0.13, ease: [0.215, 0.61, 0.355, 1] } as const);7980 return (81 <Button82 type={type}83 variant={variant}84 size={size}85 aria-label={ariaLabel}86 data-copied={copied}87 onClick={handleCopy}88 className={className}89 {...props}90 >91// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from ericts-ui
All 33 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Adaptive Draweradaptive-drawer | ericts-ui | Components | Free | 2 deps | |
| Check Animationcheck-animation | ericts-ui | Components | Free | no deps · 2 files | |
| Context Cursorcontext-cursor | ericts-ui | Components | Free | 1 dep | |
| Expandable Dialogexpandable-modal | ericts-ui | Components | Free | 1 dep | |
| Expandable Tabsexpandable-tabs | ericts-ui | Components | Free | 1 dep | |
| Expandable Toolbarexpandable-toolbar | ericts-ui | Components | Free | 1 dep | |
| Expanding Panelexpanding-panel | ericts-ui | Components | Free | 2 deps | |
| Expanding Segmented Tabsexpanding-segmented-tabs | ericts-ui | Components | Free | 1 dep |
