
Thinking Deeply About Theming and Color Naming
Thinking Deeply About Theming and Color Naming ź“ė Ø
As a front-end developer, Iāve been pretty curious about how other people code up their websites. So I tend to poke my head into design systems whenever I find one.
Then, late last year, a conversation with Geoff Graham set me off thinking even deeper about theming websites. (If you donāt know that dude, heās the chief editor of this site, so that guyās a pretty big deal, too.)
So Iāve been watching, pondering, and exploring:
- How can we create better themes?
- How can we allow increased flexibility in theming?
- How can we allow more colors to be used so that sites can be more alive and dimensional instead of being so flat all the time?
Today, I want to discuss a couple of patterns that the community is using, and how I propose we can improve, so we achieve both flexibility and beauty.
Hope youāre ready to go on a wild ride with me!
Color Palettes
Letās begin from the beginning. After all, how can you discuss theming without including colors into the site?
I think this problem is pretty much solved by now. Everyone has adopted systems that allows for various hues ā along with multiple tints and shades ā that can give some life to the design.
We donāt need to go very far to see this trend happening. For example, Tailwind CSS includes a ton of colors and their respective tones.

Open Props by Adam Argyle provides even more tones, up to 13 per color.

And Pico CSS ups the ante by introducing 19 different tones per color.

Er⦠this is not a race, so the number of tones doesnāt really matter. Whatās important is you get sufficient color tones to create subtle effects for various parts of the design.
Designing Your Own Palette
Color palettes provided by these libraries and frameworks can be good starting points, but Iād argue that you almost never want to use them.
Why?
Because colors create differentiation; differentiation creates distinction; distinction creates identity.
You probably know this is true without me telling you.
- Sites that use Bootstrap, look like Bootstrap.
- Sites that use Tailwind, look like Tailwind.
- Sites that use shadcn, look like that tooā¦
Of course there are makers who can break the mould, use Tailwind, and make it completely not like Tailwind. But thatās because they tweak many things.
Color is one of these things ā one of the most important ones ā but other important pieces include typography, spacing, the roundness of your corners⦠and many others. Covering those is a story for another day, and perhaps best covered by Jack McDade who teaches Radical Design.
So, if you donāt wanna drown in the sea of sameness ā looking like everyone else ā creating your own color palettes is a first step forward.
Now, you may be anxious about creating color palettes because thereās been lots of writing about the amount of work that goes into creating accessible color palettes, so that might sound like a daunting task.
Plus, anything related to accessibility carries āBig Potential Consequencesā and āHighly Shameful When Done Incorrectly,ā so that can add extra pressure on you.
Throw all those pressures away.
Donāt be scared.
Because if you want to create a corner of the internet that looks like you (or your company), breathes like you, acts like you, and exudes fun like you do, then gotta do what you gotta do.
There are only two words you have to remember.
Just two words.
Sufficient contrast.
And youāre set for accessibility (design-wise, at least).
Thatās it.
Designing Color Palettes by Hand
I tend to design color palettes by hand ā in Figma ā when I design my websites. This seems to be the most natural process for me. (Or maybe Iām just influenced by how Jack designs stuff š).
If you do this, thereās no need to stress about filling up tones from 50 to 950. Thatās because youāll have no idea what colors would look nice before you fit them into the design. Stressing over tones is putting the cart before the horse.
Hereās a decent example. When I designed Splendid Labz, I omitted a ton of color tones. Hereās an example of the pink color variables for the site.
oklch()
.- Notice I skipped values between 50 and 400? Well, I didnāt need āem.
- Notice I added
200d
and600d
? Well, I kinda wanted a desaturated (or muted) variant of these colors⦠which⦠could not be captured the existing systems. So I addedd
for desaturated š.
You can see the results of that yourself. Itās not too shabby in my opinion ā with splashes of color that perhaps bring some joy to your face when you scroll through the site.

