seedflip
Archive
Mixtapes
Pricing
Sign in

shadcn/ui Theming Troubleshooting: 10 Common Mistakes and Fixes

shadcn/ui theming looks simple until it doesn't work. You copy a theme block into globals.css, toggle dark mode, and nothing changes. Or worse, half your components break while the other half look fine. It's not you. The theming system has real gotchas that trip up everyone from beginners to experienced developers.

Generate a complete shadcn theme →

1. Missing CSS variables in globals.css

The symptom: Components render with no background, wrong text color, or transparent borders. The page looks half-broken.

The cause: Your globals.css is missing one or more required CSS variables. shadcn/ui components reference specific variable names. If --background, --foreground, --card, --popover, or any other semantic token is missing, the component falls back to nothing.

The fix: Make sure your :root block includes every required variable. Here is the minimum set:

:root { --background: 0 0% 100%; --foreground: 240 10% 3.9%; --card: 0 0% 100%; --card-foreground: 240 10% 3.9%; --popover: 0 0% 100%; --popover-foreground: 240 10% 3.9%; --primary: 240 5.9% 10%; --primary-foreground: 0 0% 98%; --secondary: 240 4.8% 95.9%; --secondary-foreground: 240 5.9% 10%; --muted: 240 4.8% 95.9%; --muted-foreground: 240 3.8% 46.1%; --accent: 240 4.8% 95.9%; --accent-foreground: 240 5.9% 10%; --destructive: 0 84.2% 60.2%; --destructive-foreground: 0 0% 98%; --border: 240 5.9% 90%; --input: 240 5.9% 90%; --ring: 240 5.9% 10%; --radius: 0.5rem; }

Miss even one and the component referencing it breaks. Tools like shadcn theme generators handle this automatically by exporting the complete variable set.

2. Using hex values instead of HSL channel format

The symptom: You paste a color like #6366F1 into a variable and the component ignores it entirely.

The cause: shadcn/ui's default setup expects HSL values without the hsl() wrapper. The Tailwind config wraps them using hsl(var(--primary)). If you put a hex value in the variable, the resulting hsl(#6366F1) is invalid CSS.

The fix: Convert your colors to HSL channel format (hue, saturation%, lightness%) without the function wrapper:

/* Wrong */ --primary: #6366F1; --primary: hsl(239 84% 67%); /* Right */ --primary: 239 84% 67%;

3. Dark mode variables not scoped to .dark

The symptom: You added dark mode colors but toggling dark mode does nothing.

The cause: Your dark mode variables are in a @media (prefers-color-scheme: dark) block instead of a .dark class selector. shadcn/ui uses the class strategy by default, meaning dark mode activates when a .dark class is present on the HTML element.

The fix: Scope your dark variables to the .dark class:

/* Wrong: media query strategy */ @media (prefers-color-scheme: dark) { :root { --background: 240 10% 3.9%; } } /* Right: class strategy */ .dark { --background: 240 10% 3.9%; --foreground: 0 0% 98%; /* ... all other dark values */ }

For a deeper breakdown of dark mode configuration, see shadcn Dark Mode Theming.

4. Missing chart color variables

The symptom: Your dashboard charts render in black, gray, or some random fallback color.

The cause: shadcn/ui's chart components reference --chart-1 through --chart-5. Older theme generators and copy-paste themes don't include these variables. They were added later, and a lot of themes floating around the internet predate them.

The fix: Add all five chart variables to both light and dark modes:

:root { --chart-1: 12 76% 61%; --chart-2: 173 58% 39%; --chart-3: 197 37% 24%; --chart-4: 43 74% 66%; --chart-5: 27 87% 67%; } .dark { --chart-1: 220 70% 50%; --chart-2: 160 60% 45%; --chart-3: 30 80% 55%; --chart-4: 280 65% 60%; --chart-5: 340 75% 55%; }

5. Border radius not applying

The symptom: Components look square when they should have rounded corners, or all components have the same border radius regardless of your --radius value.

The cause: shadcn/ui uses a base --radius variable and derives component-specific radii from it using calc(). If --radius is missing or set incorrectly (like 8px instead of 0.5rem), the derived calculations break.

The fix: Set --radius in rem units:

:root { --radius: 0.5rem; }

Common values: 0.3rem for subtle rounding, 0.5rem for the default look, 0.75rem for softer edges, 1rem for pronounced rounding.

