shadcn.io is not affiliated with official shadcn/ui
Shadcn Clipboard for React and Tailwind
Copy text to the clipboard.
Installation
bunx --bun shadcn@latest add https://kit.dev/r/clipboard.jsonpnpm dlx shadcn@latest add https://kit.dev/r/clipboard.jsonnpx shadcn@latest add https://kit.dev/r/clipboard.jsonyarn shadcn@latest add https://kit.dev/r/clipboard.jsonThis component depends on Input. Install it first if you haven't already.
Install the following dependencies:
bun add @ark-ui/react lucide-react tailwind-variantspnpm add @ark-ui/react lucide-react tailwind-variantsnpm install @ark-ui/react lucide-react tailwind-variantsyarn add @ark-ui/react lucide-react tailwind-variantsCopy and paste the following code into your project.
"use client";
import {
Clipboard as ArkClipboard,
useClipboard as useArkClipboard,
useClipboardContext as useArkClipboardContext,
} from "@ark-ui/react/clipboard";
import { CheckIcon, ClipboardIcon } from "lucide-react";
import type React from "react";
import { tv, type VariantProps } from "tailwind-variants";
import { cn } from "@/lib/utils";
import { inputVariants } from "@/components/ui/input";
export const useClipboard = useArkClipboard;
export const useClipboardContext = useArkClipboardContext;
export const ClipboardContext = ArkClipboard.Context;
const clipboardControlClassName = "flex flex-wrap items-center gap-2";
interface ClipboardProps
extends React.ComponentProps<typeof ArkClipboard.Root> {
/**
* Class names on the root. `className` is applied to the baked control.
*/
rootClassName?: string;
}
export const Clipboard = (props: ClipboardProps) => {
const { rootClassName, className, children, ...rest } = props;
return (
<ArkClipboard.Root
className={cn(rootClassName)}
data-slot="clipboard"
{...rest}
>
<ArkClipboard.Control
className={cn(clipboardControlClassName, className)}
data-slot="clipboard-control"
>
{children}
</ArkClipboard.Control>
</ArkClipboard.Root>
);
};
export const ClipboardRootProvider = (
props: React.ComponentProps<typeof ArkClipboard.RootProvider> & {
/**
* Class names on the root. `className` is applied to the baked control.
*/
rootClassName?: string;
}
) => {
const { rootClassName, className, children, ...rest } = props;
return (
<ArkClipboard.RootProvider
className={cn(rootClassName)}
data-slot="clipboard"
{...rest}
>
<ArkClipboard.Control
className={cn(clipboardControlClassName, className)}
data-slot="clipboard-control"
>
{children}
</ArkClipboard.Control>
</ArkClipboard.RootProvider>
);
};
export const ClipboardLabel = (
props: React.ComponentProps<typeof ArkClipboard.Label>
) => {
const { className, ...rest } = props;
return (
<ArkClipboard.Label
className={cn("w-full font-medium text-sm", className)}
data-slot="clipboard-label"
{...rest}
/>
);
};
export const ClipboardTrigger = (
props: React.ComponentProps<typeof ArkClipboard.Trigger>
) => <ArkClipboard.Trigger data-slot="clipboard-trigger" {...props} />;
export const ClipboardInput = (
props: React.ComponentProps<typeof ArkClipboard.Input>
) => {
const { className, ...rest } = props;
return (
<ArkClipboard.Input
className={cn(inputVariants(), className)}
data-slot="clipboard-input"
{...rest}
/>
);
};
export const clipboardValueVariants = tv({
base: [
"inline-flex items-center",
"px-3",
"bg-transparent dark:bg-input/30",
"text-base md:text-sm",
"rounded-lg border border-input shadow-sm/5",
],
defaultVariants: {
size: "md",
},
variants: {
// biome-ignore assist/source/useSortedKeys: Input-matching size scale
size: {
xs: "h-6",
sm: "h-7",
md: "h-8",
lg: "h-9",
xl: "h-10",
},
},
});
interface ClipboardValueProps
extends React.ComponentProps<typeof ArkClipboard.ValueText>,
VariantProps<typeof clipboardValueVariants> {}
export const ClipboardValue = (props: ClipboardValueProps) => {
const { size, className, ...rest } = props;
return (
<ArkClipboard.ValueText
className={cn(clipboardValueVariants({ size }), className)}
data-slot="clipboard-value"
{...rest}
/>
);
};
export const ClipboardIndicator = (
props: React.ComponentProps<typeof ArkClipboard.Indicator>
) => {
const {
copied = <CheckIcon aria-hidden="true" />,
className,
children,
...rest
} = props;
return (
<ArkClipboard.Indicator
className={cn("pointer-events-none", className)}
copied={copied}
data-slot="clipboard-indicator"
{...rest}
>
{children ?? <ClipboardIcon aria-hidden="true" />}
</ArkClipboard.Indicator>
);
};Update the import paths to match your project setup.
Anatomy
Clipboard
├── ClipboardControl
├── ClipboardTrigger
├── ClipboardIndicator
├── ClipboardInput
└── ClipboardLabelUsage
import { SITE_URL } from "@/lib/brand";
import { Button } from "@/components/ui/button";
import {
Clipboard,
ClipboardIndicator,
ClipboardInput,
ClipboardTrigger,
} from "@/components/ui/clipboard";<div className="w-full max-w-sm">
<Clipboard value={SITE_URL}>
<ClipboardInput />
<ClipboardTrigger asChild>
<Button size="icon-md">
<ClipboardIndicator />
</Button>
</ClipboardTrigger>
</Clipboard>
</div>Accessibility
| Key | Description |
|---|---|
Enter | Activate the focused control. |
Space | Activate the focused control. |
Demos
Context
Controlled
Copy Status
Custom Timeout
Different Icon
Icon Only
Root Provider
Value Text
With Label
API Reference
Clipboard
div
PropType
AttributeType
ClipboardControl
div
PropType
ClipboardTrigger
button
PropType
AttributeType
ClipboardIndicator
div
PropType
AttributeType
ClipboardInput
input
PropType
AttributeType
ClipboardLabel
label
PropType
AttributeType
ClipboardValue
span
PropType
AttributeType
ClipboardRootProvider
div
PropType
AttributeType
useClipboard
PropType
ClipboardContext
PropType