Original
Copy Button
Copies a value to the clipboard and confirms with a tick.
npx shadcn@latest add @doffy/asciiInstallation
npx shadcn@latest add @doffy/copy-buttonOr, without the registry in components.json:
npx shadcn@latest add https://ui.doffy.com/r/copy-button.jsonUsage
import { CopyButton } from "@/components/ui/copy-button"
const command = "npx shadcn@latest add @doffy/ascii"
export default function CopyButtonDemo() {
return (
<div className="flex items-center gap-3 border bg-background py-1 pr-1 pl-3 text-xs">
<code>{command}</code>
<CopyButton value={command} />
</div>
)
}
Source
ShowHide copy-button.tsx
"use client"
import * as React from "react"
import { CheckIcon, CopyIcon } from "@phosphor-icons/react"
import { cn } from "cn"
import { Button } from "@/registry/doffy/ui/button"
type CopyButtonProps = React.ComponentProps<typeof Button> & {
/** Text written to the clipboard. */
value: string
}
function CopyButton({
value,
variant = "ghost",
size = "icon-sm",
className,
onClick,
...props
}: CopyButtonProps) {
const [copied, setCopied] = React.useState(false)
React.useEffect(() => {
if (!copied) return
const timeout = setTimeout(() => setCopied(false), 1500)
return () => clearTimeout(timeout)
}, [copied])
return (
<Button
data-slot="copy-button"
data-copied={copied}
variant={variant}
size={size}
aria-label={copied ? "Copied" : "Copy to clipboard"}
className={cn(className)}
onClick={async (event) => {
onClick?.(event)
try {
await navigator.clipboard.writeText(value)
setCopied(true)
} catch {
// Clipboard access can be denied (insecure context, permissions); stay silent.
}
}}
{...props}
>
{copied ? <CheckIcon /> : <CopyIcon />}
</Button>
)
}
export { CopyButton }
export type { CopyButtonProps }