Overview

shadcn/ui installation is a one-time scaffold, not an npm dependency. The CLI writes components directly into your source tree. Getting the init step right prevents a class of problems: missing CSS variables, broken dark mode, Tailwind config mismatches, and component files that reference utilities you never set up. This page covers the sequence that avoids those issues.

Run init once per project before adding any component

The init command writes components.json, the base CSS variables, and the utility helpers (lib/utils.ts) that every component imports. Run it before any add call.

npx shadcn@latest init

The CLI asks four questions: TypeScript preference, base color, globals.css path, and tailwind.config path. Answer accurately; these values are written to components.json and used on every subsequent add call. If you answer wrong, re-run init before proceeding rather than editing components.json by hand.

For Next.js with App Router, the CSS path is app/globals.css and tailwind.config is tailwind.config.ts at the project root. See nextjs-app-router for the full App Router layout.

Inspect components.json after init

components.json is the config the CLI reads on every add. The critical fields are aliases: they control the import paths written into every component file.

{
  "style": "default",
  "rsc": true,
  "tsx": true,
  "tailwind": {
    "config": "tailwind.config.ts",
    "css": "app/globals.css",
    "baseColor": "slate",
    "cssVariables": true
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils"
  }
}

Set cssVariables: true. The CSS-variable path keeps theming in one place; the raw Tailwind-color path hardcodes colors into every component and makes retheming expensive. See shadcn-theming for the full theming model.

Add components one at a time as features need them

Install a component only when the next feature uses it. The CLI writes one file (plus peer files) per call.

npx shadcn@latest add button
npx shadcn@latest add dialog
npx shadcn@latest add form
npx shadcn@latest add table

Do not run a bulk add of the entire catalog. Each component adds code you will read in PRs, audit for accessibility, and occasionally edit. Dead components are dead weight in a layer you own. If a component is never imported, remove the file.

Confirm peer dependencies are installed

The CLI installs Radix primitives and helpers (class-variance-authority, tailwind-merge, clsx) as it goes. Verify they landed in package.json after the first add.

npm ls class-variance-authority tailwind-merge clsx

If the package manager is Yarn or pnpm, the CLI may not auto-install them. Run the install step manually if you see missing-module errors. The lib/utils.ts file the CLI writes uses tailwind-merge directly, so a missing dep will fail at runtime, not build time.

Confirm the CSS variables block landed in globals.css

Open globals.css after init and verify the :root and .dark blocks exist. A failed write (permission error, wrong path in components.json) leaves the block out and every component renders with broken tokens.

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 222.2 84.7% 4.1%;
    /* ... */
  }
  .dark {
    --background: 222.2 84% 4.9%;
    --foreground: 210 40% 98%;
  }
}

These values are hsl channels, not full hsl() declarations, so Tailwind can compose them as hsl(var(--background) / <alpha>). Do not convert them to oklch or full color syntax without updating the @theme block as well. See shadcn-theming for how to change the palette.

Wire Tailwind so the CSS variables become utilities

shadcn components use utilities like bg-background, text-foreground, and border-border. These work only if Tailwind maps those names to the CSS variables.

With Tailwind v4, add an @theme block in globals.css:

@theme {
  --color-background: hsl(var(--background));
  --color-foreground: hsl(var(--foreground));
  --color-primary: hsl(var(--primary));
  --color-primary-foreground: hsl(var(--primary-foreground));
}

With Tailwind v3, add extend.colors in tailwind.config.ts:

colors: {
  background: "hsl(var(--background) / <alpha-value>)",
  foreground: "hsl(var(--foreground) / <alpha-value>)",
}

The v3 <alpha-value> token is required for opacity utilities. Without it, bg-background/50 silently renders as fully opaque. See tailwind for the broader Tailwind configuration.

Commit components/ui as first-party code

Every file the CLI writes is yours. Commit it. There is no shadcn version to pin, no upgrade command, and no runtime package to audit. When upstream ships a fix for a component, read the diff on GitHub and port the relevant lines yourself.

Treat components/ui/*.tsx the same as any code you write: review it in PRs, lint it, and test any behavioral changes. See shadcn-composition for how to extend and compose the installed components.