seedflip
Archive
Mixtapes
Pricing
Sign in

shadcn/ui Theming: How to Customize globals.css

Every shadcn/ui component reads its colors from CSS variables defined in your globals.css file. These variables use HSL format (hue, saturation, lightness) and control everything from background color to button hover states. You don't need Figma. You don't need a design tool. You need to understand 15 variables in one file.

Get a complete shadcn theme in one flip →

Where the variables live

When you init shadcn/ui, it adds a block of CSS variables to your globals.css (or app/globals.css in Next.js). They look like this:

@layer base { :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%; --border: 240 5.9% 90%; --input: 240 5.9% 90%; --ring: 240 5.9% 10%; --radius: 0.5rem; } }

These are HSL values without the hsl() wrapper. That's important. shadcn uses them with Tailwind's hsl(var(--variable)) syntax so it can add opacity modifiers like bg-primary/50. Don't wrap them in hsl() yourself.

What each variable controls

The naming is straightforward once you see the pattern. Every color pair has a base and a -foreground variant. The base is the background/fill color. The foreground is the text/icon color that sits on top of it.

The big five

--background / --foreground: Your page background and default text color. Every component inherits from these unless overridden.

--primary / --primary-foreground: Your main action color. Buttons, active states, primary CTAs. This is the one most people want to change first.

--secondary / --secondary-foreground: Your secondary action color. Less prominent buttons, toggle states, chips.

--muted / --muted-foreground: Subdued surfaces and text. Placeholder text, disabled states, subtle backgrounds.

--accent / --accent-foreground: Hover highlights and emphasis. Menu item hovers, sidebar active states.

Supporting players

--card / --card-foreground: Surfaces that sit above the background (cards, dialogs).

--popover / --popover-foreground: Floating elements (dropdowns, tooltips).

--destructive: Danger actions (delete buttons, error states).

--border: All borders, dividers, separators.

--input: Input field borders specifically.

--ring: Focus ring color (keyboard navigation outlines).

--radius: Global border radius (the only non-color variable). Controls every rounded corner.

How to actually change them

Pick any color you want. Convert it to HSL. Drop the hsl() wrapper and just use the three numbers. Here's an example that transforms the default shadcn theme into something with an indigo accent:

/* From boring defaults to Stripe-ish indigo */ :root { --background: 0 0% 100%; --foreground: 240 10% 3.9%; --primary: 239 84% 67%; /* #6366F1 → indigo */ --primary-foreground: 0 0% 100%; --accent: 240 100% 97%; /* soft indigo wash */ --accent-foreground: 239 84% 67%; --radius: 0.625rem; /* 10px, slightly softer */ }

That's seven lines. Every button, badge, toggle, dialog, and input in your project now looks different. No component modifications. No prop overrides. Just variables.

Prism
Light through dark glass
Inter+Inter
darkvibrantdeveloper
View seed →

The HSL conversion trick

If you have a hex color like #6366F1 and need the HSL values, you can use any converter. But here's the quick mental model:

Hue (0-360): The color wheel position. 0 = red, 120 = green, 240 = blue.
Saturation (0-100%): How vivid. 0% = gray, 100% = pure color.
Lightness (0-100%): How bright. 0% = black, 50% = full color, 100% = white.

In your globals.css, write them space-separated without units on the hue: 239 84% 67%. That's the format shadcn expects.

Don't forget dark mode

The same globals.css file has a .dark block right below :root. Same variables, different values:

.dark { --background: 240 10% 3.9%; --foreground: 0 0% 98%; --primary: 239 84% 67%; --primary-foreground: 0 0% 100%; --border: 240 3.7% 15.9%; --radius: 0.625rem; }

If you customize the light theme, do the dark theme too. Mismatched themes are worse than default ones.


That's the whole system. 15 CSS variables in one file. No design tool required. If you want to see how this fits into the broader shadcn theming workflow, read How to Theme shadcn/ui Without Figma. For a deeper comparison of theming tools, check The Best shadcn/ui Theme Generator. Or skip the manual work entirely and grab a seed that exports a complete shadcn theme in one click.

Ready to stop guessing?

One flip. Complete design system. Free CSS export.

Get a complete shadcn theme in one flip →