Skip to content

Instantly share code, notes, and snippets.

@benhodgson87
Last active July 28, 2025 08:27
Show Gist options
  • Save benhodgson87/764445d66cdc0d0d1ccb243494bbcd01 to your computer and use it in GitHub Desktop.
Save benhodgson87/764445d66cdc0d0d1ccb243494bbcd01 to your computer and use it in GitHub Desktop.
A shadcn/ui component to copy text to the clipboard

Click to Copy

This <CopyButton /> component allows you to copy text to the clipboard on click.

There are two main ways to use the <CopyButton /> component:

  1. Icon-only button
  2. Text + icon button

The main difference between the two is that the icon-only button is a standalone button, while the text + icon button is a button that wraps around the text you want to copy, letting you click either the text or the icon to copy the text.

Basic Usage

Icon-Only Button

import { CopyButton } from "@/components/CopyButton";

// Simple copy button with icon only
<CopyButton value="Copy this text" />

// With custom tooltip
<CopyButton
  value="Copy this text"
  label="Copy text to clipboard"
/>

// With custom tooltip position
<CopyButton
  value="Copy this text"
  tooltipPosition="bottom"
/>

Text + Icon Button

import { CopyButton } from "@/components/CopyButton";

// Text with copy functionality
<CopyButton value="Copy this text">
  <p className="text-lg">Clicking this text will copy "Copy this text"</p>
</CopyButton>

Props

Prop Type Description
value string The text to copy to clipboard
label string Tooltip text on hover
children React.ReactNode Content to render with the copy icon (optional)
tooltipPosition "top" | "right" | "bottom" | "left" Tooltip position (optional, defaults: top for icon-only, right for text + icon)
"use client";
import { useState } from "react";
import { Copy, Check } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
type CopyButtonProps = {
value: string;
label?: string;
tooltipPosition?: "top" | "right" | "bottom" | "left";
className?: string;
children?: React.ReactNode;
}
const SHOW_SUCCESS_TIMEOUT = 2000;
export function CopyButton({
value,
label = "Copy to Clipboard",
tooltipPosition,
className,
children,
}: CopyButtonProps) {
const [hasCopied, setHasCopied] = useState(false);
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(value);
setHasCopied(true);
setTimeout(() => setHasCopied(false), SHOW_SUCCESS_TIMEOUT);
} catch (err) {
console.error("Failed to copy to clipboard:", err);
}
};
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size={children ? "default" : "sm"}
onClick={handleCopy}
aria-label={t(label)}
className={
children
? `group flex items-center gap-2 p-0 h-auto bg-transparent hover:bg-transparent text-foreground hover:text-foreground font-normal cursor-pointer ${
className || ""
}`
: `px-2 transition-colors duration-300 ${
hasCopied
? "bg-green-300 dark:bg-green-700 hover:bg-green-600 dark:hover:bg-green-500"
: ""
} ${className || ""}`
}
>
{children ? children : null}
<span
className={
children
? `inline-flex items-center justify-center w-8 h-8 rounded transition-colors duration-300 ${
hasCopied
? "bg-green-300 dark:bg-green-700"
: "group-hover:bg-accent group-hover:text-accent-foreground"
}`
: ""
}
>
{hasCopied ? (
<Check className="h-4 w-4 text-green-600 dark:text-green-200 flex-shrink-0 transition-colors duration-300" />
) : (
<Copy className="h-4 w-4 flex-shrink-0 transition-colors duration-300" />
)}
</span>
</Button>
</TooltipTrigger>
<TooltipContent side={tooltipPosition || (children ? "right" : "top")}>
<p>{label}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment