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 a handful of questions: TypeScript preference, base color, and the globals.css path. On a Tailwind v4 project (the current default) it does not ask for a tailwind.config path, because there is no config file to point at; theming lives entirely in globals.css. Answer accurately; the 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. 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": "new-york",
  "rsc": true,
  "tsx": true,
  "tailwind": {
    "css": "app/globals.css",
    "baseColor": "neutral",
    "cssVariables": true
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils"
  }
}

On a Tailwind v3 project, tailwind.config still points at tailwind.config.ts; the field is simply absent (or unused) once the project is on v4. Set cssVariables: true either way. 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.

:root {
  --background: oklch(1 0 0);
  --foreground: oklch(0.145 0 0);
  /* ... */
}
.dark {
  --background: oklch(0.145 0 0);
  --foreground: oklch(0.985 0 0);
}

The current CLI default writes full oklch() color functions, not the bare HSL channel triples used by older shadcn scaffolds. A project generated before the Tailwind v4 switch may still carry HSL channels (--background: 0 0% 100%) composed as hsl(var(--background) / <alpha>); both formats work, but do not mix them within one token set. 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 (the current default), map the variables with @theme inline in globals.css; no tailwind.config file is involved:

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

With a legacy Tailwind v3 project, add extend.colors in tailwind.config.ts instead:

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

The v3 <alpha-value> token is required for opacity utilities on the HSL-channel format. Without it, bg-background/50 silently renders as fully opaque. On oklch tokens under Tailwind v4, @theme inline mapping handles opacity automatically through color-mix(). 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.