Skip to content

Clipboard

Shared Components

Reusable copy action with success, failure, and pending states.

Editable invitationPrimary command actionDisabled copyFailure feedback
Open preview

Responsive viewport — resize the window to explore. Direction and density apply only to this preview. Theme applies to the docs too.

Usage

Pass the exact text to copy and a clear action label. The native Clipboard API requires a secure context and browser permission. A failed copy shows actionable feedback rather than claiming success. Feedback clears after 2.5 seconds and does not apply to a different value. The pending state prevents duplicate clicks. This component copies text; it never cuts or deletes source content.

Import
import { Clipboard } from "@/components/ui/clipboard";

Shipped as editable source in src/components/ui/clipboard.tsx in both apps. Copy the implementation below when moving it to another project. Copy the complete example from Source code for all imports and state. Add "use client" when using hooks or event handlers. See installation and theming for project setup.

Component source

The complete implementation is included in both apps. It uses the React, Radix, Lucide, and shared-core dependencies already installed in Apex. Keep your project’s semantic theme tokens when reusing it.

src/components/ui/clipboard.tsx
"use client";
import { useEffect, useRef, useState } from "react";
import { Check, Copy } from "lucide-react";
import { Button } from "@dashboardpack/core/components/ui/button";
export interface ClipboardProps {
  value: string;
  label?: string;
  variant?: "default" | "outline" | "ghost";
  disabled?: boolean;
  onCopied?: (value: string) => void;
  className?: string;
}
export function Clipboard({
  value,
  label = "Copy value",
  variant = "outline",
  disabled,
  onCopied,
  className,
}: ClipboardProps) {
  const [feedback, setFeedback] = useState<{
    value: string;
    state: "copied" | "error";
  } | null>(null);
  const [pending, setPending] = useState(false);
  const mounted = useRef(false);
  useEffect(() => {
    mounted.current = true;
    return () => {
      mounted.current = false;
    };
  }, []);
  useEffect(() => {
    if (!feedback) return;
    const timer = setTimeout(() => setFeedback(null), 2500);
    return () => clearTimeout(timer);
  }, [feedback]);
  const state = feedback?.value === value ? feedback.state : null;
  const copy = async () => {
    setPending(true);
    try {
      await navigator.clipboard.writeText(value);
      if (mounted.current) {
        setFeedback({ value, state: "copied" });
        onCopied?.(value);
      }
    } catch {
      if (mounted.current) setFeedback({ value, state: "error" });
    } finally {
      if (mounted.current) setPending(false);
    }
  };
  return (
    <span className={className}>
      <Button
        type="button"
        variant={variant}
        disabled={disabled || pending}
        onClick={copy}
      >
        {state === "copied" ? (
          <Check className="size-4" />
        ) : (
          <Copy className="size-4" />
        )}
        {label}
      </Button>
      <span role="status" className="ms-2 text-xs text-muted-foreground">
        {state === "copied"
          ? "Copied"
          : state === "error"
            ? "Copy failed. Try again or select the text manually."
            : ""}
      </span>
    </span>
  );
}

API reference

Common props and defaults. Slot-specific props are named below; native elements also accept their standard HTML attributes. The component TypeScript definitions are the complete type reference.

Clipboard props
PropType / DefaultDescription
valuestring

Default: required

Exact string passed to navigator.clipboard.writeText.
labelstring

Default: Copy value

Visible action label.
variantdefault | outline | ghost

Default: outline

Button visual appearance.
disabledboolean

Default: false

Prevent copying while unavailable.
onCopied(value: string) => void

Default: —

Called only after the clipboard write succeeds.
classNamestring

Default: —

Extend the component container with theme-aware utility classes.

Build with these too