6. Tailwind config not wrapping variables in hsl()

The symptom: CSS variables are set correctly in globals.css but utility classes like bg-primary don't pick up the color.

The cause: In Tailwind v3, your tailwind.config.js needs to reference CSS variables with the hsl() wrapper. Without it, Tailwind can't resolve the color.

/* tailwind.config.js (v3) */ module.exports = { theme: { extend: { colors: { /* Wrong */ primary: 'var(--primary)', /* Right */ primary: 'hsl(var(--primary))', }, }, }, }

If you're on Tailwind v4, this is no longer an issue. The @theme directive handles variable resolution natively.

7. Opacity modifiers not working with custom colors

The symptom: bg-primary/50 doesn't produce a semi-transparent version of your primary color. It either shows full opacity or breaks entirely.

The cause: Tailwind's opacity modifier syntax requires colors in a format that supports alpha injection. If your CSS variable contains a complete color value like hsl(239 84% 67%), Tailwind can't inject an alpha channel. The variable needs to contain just the channels: 239 84% 67%.

The fix: Store channel values only, and let Tailwind wrap them:

/* Variable: channels only */ --primary: 239 84% 67%; /* Tailwind config: wraps in hsl() */ primary: 'hsl(var(--primary))', /* Now this works */ <div className="bg-primary/50">...</div>

8. next-themes flash of wrong theme

The symptom: The page briefly flashes white before switching to dark mode (or vice versa) on load.

The cause: The theme provider script hasn't executed before the initial render. React hydration happens first, painting the default theme, then the provider switches to the saved theme.

The fix: Make sure your ThemeProvider wraps the layout body and uses the attribute prop. Add suppressHydrationWarning to the html tag:

/* app/layout.tsx */ <html lang="en" suppressHydrationWarning> <body> <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange > {children} </ThemeProvider> </body> </html>

The disableTransitionOnChange prop prevents a brief transition flash during the switch. For more dark mode patterns, see shadcn Dark Mode Theming.

9. Sidebar and sheet colors don't match theme

The symptom: Your sidebar or sheet component uses a different background than the rest of your app, even though you set --background.

The cause: Sidebar components often reference --sidebar-background, --sidebar-foreground, and other sidebar-specific tokens. These are separate from the global theme variables. If you don't define them, the component uses its own defaults.

The fix: Add sidebar-specific variables to your theme:

:root { --sidebar-background: 0 0% 98%; --sidebar-foreground: 240 5.3% 26.1%; --sidebar-primary: 240 5.9% 10%; --sidebar-primary-foreground: 0 0% 98%; --sidebar-accent: 240 4.8% 95.9%; --sidebar-accent-foreground: 240 5.9% 10%; --sidebar-border: 220 13% 91%; --sidebar-ring: 217.2 91.2% 59.8%; }

10. Theme looks good in light mode but washed out in dark

The symptom: You spent time on your light theme and it looks great. You copy the structure to .dark, invert some values, and dark mode looks flat and lifeless.

The cause: Dark themes aren't the inverse of light themes. Simply flipping lightness values produces washed-out midtones and low contrast borders. Dark mode needs its own carefully tuned palette with slightly higher saturation in accent colors and more deliberate contrast steps.

The fix: Design dark mode as its own system. Increase saturation for accent colors by 5-10%. Use darker, slightly warm backgrounds (not pure black). Give borders enough contrast to remain visible:

.dark { /* Background: very dark, slight warmth */ --background: 240 10% 3.9%; /* Primary: bump saturation for vibrancy on dark bg */ --primary: 239 90% 71%; /* Borders: enough contrast to be visible */ --border: 240 3.7% 15.9%; /* Muted: warm gray, not dead gray */ --muted: 240 3.7% 15.9%; }

Most shadcn/ui theming problems come down to one thing: the gap between what you think a CSS variable should contain and what shadcn's components actually expect. Every fix above is a two-minute change once you know where to look. For a detailed walkthrough of the globals.css structure, read How to Customize shadcn/ui globals.css. Or skip the manual work entirely. SeedFlip generates a complete shadcn/ui theme block with every required variable, both light and dark modes, chart colors included. One click, zero missing tokens.

Ready to stop guessing?

One flip. Complete design system. Free CSS export.

Generate a complete shadcn theme →