React SME Cookbook
All FAQs

Search Documentation

Search across all documentation pages

shadcnsetupinstallationthemingconfiguration

shadcn/ui Setup

Install, configure, and theme shadcn/ui — copy-paste components built on Radix UI and Tailwind CSS.

Recipe

Quick-reference recipe card — copy-paste ready.

# Initialize shadcn in your project
npx shadcn@latest init
 
# Add specific components
npx shadcn@latest add button
npx shadcn@latest add input
npx shadcn@latest add card
 
# Add multiple at once
npx shadcn@latest add button input card dialog form
// components.json — generated by init
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "new-york",
  "tailwind": {
    "config": "",
    "css": "app/globals.css",
    "baseColor": "zinc",
    "cssVariables": true
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils"
  }
}
// lib/utils.ts — the cn utility
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
 
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

When to reach for this: At the start of any Next.js or React project where you want polished, accessible UI components with full control over the source code.

Working Example

/* app/globals.css — shadcn theme with CSS variables */
@import "tailwindcss";
 
@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
  --color-secondary: var(--secondary);
  --color-secondary-foreground: var(--secondary-foreground);
  --color-muted: var(--muted);
  --color-muted-foreground: var(--muted-foreground);
  --color-accent: var(--accent);
  --color-accent-foreground: var(--accent-foreground);
  --color-destructive: var(--destructive);
  --color-border: var(--border);
  --color-input: var(--input);
  --color-ring: var(--ring);
  --radius-sm: calc(var(--radius) - 4px);
  --radius-md: calc(var(--radius) - 2px);
  --radius-lg: var(--radius);
  --radius-xl: calc(var(--radius) + 4px);
}
 
@layer base {
  :root {
    --background: oklch(1 0 0);
    --foreground: oklch(0.145 0 0);
    --primary: oklch(0.205 0 0);
    --primary-foreground: oklch(0.985 0 0);
    --secondary: oklch(0.97 0 0);
    --secondary-foreground: oklch(0.205 0 0);
    --muted: oklch(0.97 0 0);
    --muted-foreground: oklch(0.556 0 0);
    --accent: oklch(0.97 0 0);
    --accent-foreground: oklch(0.205 0 0);
    --destructive: oklch(0.577 0.245 27.325);
    --border: oklch(0.922 0 0);
    --input: oklch(0.922 0 0);
    --ring: oklch(0.708 0 0);
    --radius: 0.625rem;
  }
 
  .dark {
    --background: oklch(0.145 0 0);
    --foreground: oklch(0.985 0 0);
    --primary: oklch(0.985 0 0);
    --primary-foreground: oklch(0.205 0 0);
    --secondary: oklch(0.269 0 0);
    --secondary-foreground: oklch(0.985 0 0);
    --muted: oklch(0.269 0 0);
    --muted-foreground: oklch(0.708 0 0);
    --accent: oklch(0.269 0 0);
    --accent-foreground: oklch(0.985 0 0);
    --destructive: oklch(0.577 0.245 27.325);
    --border: oklch(0.269 0 0);
    --input: oklch(0.269 0 0);
    --ring: oklch(0.439 0 0);
  }
}
// app/layout.tsx
import { ThemeProvider } from "next-themes";
import "@/app/globals.css";
 
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body className="bg-background text-foreground antialiased">
        <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
}
// Quick demo using shadcn components
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
 
export function LoginCard() {
  return (
    <Card className="w-[350px]">
      <CardHeader>
        <CardTitle>Login</CardTitle>
        <CardDescription>Enter your credentials to continue.</CardDescription>
      </CardHeader>
      <CardContent className="space-y-4">
        <div className="space-y-2">
          <Label htmlFor="email">Email</Label>
          <Input id="email" type="email" placeholder="you@example.com" />
        </div>
        <div className="space-y-2">
          <Label htmlFor="password">Password</Label>
          <Input id="password" type="password" />
        </div>
        <Button className="w-full">Sign in</Button>
      </CardContent>
    </Card>
  );
}

What this demonstrates:

  • shadcn initialization and component installation
  • CSS variable-based theming with light and dark mode
  • ThemeProvider setup from next-themes
  • Component composition (Card + Input + Button + Label)
  • The cn() utility for merging class names

Deep Dive

