Skip to content
TextInstall Command
Text

Install CommandFree

An npm / pnpm / yarn / bun command block where only the words that differ change, and every block on the page follows your pick.

Worst case

Install Command with the data that usually breaks a component: long German words, Arabic right to left, odd numbers, empty and broken values. It runs live here, so try it. Set the frame to 320 px to see a small phone.

Loading the worst case…

Also checked with reduced motion turned on, in dark mode, and against stored 320 px screenshots with a pixel diff before every release.

Use Install Command to show a package install command with an npm, pnpm, yarn and bun picker that every block on the page follows.

A command block for docs and READMEs with one tab per package manager and a copy button. Switching tabs animates only the words that change: install rolls out, add rolls in, and the package name glides into place. By default every block on the page shares the choice, follows it in a cascade outward from the block you clicked, and remembers it across visits and browser tabs.

Quick answers

  • Dependencies: react, react-dom and motion 12 or newer. Nothing else.
  • React version: 19 or newer.
  • SSR and 'use client': Renders on the server. The file starts with 'use client', so the Next.js App Router can import it straight into a Server Component. In the Pages Router, Vite, React Router and Astro that line does nothing. See Troubleshooting.
  • Licence: MIT. Use it in any project, commercial or not.
  • Paid access: None. No account and no key.

Installation

shadcn

npx shadcn@latest add https://brioui.com/r/install-command.json

Manual

  1. Copy install-command.tsx into your project, for example components/brio/install-command.tsx.
  2. Install the peer dependency: npm install motion.
  3. Requires React 19. The theme tokens (--ui-*) are optional; the component falls back to its own defaults.

Usage

TSX
import { InstallCommand } from '@/components/brio/install-command'

export function QuickStart() {
  return <InstallCommand add="motion" />
}

This renders npm install motion, pnpm add motion, yarn add motion and bun add motion.

Examples

Presets

Each preset maps to the right command for every manager. Use one preset per block.

TSX
import { InstallCommand } from '@/components/brio/install-command'

export function Setup() {
  return (
    <>
      {/* npm install -D typescript @types/react / pnpm add -D ... */}
      <InstallCommand add={['typescript', '@types/react']} dev />

      {/* npx shadcn@latest add button / pnpm dlx ... / yarn dlx ... / bunx ... */}
      <InstallCommand exec="shadcn@latest add button" />

      {/* npm create vite@latest my-app / pnpm create ... */}
      <InstallCommand create="vite@latest my-app" />

      {/* npm run dev / pnpm dev / yarn dev / bun run dev */}
      <InstallCommand run="dev" />
    </>
  )
}

Exact commands

commands overrides the preset per manager. Managers you leave out still use the preset.

TSX
import { InstallCommand } from '@/components/brio/install-command'

export function CliInstall() {
  return (
    <InstallCommand
      add="my-cli"
      global
      commands={{ bun: 'bun add -g my-cli@canary' }}
      managers={['npm', 'pnpm', 'bun']}
      defaultManager="pnpm"
    />
  )
}

Independent block with a copy callback

Set sync={false} so this block keeps its own tab and does not read or write storage. onCopy fires after a successful copy.

TSX
import { InstallCommand, type PackageManager } from '@/components/brio/install-command'

export function TrackedInstall() {
  const track = (command: string, manager: PackageManager) => {
    console.log('copied', { command, manager })
  }
  return <InstallCommand add="motion" sync={false} prompt="" onCopy={track} />
}

Translated labels

TSX
import { InstallCommand } from '@/components/brio/install-command'

export function Installieren() {
  return (
    <InstallCommand
      add="motion"
      labels={{
        tabs: 'Paketmanager',
        copy: 'Befehl kopieren',
        copied: 'Kopiert',
        copyFailed: 'Kopieren fehlgeschlagen',
        copiedSpoken: 'In die Zwischenablage kopiert',
        copyFailedSpoken: 'Kopieren fehlgeschlagen. Befehl markieren und manuell kopieren.',
      }}
    />
  )
}

Separate remembered choice

Give a site its own storageKey so its remembered choice does not collide with other sites on the same origin.

TSX
import { InstallCommand } from '@/components/brio/install-command'

const KEY = 'acme-docs:pm'

export function Steps() {
  return (
    <>
      <InstallCommand add="@acme/sdk" storageKey={KEY} />
      <InstallCommand run="dev" storageKey={KEY} />
    </>
  )
}

All synced blocks share one in-memory choice, whatever their storageKey. The key only controls what is saved and which other-tab changes are followed.

Props

InstallCommand

