UI Elements

Install Command

Copyable shell command in an InputGroup: a $ prompt, the command in Geist Mono and a copy button. If copying fails it selects the text and shows a hint; add the dark class over art.

Free, MIT licenseOpen in Browse
"use client"

import * as React from "react"
import { CheckIcon, CopyIcon } from "@phosphor-icons/react"
import { cn } from "cn"

import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
import {
  InputGroup,
  InputGroupAddon,
  InputGroupButton,
  InputGroupText,
} from "@/components/ui/input-group"

/**
 * A command to copy, such as an install line in a hero or docs page: a `$`
 * prompt, the command in monospace and a copy button, in the stock
 * `InputGroup`. A long command is cut off with an ellipsis.
 *
 * After a copy, the button shows a check for two seconds and a hidden status
 * says "Copied". If copying fails, the command is selected and a message
 * below asks the reader to press ⌘C or Ctrl+C. Over artwork, add
 * `className="dark"`. `className` lands on the `InputGroup`.
 */
function InstallCommand({
  className,
  children,
  ...props
}: Omit<React.ComponentProps<typeof InputGroup>, "children"> & {
  /**
   * The command, as plain text or inline elements such as `span`s for
   * highlighting. The button copies the text as it shows, so block elements
   * would be copied with no space between them.
   */
  children: React.ReactNode
}) {
  const { copied, failed, copyToClipboard } = useCopyToClipboard()
  const snippetRef = React.useRef<HTMLElement>(null)

  // On failure the command is selected, so one keystroke copies it by hand.
  const copy = async () => {
    const code = snippetRef.current
    if (!code) return
    if (!(await copyToClipboard(code.textContent ?? ""))) {
      window.getSelection()?.selectAllChildren(code)
    }
  }

  return (
    <InputGroup
      data-copied={copied || undefined}
      className={cn(
        "h-11 border-border bg-card font-mono text-card-foreground",
        className
      )}
      {...props}
    >
      <InputGroupAddon aria-hidden="true" className="pr-2 pl-4 font-normal">
        $
      </InputGroupAddon>
      <InputGroupText className="min-w-0 flex-1 text-sm/7 text-current">
        <code ref={snippetRef} className="truncate">
          {children}
        </code>
      </InputGroupText>
      <InputGroupAddon align="inline-end">
        <InputGroupButton
          size="icon-sm"
          onClick={copy}
          aria-label={
            copied ? "Copied" : failed ? "Copy failed" : "Copy command"
          }
        >
          {copied ? (
            <CheckIcon aria-hidden="true" />
          ) : (
            <CopyIcon aria-hidden="true" />
          )}
        </InputGroupButton>
      </InputGroupAddon>
      <span
        role="status"
        className={
          failed
            ? "absolute top-full left-0 mt-2 px-4 font-sans text-xs"
            : "sr-only"
        }
      >
        {copied
          ? "Copied"
          : failed
            ? "Couldn't copy. It's selected: press ⌘C or Ctrl+C."
            : ""}
      </span>
    </InputGroup>
  )
}

export { InstallCommand }
export default InstallCommand

Installation

pnpm dlx shadcn add @01orbital/install-command
npx shadcn add @01orbital/install-command
yarn shadcn add @01orbital/install-command
bunx --bun shadcn add @01orbital/install-command

Free under the MIT license. Add the @01orbital registry to components.json once (install guide), then run the command.

It lands at components/code/install-command.tsx, as source you own and edit.

Usage

import { InstallCommand } from "@/components/code/install-command"

// Deep ground, painted with two washes: over art the command takes the dark theme.
const PALETTES = {
  tidepool: {
    ground: "oklch(0.179 0.029 240.3)",
    upper:
      "radial-gradient(ellipse 140% 110% at 50% 45%, oklch(0.413 0.082 166.3) 0%, oklch(0.413 0.082 166.3 / 0.5) 55%, transparent 100%)",
    lower:
      "radial-gradient(ellipse 130% 100% at 50% 55%, oklch(0.32 0.06 250) 0%, oklch(0.32 0.06 250 / 0.5) 55%, transparent 100%)",
  },
} as const

export default function InstallCommandDemo() {
  return (
    <div className="grid w-full max-w-lg gap-6">
      <InstallCommand>
        pnpm dlx shadcn add @01orbital/moon-sphere
      </InstallCommand>
      <div className="relative overflow-hidden rounded-2xl p-8">
        <div
          aria-hidden="true"
          className="absolute inset-0"
          style={{
            backgroundColor: PALETTES.tidepool.ground,
            backgroundImage: `${PALETTES.tidepool.upper}, ${PALETTES.tidepool.lower}`,
          }}
        />
        <InstallCommand className="dark relative">
          pnpm dlx shadcn add @01orbital/moon-sphere
        </InstallCommand>
      </div>
    </div>
  )
}

API reference

Prop

Type

It also accepts the HTML attributes of the element it renders.

Dependencies

npm: @phosphor-icons/react, cn. The CLI installs them for you.

Registry pieces it installs with it:

  • font-geist-mono
  • input-group
  • use-copy-to-clipboard