Denys Isaichenko 🧘

Making Sense of Shadcn UI's Theming and Color Variables

When I first tried Shadcn UI, I noticed the strange color naming, like bg-background and text-primary-foreground. At first, I just renamed those classes to Tailwind-style colors (text-gray-500, etc.), but I quickly realized that’s not a good idea because:

So it’s clear that using color tokens instead of raw Tailwind colors is beneficial, but there’s still a problem: background, foreground, card, card-foreground, popover, popover-foreground, primary, primary-foreground, secondary, secondary-foreground, muted, muted-foreground, accent, accent-foreground, destructive, border, input, ring, sidebar, sidebar-foreground, sidebar-primary, sidebar-primary-foreground, sidebar-accent, sidebar-accent-foreground, sidebar-border, sidebar-ring.

There are too many of them, and the difference between some of them is not entirely clear.

For example, from the first look, it’s hard to understand the difference between foreground and primary-foreground. Also, all colors are set as oklch, so it’s impossible to understand the Tailwind CSS reference for those colors at first glance. I know color-neutral-950, but of course, I don’t know oklch(0.145 0 0), which are the same.

This post is my attempt to understand it.

background and foreground

This one is easy: background is the color of the background, and foreground is the color of the text. In the default theme, background is color-white, and foreground is color-neutral-950 (dark gray).

foreground

primary and primary-foreground

Following the convention from above, primary-foreground is the color of the text. By default, it’s color-neutral-50 (almost white).

primary, on the other hand, is actually a “background” color, and by default, it’s color-neutral-900 (dark gray).

So the idea is to use primary for backgrounds of elements that define the main project color, like buttons, and to use primary-foreground for text colors on such elements. You shouldn’t really use bg-primary text-primary; you should use bg-primary text-primary-foreground.

The same idea is behind secondary, muted, and accent:

primary-foreground
secondary-foreground
muted-foreground
accent-foreground

Notice that secondary and accent are the same in the default theme.

border, input, and ring

ring and border are clear; by default, those are color-neutral-400 and color-neutral-200. With input, you might think that it’s the background color of the input, but that’s actually its border color, and by default, it’s color-neutral-200 (same as border):

Notice that the ring color is darker.

In Shadcn UI, you might also notice that ring for an input is used with a transparency modifier, like ring-ring/50, so it’s not really the true color of the ring.

card and popover

These two are related to the Card and Popover components and all components that use them. Both card and popover are color-white by default. card-foreground and popover-foreground are for the text on those components. By default, they both correspond to color-neutral-950 from Tailwind CSS.

card-foreground
popover-foreground

destructive

This is just red.

All sidebar colors in the default theme are related to the sidebar component and are mostly a copy of other existing variables. But I think the idea was to provide an example for a theme extension where a component has its own subset of colors.

Does it make sense?

After using it for a while, it all started to make sense eventually. But to make life easier for yourself, you could reference Tailwind CSS variables in your styles instead of the corresponding oklch colors. So instead of:

--foreground: oklch(0.145 0 0);

it should be:

--foreground: var(--color-neutral-950);

Since Tailwind CSS color naming is a lot easier to understand. This way, when you switch to your index.css file, you can get a color palette (some might argue that this way the editor won’t show the actual colors, but I think Tailwind-named colors are easier to read than the small square color boxes in an editor).