Fix: shadcn/ui Not Working — Components Not Rendering, Styles Missing, or Dark Mode Broken
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 unstyledOr 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-backgroundWhy 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 usesbg-backgroundortext-foregroundrenders with no color. - Tailwind
contentpaths miss component files — Tailwind purges unused classes. If yourtailwind.configdoesn’t scan the right directories, classes used in shadcn components are removed from the build. - Dark mode requires
classstrategy — Tailwind’sdarkMode: 'class'applies dark styles when adarkclass is on the<html>element. The defaultmediastrategy 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 oftailwind.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? › yesThis command sets up:
components.json— shadcn configurationtailwind.config.ts— with the right content paths and pluginsglobals.css— with all CSS variableslib/utils.ts— with thecn()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 initFix 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-animateFix 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-mergeCustomize 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 buttonStill 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.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: AutoAnimate Not Working — Transitions Not Playing, List Items Not Animating, or React State Changes Ignored
How to fix @formkit/auto-animate issues — parent ref setup, React useAutoAnimate hook, Vue directive, animation customization, disabling for specific elements, and framework integration.
Fix: Blurhash Not Working — Placeholder Not Rendering, Encoding Failing, or Colors Wrong
How to fix Blurhash image placeholder issues — encoding with Sharp, decoding in React, canvas rendering, Next.js image placeholders, CSS blur fallback, and performance optimization.
Fix: Embla Carousel Not Working — Slides Not Scrolling, Autoplay Not Starting, or Thumbnails Not Syncing
How to fix Embla Carousel issues — React setup, slide sizing, autoplay and navigation plugins, loop mode, thumbnail carousels, responsive breakpoints, and vertical scrolling.
Fix: i18next Not Working — Translations Missing, Language Not Switching, or Namespace Errors
How to fix i18next issues — react-i18next setup, translation file loading, namespace configuration, language detection, interpolation, pluralization, and Next.js integration.