Skip to content

Fix: shadcn/ui Not Working — Components Not Rendering, Styles Missing, or Dark Mode Broken

FixDevs ·

Quick Answer

How to fix shadcn/ui issues — Tailwind CSS v4 vs v3 configuration, CSS variables, dark mode setup, component installation, cn() utility, and common errors after adding components.

The Problem

After installing shadcn/ui components, they render with no styles:

npx shadcn@latest add button
# Component added but <Button> appears unstyled

Or dark mode doesn’t work despite setting the class:

<html className="dark">
  {/* Dark mode styles don't apply */}
</html>

Or the cn() utility causes a type error:

import { cn } from '@/lib/utils';
// Error: Cannot find module '@/lib/utils'

Or after upgrading Tailwind to v4, shadcn components break:

Error: Cannot apply unknown utility class: bg-background

Why This Happens

shadcn/ui isn’t a component library you install as a package — it’s a CLI that copies component code into your project. Issues arise when the surrounding configuration isn’t set up correctly:

  • CSS variables not injected — shadcn/ui uses CSS custom properties (--background, --foreground, etc.) for theming. These must be added to your global CSS file. Without them, every component that uses bg-background or text-foreground renders with no color.
  • Tailwind content paths miss component files — Tailwind purges unused classes. If your tailwind.config doesn’t scan the right directories, classes used in shadcn components are removed from the build.
  • Dark mode requires class strategy — Tailwind’s darkMode: 'class' applies dark styles when a dark class is on the <html> element. The default media strategy uses the OS preference and ignores the class.
  • Tailwind v4 uses a different config format — shadcn/ui CLI was initially designed for Tailwind v3. Tailwind v4 uses CSS-based configuration (@import "tailwindcss") instead of tailwind.config.js.

Fix 1: Run the Init Command Correctly

# Start fresh — let the CLI configure everything
npx shadcn@latest init

# Answer the prompts:
# ✔ Which style would you like to use? › Default
# ✔ Which color would you like to use as the base color? › Slate
# ✔ Would you like to use CSS variables for theming? › yes

This command sets up:

  • components.json — shadcn configuration
  • tailwind.config.ts — with the right content paths and plugins
  • globals.css — with all CSS variables
  • lib/utils.ts — with the cn() function

If you already have a project, verify these files exist:

ls components.json lib/utils.ts app/globals.css
# If any are missing, re-run: npx shadcn@latest init

Fix 2: Configure Tailwind Correctly

// tailwind.config.ts — required configuration for shadcn/ui
import type { Config } from 'tailwindcss';

const config: Config = {
  // REQUIRED: dark mode must use 'class' strategy
  darkMode: ['class'],

  // REQUIRED: include all paths where Tailwind classes are used
  content: [
    './pages/**/*.{ts,tsx}',
    './components/**/*.{ts,tsx}',
    './app/**/*.{ts,tsx}',
    './src/**/*.{ts,tsx}',
  ],

  theme: {
    container: {
      center: true,
      padding: '2rem',
      screens: {
        '2xl': '1400px',
      },
    },
    extend: {
      // REQUIRED: CSS variable-based color system
      colors: {
        border: 'hsl(var(--border))',
        input: 'hsl(var(--input))',
        ring: 'hsl(var(--ring))',
        background: 'hsl(var(--background))',
        foreground: 'hsl(var(--foreground))',
        primary: {
          DEFAULT: 'hsl(var(--primary))',
          foreground: 'hsl(var(--primary-foreground))',
        },
        secondary: {
          DEFAULT: 'hsl(var(--secondary))',
          foreground: 'hsl(var(--secondary-foreground))',
        },
        destructive: {
          DEFAULT: 'hsl(var(--destructive))',
          foreground: 'hsl(var(--destructive-foreground))',
        },
        muted: {
          DEFAULT: 'hsl(var(--muted))',
          foreground: 'hsl(var(--muted-foreground))',
        },
        accent: {
          DEFAULT: 'hsl(var(--accent))',
          foreground: 'hsl(var(--accent-foreground))',
        },
        popover: {
          DEFAULT: 'hsl(var(--popover))',
          foreground: 'hsl(var(--popover-foreground))',
        },
        card: {
          DEFAULT: 'hsl(var(--card))',
          foreground: 'hsl(var(--card-foreground))',
        },
      },
      borderRadius: {
        lg: 'var(--radius)',
        md: 'calc(var(--radius) - 2px)',
        sm: 'calc(var(--radius) - 4px)',
      },
      // Required for animations
      keyframes: {
        'accordion-down': {
          from: { height: '0' },
          to: { height: 'var(--radix-accordion-content-height)' },
        },
        'accordion-up': {
          from: { height: 'var(--radix-accordion-content-height)' },
          to: { height: '0' },
        },
      },
      animation: {
        'accordion-down': 'accordion-down 0.2s ease-out',
        'accordion-up': 'accordion-up 0.2s ease-out',
      },
    },
  },
  plugins: [require('tailwindcss-animate')],
};