PropTypeDefaultDescription
addstring | string[]—Package(s) to add: add="motion" → npm install motion / pnpm add motion … Arrays are joined with spaces. Used when no other preset or exact command applies.
devbooleanfalseWith add: install as a dev dependency (-D). Ignored when global is set (warns in development). @default false Ignored when global is set, with a console warning in development.
globalbooleanfalseWith add: install globally (npm install -g, pnpm add -g, yarn global add, bun add -g). @default false
execstring—One-off binary: exec="shadcn@latest add button" → npx / pnpm dlx / yarn dlx / bunx
createstring—Scaffolder: create="vite@latest my-app" → npm create / pnpm create … Scaffolder: <manager> create <value>.
runstring—package.json script: run="dev" → npm run dev / pnpm dev / yarn dev / bun run dev
commandsPartial<Record<PackageManager, string>>—Exact command per manager. Wins over every preset. Line breaks are kept (e.g. a \ + newline continuation shows on two lines, exactly as it copies). Exact commands per manager; wins over presets. Managers left out fall back to the preset.
managersreadonly PackageManager[]PACKAGE_MANAGERSWhich tabs to show, in order. Unknown names are dropped; an empty list falls back to all four (dev warning). @default PACKAGE_MANAGERS (npm, pnpm, yarn, bun)
defaultManagerPackageManager'npm'Tab selected before the user picks one (or nothing is remembered). Falls back to the first of managers when not listed there. @default 'npm'
syncbooleantrueShare the choice with every synced block on the page, save it to localStorage, and follow changes from other browser tabs. Share the choice with every block on the page and remember it in localStorage (also follows other tabs). @default true
storageKeystring'install-command:pm'localStorage key for the remembered choice when sync is on. @default 'install-command:pm'
promptstring'$'Shown before the command, never copied. Pass '' to hide it. At most 40% of the row: a long shell prompt ends in “…” (full text on hover). At most 40% of the row (ends in "…"). @default '$'
onCopy(command: string, manager: PackageManager) => void—Called after a successful copy with the exact command text and the active manager. Not called when copying fails.
tabStyle'labels' | 'icons''labels'How the package-manager tabs read. 'icons' shows only the logos (the name stays as the tab's accessible name and tooltip), for tight rows.
labelsPartial<InstallCommandLabels>—Override any of the UI strings (tab list name, copy button, copy feedback). Override any UI string. See InstallCommandLabels.
classNamestring—Extra class on the root element (.ic-root).
styleCSSProperties—Inline styles on the root element; handy for --install-command-* variables.

Preset precedence: commands > exec > create > run > add.

InstallCommandLabels

FieldTypeDefault
tabsstring'Package manager' (accessible name of the tab list)
copystring'Copy command' (copy button's accessible name)
copiedstring'Copied' (shown beside the button after a copy, and the button's name meanwhile)
copyFailedstring'Copy failed' (shown beside the button when copying fails)
copiedSpokenstring'Copied to clipboard' (announced)
copyFailedSpokenstring'Copy failed. Select the command and copy it manually.' (announced)

PackageManager

'npm' | 'pnpm' | 'yarn' | 'bun'

PACKAGE_MANAGERS

readonly ['npm', 'pnpm', 'yarn', 'bun']. The default value of managers.

Customisation

  • Theme tokens: --ui-ink, --ui-surface, --ui-accent, --ui-danger, --ui-font-mono. Corners: --ui-radius (surfaces and inner cards, scaled from it), --ui-radius-sm (buttons, fields, rows and tags, scaled from it).
  • Component CSS variables: --install-command-ink, --install-command-surface, --install-command-accent (focus ring and copy highlight), --install-command-danger ("Copy failed" text), --install-command-font (monospace font). --install-command-radius sets the corner of the block (default 0.875 × --ui-radius, 14px); the other corners scale from it.
  • Class names: className goes on .ic-root. Stable inner classes: .ic-head, .ic-tabs, .ic-tab, .ic-pill (sliding tab background), .ic-logo, .ic-copy, .ic-copy-label, .ic-panel, .ic-prompt, .ic-line, .ic-code.
  • Long content: a long command scrolls sideways under an edge fade, with a thin scrollbar while hovered or focused. Letters animate per grapheme (emoji stay whole); words in scripts that need shaping (Arabic, Hebrew, Devanagari, Thai…) animate as whole words. The copy feedback label uses the free space next to the tabs (at least ~10 characters) and ends in “…” when longer. A preset that is set but blank (add="", run=" ") logs a dev warning.
  • Labels: every UI string (tab list name, copy button, visible and announced copy feedback) can be replaced through labels; see InstallCommandLabels.

Responsive behaviour

  • On narrow blocks the package-manager tabs scroll sideways, so the copy button always stays on the card.
  • On touch screens the tabs and the copy button are 44px tall.

Keyboard

KeyAction
TabMoves between the active tab, the copy button and the command panel.
ArrowRight / ArrowLeftOn the tabs: select the next / previous manager (wraps around).
Home / EndOn the tabs: select the first / last manager.
Enter / SpaceOn the copy button: copy the command.
Cmd+C / Ctrl+COn the focused command panel: copy the command when no text is selected.

Accessibility

  • The tabs are a real tablist with tab buttons (roving tabIndex) and one tabpanel, labelled by the active tab.
  • The command is read once as plain text; the per-letter animated copy is aria-hidden.
  • Copy success ("Copied to clipboard") and failure ("Copy failed. Select the command and copy it manually.") are announced in a polite role="status" region.
  • One click on the command selects all of it. The prompt is never selected or copied.
  • If the Clipboard API is unavailable, it falls back to document.execCommand('copy').
  • Reduced motion: words crossfade instead of rolling, the tab pill jumps instead of stretching, the cascade delay and copy highlight are off.

Agent prompt

Agent prompt
Add the Brio UI "Install Command" component to this project.
1. Run `npx shadcn@latest add https://brioui.com/r/install-command.json`.
2. Make sure `motion` is installed and React is 19 or newer.
3. Import it from the file the CLI created and render it with one preset: add="pkg" (plus dev or global), exec="bin args", create="template", or run="script"; use commands={{ ... }} for exact per-manager text.
4. Blocks sync their package-manager choice by default; pass the same storageKey to every block on the site, or sync={false} for a standalone block.
Full docs: https://brioui.com/docs/components/install-command.md
Free set rolling out now

Pro launches in November

Install Command is free to copy today, one of 10 free components rolling out now. The other 40 are Pro: plan comparisons, onboarding checklists, notification inboxes and more, all tested against the same worst cases. Founding members get 50% off Pro for life.

No payment. Just three emails: welcome, launch, Pro access.