How It Works

  • shadcn/ui is NOT a package — it copies component source code into your project
  • Components live in components/ui/ and are fully editable
  • Each component uses Radix UI primitives for accessibility and behavior
  • Styling uses Tailwind CSS utilities + CSS variables for theming
  • The cn() utility merges class names with clsx and resolves Tailwind conflicts with tailwind-merge
  • CSS variables enable runtime theme switching without rebuilding

Variations

Custom theme colors:

/* Use the shadcn theme generator at ui.shadcn.com/themes */
:root {
  --primary: oklch(0.5 0.2 260);  /* custom blue */
  --destructive: oklch(0.5 0.2 25); /* custom red */
}

Adding a component with dependencies:

# Some components auto-install their dependencies
npx shadcn@latest add dialog
# This installs @radix-ui/react-dialog and the Dialog component

Path aliases setup:

// tsconfig.json
{
  "compilerOptions": {
    "paths": {
      "@/*": ["./*"]
    }
  }
}

TypeScript Notes

// cn utility is fully typed
import { cn } from "@/lib/utils";
 
cn("bg-red-500", undefined, false, "text-white");
// returns "bg-red-500 text-white"
 
// Component props extend Radix types
// e.g., Button extends React.ButtonHTMLAttributes<HTMLButtonElement>
import { Button, type ButtonProps } from "@/components/ui/button";
 
function MyButton(props: ButtonProps) {
  return <Button variant="outline" {...props} />;
}

Gotchas

  • Not a package — it is source codenpm install shadcn is wrong. Fix: Use npx shadcn@latest add <component> to copy components into your project.

  • Tailwind v4 compatibility — shadcn/ui supports Tailwind v4. Make sure to run the latest npx shadcn@latest init which generates the v4-compatible CSS.

  • CSS variables must be defined — If you skip the CSS variable setup, components render with no colors. Fix: Ensure your globals.css includes the full :root and .dark variable blocks.

  • cn() import path — Components import from @/lib/utils. If your alias differs, update components.json and run init again.

  • Component updates — Since components are copied, they do not auto-update. Fix: Re-run npx shadcn@latest add <component> to get the latest version (but this overwrites your changes).

Alternatives

AlternativeUse WhenDon't Use When
Radix UI (bare)You want primitives without opinions on stylingYou want pre-styled, ready-to-use components
Headless UIYou use Tailwind but prefer a different primitive libraryYou want the full shadcn ecosystem
MUI / Chakra UIYou want a fully packaged component libraryYou want full control over component source code
MantineYou want a batteries-included React component libraryYou use Tailwind for styling

FAQs

Is shadcn/ui an npm package you install?

No. shadcn/ui copies component source code into your project. Use npx shadcn@latest add <component>, not npm install shadcn.

What does npx shadcn@latest init do?
  • Generates components.json with project settings
  • Creates lib/utils.ts with the cn() utility
  • Sets up CSS variables in globals.css for theming
What is the cn() utility and why is it needed?

cn() combines clsx (conditional class merging) with tailwind-merge (Tailwind conflict resolution). It ensures className overrides work correctly on shadcn components.

Where do shadcn components live in the project?

In components/ui/ by default. Each component is a fully editable source file, not a node_modules dependency.

How do you customize the shadcn theme colors?

Edit the CSS variables in globals.css under :root (light) and .dark (dark). Use the theme generator at ui.shadcn.com/themes for quick presets.

How does dark mode work with shadcn?

CSS variables are defined twice -- once in :root for light mode and once in .dark for dark mode. Use next-themes with attribute="class" to toggle the .dark class.

How do you add multiple components at once?
npx shadcn@latest add button input card dialog form
Gotcha: Components render with no colors after setup. What is wrong?

The CSS variables are missing. Ensure globals.css includes the full :root and .dark variable blocks generated by npx shadcn@latest init.

Gotcha: You updated a component but your changes were lost. What happened?

Re-running npx shadcn@latest add <component> overwrites the file. Back up your customizations before updating, or use version control.

How is cn() typed in TypeScript?
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
 
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

ClassValue accepts strings, arrays, objects, undefined, null, and booleans.

How do you use shadcn component types in TypeScript?
import { Button, type ButtonProps } from "@/components/ui/button";
 
function MyButton(props: ButtonProps) {
  return <Button variant="outline" {...props} />;
}

Component props extend their underlying Radix or HTML element types.