export default config;

Install required plugins:

npm install tailwindcss-animate

Fix 3: Add CSS Variables to globals.css

/* app/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 222.2 84% 4.9%;
    --card: 0 0% 100%;
    --card-foreground: 222.2 84% 4.9%;
    --popover: 0 0% 100%;
    --popover-foreground: 222.2 84% 4.9%;
    --primary: 222.2 47.4% 11.2%;
    --primary-foreground: 210 40% 98%;
    --secondary: 210 40% 96.1%;
    --secondary-foreground: 222.2 47.4% 11.2%;
    --muted: 210 40% 96.1%;
    --muted-foreground: 215.4 16.3% 46.9%;
    --accent: 210 40% 96.1%;
    --accent-foreground: 222.2 47.4% 11.2%;
    --destructive: 0 84.2% 60.2%;
    --destructive-foreground: 210 40% 98%;
    --border: 214.3 31.8% 91.4%;
    --input: 214.3 31.8% 91.4%;
    --ring: 222.2 84% 4.9%;
    --radius: 0.5rem;
  }

  .dark {
    --background: 222.2 84% 4.9%;
    --foreground: 210 40% 98%;
    --card: 222.2 84% 4.9%;
    --card-foreground: 210 40% 98%;
    --popover: 222.2 84% 4.9%;
    --popover-foreground: 210 40% 98%;
    --primary: 210 40% 98%;
    --primary-foreground: 222.2 47.4% 11.2%;
    --secondary: 217.2 32.6% 17.5%;
    --secondary-foreground: 210 40% 98%;
    --muted: 217.2 32.6% 17.5%;
    --muted-foreground: 215 20.2% 65.1%;
    --accent: 217.2 32.6% 17.5%;
    --accent-foreground: 210 40% 98%;
    --destructive: 0 62.8% 30.6%;
    --destructive-foreground: 210 40% 98%;
    --border: 217.2 32.6% 17.5%;
    --input: 217.2 32.6% 17.5%;
    --ring: 212.7 26.8% 83.9%;
  }
}

@layer base {
  * {
    @apply border-border;
  }
  body {
    @apply bg-background text-foreground;
  }
}

Fix 4: Implement Dark Mode Toggling

// With next-themes (recommended for Next.js)
npm install next-themes

// app/providers.tsx
'use client';

import { ThemeProvider } from 'next-themes';

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <ThemeProvider
      attribute="class"      // Adds 'dark' class to <html>
      defaultTheme="system"  // Follow OS preference
      enableSystem
      disableTransitionOnChange
    >
      {children}
    </ThemeProvider>
  );
}

// app/layout.tsx
import { Providers } from './providers';

export default function RootLayout({ children }) {
  return (
    <html lang="en" suppressHydrationWarning>
      {/* suppressHydrationWarning prevents hydration mismatch from theme */}
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

// components/theme-toggle.tsx
'use client';