You get the drift, yeah? Itās not too hard. So, donāt be scared and give that a try.
Designing Color Palettes Programmatically
If youāre the kinda person that prefers generating colors programmatically (and of course you can hand-tweak them afterwards if you desire), here are a few generators you may fancy:


Of these, I highly recommend checking out @meodai
ās RampenSau because heās really knowledgeable about the color space and does incredible work there. (Use the monochromatic feature to make this easy.)
Using the Color Palettes
A thousand words later, weāre finally getting to the meat of this article. š
With a seemingly unlimited amount of options given by the color palettes, it makes sense to assume that we can use them however we want ā but when it comes to application, most systems seem to fall short.
(Even short is generous. They actually seem to be severely restricted.)
For example, DaisyUI seems to support only two tones per colorā¦

Pico CSS, a system with one of the most options, on first glance, limits to 10 possible variants āsemantic class namesā.

But if you look deeper, weāre still looking at about two tones per āthingā:
- Primary (one tone)
- Background and background hover (two tones)
- Border and border hover (two tone)
- Underline (is this the same as border? More on this below.)
- And so onā¦
Which brings me to one very important and very puzzling question:
If colors are so important, why do these frameworks allow only the utilization of so few colors?
I canāt answer this question because Iām not the creators behind those systems, but Iād guess these might be the possible causes:
- These system designers might not be as sensitive to colors as visual designers.
- Semantic class name confusion.
- Values were simply meant as guidelines.
The second one a serious, and limiting, issue that we can deal with today.
As for the first, Iām not saying Iām a great designer. Iām simply saying that, with what I know and have discovered, something seems to be amiss here.
Anyway, letās talk about the second point.
Semantic Class Name Confusion
Observing the āsemantic class namesā these systems use actually unveil underlying confusion about what āsemanticā means to the web development community.
Letās go back to my remark about the --pico-primary-underline
variable earlier with Pico CSS.
But if you look deeper, weāre still looking at about two tones per āthingā
- Primary (one tone)
- Background and background hover (two tones)
- Border and border hover (two tones)
- Underline (is this the same as border? More on this below)
- And so onā¦
Isnāt that an interesting remark? (I ask this question because underline
and border
can use the same color to create a unifying effect).
From what we can see here, the term āsemanticā actually means two things conflated into one:
- An order of hierarchy (
primary
,secondary
,tertiary
, etc) - The āthingā it was supposed to style
This gets even more confusing because the order of hierarchy can now be split into two parts:
- A color-specific order (so
primary
meansred
,secondary
meansblue
, and so on) - A use-case specific order (so a
heavy
button might beprimary
, while alight
button might besecondary
)

Okay. I can hear you say ānaming is hard.ā Yes, thatās the common complaint. But ānaming is hardā because we conflate and reduce things without making distinctions.
I propose that:
- We keep the hierarchy (
primary
,secondary
,tertiary
) to the color hue. - We name the strength, āoomph,ā or weight of the button with a verb that describes their relative weight or appearance, like
outline
,light
,heavy
,ghost
, etc.

We can create the appearance variations easily with something I call The Pigment System. But perhaps thatās an article for another day.
Anyway, by creating this separation, we can now create a wealth amount of color combinations without being restricted by a single hierarchical dimension.
Moving onā¦
The Second Problem With Semantics
Using the same example (just for simplicity, and definitely not trying to bash Pico CSS because I think theyāre doing a really good job in their own right), we see that semantics are conflated by stating its hierarchy along what its supposed to style.
Examples are:
--pico-primary-background
--pico-primary-border
These two properties result in a problem when designing and developing the site later. If you consider these questions, youād see the problems too:
First: By using --pico-primary-background
ā¦
- Does it mean we only have one main background color?
- What if we need other colors? Do we use
--pico-secondary-background
? - What if we need more? Do we use
tertiary
(3rd),quaternary
(4th),quinary
(5th), andsenary
(6th) for other colors?
Second: What if we have variants of the same color? Do we use things like --pico-primary-background-1
, 2
, 3
, and so on?
Third: Now, what if I need the same color for the --pico-primary-background
and the --pico-primary-border
of the same component? But Iād need another color for a second one?
This starts getting confusing and āsemanticsā begins to lose its meaning.
What Semantics Actually Mean
Consulting Etymology and the dictionary gives us clues about how to actually be semantic ā and keep meaning.


Two things we can see here:
- Semantics mean to indicate by a sign.
- It can be related to meaning or logic.
What Iām noticing is that people generally ascribe āsemanticsā to words, as if only words can convey meanings and numbers cannotā¦
But what if we broaden our scope and view numbers as semantic too ā since we know 100 is a much lighter tint and 900 is a dark shade, isnāt that semantics showing through numbers?
Better Semantics
We already have a perfectly usable semantic system ā using numbers ā through the color palettes.

This is highly semantic!
What we simply need is to adjust it such that we can use the system to easily theme anything.
How? Simple.
I made the argument above that the hierarchy (primary
, secondary
, etc.) should be used to refer to the colors.
- Then, if you have use
pink
color as your main (henceprimary
) color⦠- You can simply set another color, say
orange
as yoursecondary
color!
(Duh? Yeah, itās obvious in hindsight.)
Implementing this into our code, we can do a one-to-one port between hierarchy and hues. If you do this via CSS, it can be manual and not very funā¦
.theme-pink {
--color-primary-100: var(--color-pink-100);
--color-primary-200: var(--color-pink-200);
--color-primary-300: var(--color-pink-300);
/* and so on ...*/
--color-secondary-100: var(--color-orange-100);
--color-secondary-200: var(--color-orange-200);
--color-secondary-300: var(--color-orange-300);
/* and so on ...*/
}
With Sass, you can run a quick loop and youāll get these values quickly.
$themes: (
pink: (
primary: pink,
secondary: orange
)
);
$color-tones: 100, 200, 300, 400, 500, 600, 700, 800, 900;
@each $theme-name, $theme-colors in $themes {
.theme-#{$theme-name} {
@each $tone in $color-tones {
--color-primary-#{$tone}: var(--color-#{map-get($theme-colors, primary)}-#{$tone});
--color-secondary-#{$tone}: var(--color-#{map-get($theme-colors, secondary)}-#{$tone});
}
}
}
For Tailwind users, you could do a loop via a Tailwind plugin in v3, but Iām not quite sure how you would do this in v4.
// The plugin code
const plugin = require('tailwindcss/plugin')
module.exports = plugin(function ({ addUtilities, theme }) {
const splendidThemes = theme('splendidThemes', {})
const palette = theme('colors')
// Collect all unique tone keys used by any color in any theme
const allTones = new Set()
Object.values(splendidThemes).forEach(themeConfig => {
Object.values(themeConfig).forEach(colorName => {
if (palette[colorName]) {
Object.keys(palette[colorName]).forEach(tone => allTones.add(tone))
}
})
})
const utilities = {}
Object.entries(splendidThemes).forEach(([themeName, themeConfig]) => {
const themeClass = {}
Object.entries(themeConfig).forEach(([role, colorName]) => {
if (!palette[colorName]) return
allTones.forEach(tone => {
if (palette[colorName][tone] !== undefined) {
themeClass[`--color-${role}-${tone}`] =
`var(--color-${colorName}-${tone})`
}
})
})
utilities[`.theme-${themeName}`] = themeClass
})
addUtilities(utilities)
})
// Using it in Tailwind v3
module.exports = {
plugins: [
require('path-to-splendid-themes.js')
]
theme: {
splendidThemes: {
pink: {
primary: 'pink',
secondary: 'orange'
},
blue: {
primary: 'blue',
secondary: 'purple',
tertiary: 'orange'
}
}
},
}
Will this generate a lot of CSS variables?
Yes.
But will in affect performance?
Maybe, but Iād guess it wonāt affect performance much, since this code is just a couple of bytes more. (Images, by contrast, weigh thousands of times more than these variables do.)
And now we no longer need to worry about knowing whether background-1
or background-2
is the right keyword. We can simply use the semantic numerals in our components:
.card {
background-color: var(--color-primary-500)
}
.card-muted {
background-color: var(--color-primary-700);
}
One More Note on Semantics
I think most frameworks get it right by creating component-level semantics. This makes a ton of sense.
For example, with Pico CSS, you can do this:

In your own creations, you might want to reduce the amount of namespaces (so you write less code; itās less tedious, yeah?):
.card-namespaced {
--card-header-bg: var(--color-primary-600);
}
.card-without-namespace {
--header-bg: var(--color-primary-600);
}
No āextra semanticsā or even ānamespacingā needed when the project doesnāt require it. Feel free to Keep It Simple and Sweet.
This brings me to a separate point on component vs global variables.
Global Variables
Some variables should be global because they can propagate through the entirety of your site without you lifting a finger (that is, once you design the CSS variables appropriately).
An example of this with borders:
:root {
--border-width: 1px;
--border-style: solid;
--border-color: var(--color-neutral-700);
}
You can change the global --border-color
variable and adjust everything at once. Great!
To use this sorta thing, you have to build your components with those variables in mind.
.card {
border: var(--border-width) var(--border-style) var(--border-color);
}
This can be easily created with Tailwind utilities or Sass mixins. (Tailwind utilities can be convenient Sass mixins).
@utility border-scaffold {
border: var(--border-width) var(--border-style) var(--border-color);
border-radius: var(--radius);
}
Then we can easily apply them to the component:
.card {
@apply border-scaffold;
}
To change the theme of the card, we can simply change the --border-color
variable, without needing to include the card-border
namespace.
.card-red {
--border-color: var(--color-red-500);
}
This way, authors get the ability to create multiple variations of the component without having to repeat the namespace variable. (See, even the component namespace is unnecessary.)
.pico-card-red {
--pico-card-background-color: var(--color-red-500);
}
.card-red {
--bg-color: var(--color-red-500);
}
Now, I know weāre talking about colors and theming, and we segued into design systems and coding⦠but can you see that thereās a way to create a system that makes styling much easier and much more effective?
Well, Iāve been pondering this kinda thing a lot over at Splendid Labz, specifically in Splendid Styles. Take a look if you are interested.
Enough tooting my own horn! Letās go back to theming!
I think here are some other values that you might want to consider in your global variables:
:root {
--border-width: ...;
--border-style: ...;
--border-color: ...;
--outline-width: ...;
--outline-style: ...;
--outline-focus-color: ...;
--outline-offset: ...;
--transition-duration: ...;
--transition-delay: ...;
--transition-easing: ...;
}
How Important is All of This?
It depends on what you need.
People who need a single theme can skip the entire conversation we hashed out above because they can just use the color palettes and call it a day.
.card {
background: var(--color-pink-500);
color: var(--color-pink-900);
}
For those who need multiple themes with a simple design, perhaps the stuff that Pico CSS, DaisyUI, and other frameworks have provided is sufficient.

What it takes to create a DaisyUI Theme.
Side Rant: Notice that DaisyUI contains variables for --color-success
and --color-danger
? Why? Isnāt it obvious and consistent enough that you can use --color-red
for errors directly in your code? Why create an unnecessary abstraction? And why subject yourself to their limitations? Anyway, rant end. You get my point.
For those who want flexibility and lots of possible color shades to play with, youāll need a more robust system like the one I suggested.
This whole thing reminds me of Jasonās Cohenās article, āRare things become common at scaleā: what is okay at a lower level becomes not okay at a larger scale.
So, take what you need. Improve what you wish to. And may this help you through your development journey.
If you wanna check out what Iāve created for my design system, head over to Splendid Styles. The documentation may still be lacking when this post gets published, but Iām trying to complete that as soon as I can.
And if youāre interested in the same amount of rigour Iāve described in this article ā but applied to CSS layouts ā consider checking out Splendid Layouts too. I havenāt been able to look back after I started using it.
Have fun theming!