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:
- each time I add a new Shadcn UI component to the project, it would require me to do such renamings;
- if the project color theme ever changes, I would need to fix colors in many places;
- I’d have to remember the specific colors I used.
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).
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
:
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.
destructive
This is just red.
sidebar-*
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).