Logo

Copy Button

A versatile copy button component with support for animations and different states, built on top of the Shadcn Button.

Installation

Run the following command to add the copy-button component to your project using the Flow UI registry:

npx shadcn@latest add @flowui/copy-button

Install Dependencies

Ensure you have lucide-react and motion (Framer Motion) installed.

npm install lucide-react motion

Install Button Component

This component is built on top of the Shadcn UI Button. If you haven't installed it yet, run:

npx shadcn@latest add button

Create Animation Wrapper

Create a helper component animation-wrapper.tsx for handling animations.

components/ui/copy-button/animation-wrapper.tsx
import { motion, AnimatePresence } from "motion/react";

type AnimatedLogicProps = {
    children: React.ReactNode;
    mode: "copy" | "copied";
};

const AnimationWrapper = ({ children, mode }: AnimatedLogicProps) => {
    return (
        <AnimatePresence mode="wait">
            <motion.div
                key={mode}
                initial={{ scale: 0.6, opacity: 0 }}
                animate={{ scale: 1, opacity: 1 }}
                exit={{ scale: 0.6, opacity: 0 }}
            >
                {children}
            </motion.div>
        </AnimatePresence>
    )
}

export default AnimationWrapper;

Create Copy Button

Create the main component copy-button.tsx.

components/ui/copy-button/copy-button.tsx
"use client";

import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { Check, Copy } from "lucide-react";
import { useState } from "react";
import AnimationWrapper from "./animation-wrapper";

type AnimationWrapperProps = {
  children: React.ReactNode;
  mode: "copy" | "copied";
};

export type CopyButtonProps = {
    animated?: boolean;
    time?: number;
    iconType?: "default" | "rotated";
} & React.ComponentProps<typeof Button>;


export const CopyButton = ({
    animated = false,
    className,
    children,
    time = 3000,
    iconType = "default",
    ...props
}: CopyButtonProps) => {
    // Fixed Logic for any behavior
    const [mode, setMode] = useState<"copy" | "copied">("copy");

    const handleCopy = (e: React.MouseEvent<HTMLButtonElement>) => {
        if (props.onClick) {
            props.onClick(e);
        }

        // Change state to copied
        setMode("copied");
        
        // Change to the base state after user timer - default 3s
        const timer = setTimeout(() => {
            setMode("copy");
        }, time);
        
        // Clear the timer
        return () => clearTimeout(timer);
    }

    // Identify the icon to be used and create it's node

    const Icon = mode === "copy" ? Copy : Check;

    const iconNode = (
        <Icon
            className={cn(
                "size-4 shrink-0",
                iconType === "rotated" && mode === "copy" && "rotate-90"
            )}
        />
    );

    const content = animated ? (
        <AnimationWrapper mode={mode}>
            {iconNode}
        </AnimationWrapper>
    ) : (
        iconNode
    )

    return (
        <Button
            {...props}
            onClick={handleCopy}
            className={cn("relative", className)}
        >
            {content}
        </Button>
    )
}

Create Animated Wrapper (Optional)

For convenience, you can create animated-copy-button.tsx to pre-configure the animated version.

components/ui/copy-button/animated-copy-button.tsx
import { CopyButton, CopyButtonProps } from "./copy-button"

export const AnimatedCopyButton = (props: CopyButtonProps) => {
    return (
        <CopyButton
            {...props}
            animated={true}
        />
    )
}

Usage

Basic Usage

import { CopyButton } from "@/components/ui/copy-button/copy-button";

export function BasicExample() {
  return (
    <div className="flex items-center gap-4">
      <CopyButton variant="outline">
        Click to Copy
      </CopyButton>
      <CopyButton variant="default" className="rounded-full">
        Rounded Copy
      </CopyButton>
    </div>
  )
}

Animated Usage

import { AnimatedCopyButton } from "@/components/ui/copy-button/animated-copy-button";

export function AnimatedExample() {
  return (
    <div className="flex items-center gap-4">
      <AnimatedCopyButton variant="outline">
        Animated Copy
      </AnimatedCopyButton>
      <AnimatedCopyButton variant="secondary" iconType="rotated">
        Rotated Icon
      </AnimatedCopyButton>
    </div>
  )
}

Props

The CopyButton component accepts all props from the standard Shadcn Button component, plus the following:

PropTypeDefaultDescription
animatedbooleanfalseWhether to animate the icon transition between copy and check states.
timenumber3000The duration in milliseconds before the icon reverts to the copy state.
iconType"default" | "rotated""default"The style of the copy icon. "rotated" adds a rotation effect in the default state.

Note

The CopyButton does not automatically handle the actual clipboard copying logic (e.g., navigator.clipboard.writeText). You should implement the copy logic within the onClick handler, which is passed through to the underlying Button.

On this page