import { useTheme } from 'next-themes';
import { Button } from '@/components/ui/button';
import { Moon, Sun } from 'lucide-react';

export function ThemeToggle() {
  const { theme, setTheme } = useTheme();

  return (
    <Button
      variant="ghost"
      size="icon"
      onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
    >
      <Sun className="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
      <Moon className="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
    </Button>
  );
}

Fix 5: Use cn() and Customize Components

// lib/utils.ts — the cn() utility (created by shadcn init)
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

// Install if missing:
// npm install clsx tailwind-merge

Customize shadcn components:

// components/ui/button.tsx — add custom variants
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';

const buttonVariants = cva(
  // Base classes
  'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
  {
    variants: {
      variant: {
        default: 'bg-primary text-primary-foreground hover:bg-primary/90',
        destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
        outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
        secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
        ghost: 'hover:bg-accent hover:text-accent-foreground',
        link: 'text-primary underline-offset-4 hover:underline',
        // Add custom variant
        success: 'bg-green-600 text-white hover:bg-green-700',
      },
      size: {
        default: 'h-10 px-4 py-2',
        sm: 'h-9 rounded-md px-3',
        lg: 'h-11 rounded-md px-8',
        icon: 'h-10 w-10',
      },
    },
    defaultVariants: {
      variant: 'default',
      size: 'default',
    },
  }
);

// Usage
<Button variant="success" size="lg">Save Changes</Button>

Fix 6: Use with Tailwind v4

Tailwind v4 changed the configuration format. For projects using Tailwind v4:

# Check your Tailwind version
npx tailwindcss --version

# If v4, use the new CSS-based config approach
/* globals.css — Tailwind v4 format */
@import "tailwindcss";

/* Tailwind v4 uses @theme instead of tailwind.config.js */
@theme {
  --color-background: hsl(0 0% 100%);
  --color-foreground: hsl(222.2 84% 4.9%);
  --color-primary: hsl(222.2 47.4% 11.2%);
  --color-primary-foreground: hsl(210 40% 98%);
  --color-secondary: hsl(210 40% 96.1%);
  --color-secondary-foreground: hsl(222.2 47.4% 11.2%);
  --color-destructive: hsl(0 84.2% 60.2%);
  --color-border: hsl(214.3 31.8% 91.4%);
  --color-input: hsl(214.3 31.8% 91.4%);
  --color-muted: hsl(210 40% 96.1%);
  --color-muted-foreground: hsl(215.4 16.3% 46.9%);
  --color-accent: hsl(210 40% 96.1%);
  --color-popover: hsl(0 0% 100%);
  --color-card: hsl(0 0% 100%);
  --radius: 0.5rem;
}

/* Dark mode theme override */
.dark {
  --color-background: hsl(222.2 84% 4.9%);
  --color-foreground: hsl(210 40% 98%);
  /* ... rest of dark vars */
}

shadcn with Tailwind v4 — use the canary version:

# Use the shadcn canary that supports Tailwind v4
npx shadcn@canary init

# Add components with canary
npx shadcn@canary add button

Still Not Working?

Components render but look wrong after updating shadcn — shadcn components are copied into your project. When you run npx shadcn@latest add button on an existing project, it overwrites your local customizations. Check the diff before accepting the overwrite, and back up customized components before updating.

import { Button } from '@/components/ui/button' fails — the @/ alias must be configured in your TypeScript and bundler config:

// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./*"]
    }
  }
}
// vite.config.ts
import path from 'path';
export default defineConfig({
  resolve: {
    alias: { '@': path.resolve(__dirname, './src') },
  },
});

Radix UI peer dependency warning — shadcn components use Radix UI primitives. Version mismatches between Radix packages cause subtle bugs. Run npm ls @radix-ui to check versions. If there are conflicts, deduplicate: npm dedupe or reinstall with npm install --legacy-peer-deps.

For related styling issues, see Fix: Tailwind Classes Not Applying and Fix: CSS Tailwind Not Applying.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles