Command
Display shell commands with one-click copy. Perfect for installation snippets and quick-start guides.
$
pnpm dlx nachui list1import { Command } from '@/components/ui/command';23export function Default() {4 return <Command command="pnpm dlx nachui list" />;5}
Installation
Copy and paste the following code into your project.
1'use client';23import { AnimatePresence, motion, useReducedMotion } from 'motion/react';4import * as React from 'react';5import { cn } from '../lib/cn';67type IconProps = React.SVGProps<SVGSVGElement> & {8 size?: number | string;9};1011function CheckCircleIcon({ size = 24, strokeWidth = 1.5, ...props }: IconProps) {12 return (13 <svg14 xmlns="http://www.w3.org/2000/svg"15 width={size}16 height={size}17 viewBox="0 0 24 24"18 fill="none"19 stroke="currentColor"20 strokeWidth={strokeWidth}21 strokeLinecap="round"22 strokeLinejoin="round"23 aria-hidden="true"24 {...props}25 >26 <circle cx="12" cy="12" r="9.5" />27 <path d="m8 12 2.7 2.7L16 9.3" />28 </svg>29 );30}3132function CopyIcon({ size = 24, strokeWidth = 1.5, ...props }: IconProps) {33 return (34 <svg35 xmlns="http://www.w3.org/2000/svg"36 width={size}37 height={size}38 viewBox="0 0 24 24"39 fill="none"40 stroke="currentColor"41 strokeWidth={strokeWidth}42 strokeLinecap="round"43 strokeLinejoin="round"44 aria-hidden="true"45 {...props}46 >47 <rect x="8.5" y="8.5" width="12.5" height="12.5" rx="3" />48 <path d="M5.5 15.5h-.5a2.5 2.5 0 0 1-2.5-2.5V5.5A2.5 2.5 0 0 1 5 3h7.5A2.5 2.5 0 0 1 15 5.5V6" />49 </svg>50 );51}5253const ICON_VARIANTS = {54 initial: { opacity: 0, scale: 0.6, rotate: -10 },55 animate: { opacity: 1, scale: 1, rotate: 0 },56 exit: { opacity: 0, scale: 0.6, rotate: 10 },57} as const;5859const ICON_TRANSITION = { duration: 0.18, ease: 'easeOut' } as const;60const REDUCED_MOTION_PROPS = {61 initial: { opacity: 0 },62 animate: { opacity: 1 },63 exit: { opacity: 0 },64 transition: { duration: 0.12 },65} as const;6667export interface CommandLabels {68 copy: string;69 copyHint: string;70 copied: string;71}7273const DEFAULT_LABELS: CommandLabels = {74 copy: 'Copy command',75 copyHint: 'Copy to clipboard',76 copied: 'Copied!',77};7879export interface CommandProps extends React.HTMLAttributes<HTMLDivElement> {80 command: string;81 prefix?: string;82 resetDelay?: number;83 onCopied?: () => void;84 labels?: Partial<CommandLabels>;85}8687export const Command = ({88 command,89 prefix = '$',90 resetDelay = 2000,91 onCopied,92 labels: labelOverrides,93 className,94 ref,95 ...props96}: CommandProps & { ref?: React.Ref<HTMLDivElement> }) => {97 const labels = { ...DEFAULT_LABELS, ...labelOverrides };98 const [copied, setCopied] = React.useState(false);99 const shouldReduceMotion = useReducedMotion();100 const resetTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);101102 React.useEffect(() => {103 return () => {104 if (resetTimeoutRef.current) clearTimeout(resetTimeoutRef.current);105 };106 }, []);107108 const markCopied = React.useCallback(() => {109 setCopied(true);110 onCopied?.();111 if (resetTimeoutRef.current) clearTimeout(resetTimeoutRef.current);112 resetTimeoutRef.current = setTimeout(() => setCopied(false), resetDelay);113 }, [onCopied, resetDelay]);114115 const handleCopy = React.useCallback(async () => {116 if (copied) return;117 try {118 await navigator.clipboard.writeText(command);119 markCopied();120 } catch {121 const previousFocus =122 document.activeElement instanceof HTMLElement ? document.activeElement : null;123 const textarea = document.createElement('textarea');124 textarea.value = command;125 textarea.setAttribute('readonly', '');126 textarea.style.position = 'fixed';127 textarea.style.opacity = '0';128 document.body.appendChild(textarea);129 textarea.focus();130 textarea.select();131 let succeeded: boolean;132 try {133 succeeded = document.execCommand('copy');134 } catch {135 succeeded = false;136 } finally {137 document.body.removeChild(textarea);138 previousFocus?.focus();139 }140 if (succeeded) markCopied();141 }142 }, [command, copied, markCopied]);143144 return (145 <div146 ref={ref}147 className={cn(148 'text-foreground bg-muted/30 border-border flex w-full items-center gap-3 rounded-md border px-4 py-2.5 font-mono text-sm',149 className,150 )}151 {...props}152 >153 <span className="text-muted-foreground shrink-0 select-none" aria-hidden="true">154 {prefix}155 </span>156157 <code className="min-w-0 flex-1 overflow-x-auto text-sm whitespace-nowrap select-all">158 {command}159 </code>160161 <button162 type="button"163 onClick={handleCopy}164 aria-label={labels.copy}165 title={copied ? labels.copied : labels.copyHint}166 className={cn(167 'text-muted-foreground hover:text-foreground -m-1.5 shrink-0 rounded p-2 transition-colors',168 'focus-visible:ring-ring ring-offset-background focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:outline-none',169 copied && 'text-success hover:text-success',170 )}171 >172 <AnimatePresence mode="wait" initial={false}>173 {copied ? (174 <motion.span175 key="check"176 initial={shouldReduceMotion ? REDUCED_MOTION_PROPS.initial : ICON_VARIANTS.initial}177 animate={shouldReduceMotion ? REDUCED_MOTION_PROPS.animate : ICON_VARIANTS.animate}178 exit={shouldReduceMotion ? REDUCED_MOTION_PROPS.exit : ICON_VARIANTS.exit}179 transition={shouldReduceMotion ? REDUCED_MOTION_PROPS.transition : ICON_TRANSITION}180 className="flex"181 aria-hidden="true"182 >183 <CheckCircleIcon size={16} className="size-4" />184 </motion.span>185 ) : (186 <motion.span187 key="copy"188 initial={shouldReduceMotion ? REDUCED_MOTION_PROPS.initial : ICON_VARIANTS.initial}189 animate={shouldReduceMotion ? REDUCED_MOTION_PROPS.animate : ICON_VARIANTS.animate}190 exit={shouldReduceMotion ? REDUCED_MOTION_PROPS.exit : ICON_VARIANTS.exit}191 transition={shouldReduceMotion ? REDUCED_MOTION_PROPS.transition : ICON_TRANSITION}192 className="flex"193 aria-hidden="true"194 >195 <CopyIcon size={16} className="size-4" />196 </motion.span>197 )}198 </AnimatePresence>199 </button>200 <span role="status" aria-live="polite" className="sr-only">201 {copied ? labels.copied : ''}202 </span>203 </div>204 );205};
Update the import paths to match your project structure.
Anatomy
1import { Command } from '@/components/ui/command';
1<Command command="pnpm dlx nachui add button" />
Examples
Default
$
pnpm dlx nachui list1import { Command } from '@/components/ui/command';23export function Default() {4 return <Command command="pnpm dlx nachui list" />;5}
API Reference
CliCommand
| Prop | Type | Default | Description |
|---|---|---|---|
command | string | โ | The CLI command string to display and copy to clipboard. |
prefix | string | "$" | Symbol displayed before the command (e.g. $, >, %). |
resetDelay | number | 2000 | Milliseconds before the copied state resets back to the copy icon. |
onCopied | () => void | โ | Callback fired after the command is successfully copied. |
labels | Partial<CommandLabels> | โ | Labels: copy names the button ("Copy command"), copyHint is its tooltip ("Copy to clipboard") and copied is announced to screen readers and shown as the tooltip after a copy ("Copied!"). |
className | string | โ | Additional CSS class names applied to the root element. |
Found something to improve?
Notice a bug, typo, or missing detail on this page? Help us make the documentation better by opening a GitHub issue.