✦ Directorium ✦

✦ Settings ✦

Max Media Rating
Enable Grawlix (Profanity filter)
Disable Shaders
Blog Settings
Blog Theme
Background Color
Text Color
Accent Color

Tips, Tricks & Tutorials

CSS-Only Theme Generator

Let's look at how we can build a simple, but effective theme system with nothing but CSS and HTML data attributes!

So, you wanna make a CSS-only theme generator, huh?

Well, I suppose I can help you. For a price...

Something more valueable than gold: a few minutes of your time.

But really, this is a fun project, and I hope you enjoy the process!


Quick Version:

  1. Create a CSS rule for a :root {} or [data-theme] {}.

  2. Create a set of base colors to edit at a later time. (--color-primary: red;, etc.)

  3. Create a series of color-mix() properties with light/dark colors.

    • --color-primary-light: color-mix(var(--color-primary), white 60%);
    • --color-primary-mid: var(--color-primary);
    • --color-primary-dark: color-mix(var(--color-primary), black 60%);
  4. Create a unique [data-theme="theme1"] {} rule and only assign the base color values.

    • --color-primary: blue;
  5. Add a data-theme="theme1" attribute to your <html> tag.

    • <html data-theme="theme1">
  6. Apply the generated colors to your CSS rules as you would any other property:

    • background: var(--color-primary-light);

By the end you should have something like this:

Css
[data-theme] {
  /* Base colors */
    --color-primary: red;
    
    /* Generator */
    --color-primary-light: color-mix(var(--color-primary), white 60%);
    --color-primary-mid: var(--color-primary);
    --color-primary-dark: color-mix(var(--color-primary), black 60%);
}

/* Custom themes */
[data-theme="theme1"] {
  --color-primary: blue;
}

[data-theme="theme2"] {
  --color-primary: orange;
}

body {
  background: var(--color-primary-light); /* Should show as a light blue. */
}

You can add multiple [data-theme="custom-name"], and simply change the value in the <html> tag to switch themes! Example: <html data-theme="theme5">

There's a ton you can do with this one, so read further to get all the details!


Getting Started

Introduction

This "generator" relies on HTML data attributes and CSS custom properties (which I'll shorten to "properties" or change to "variables", generally).

HTML Data Attributes

HTML data attributes are just a syntax accepted by the spec to add any attribute you want to an HTML element. You can use any pattern you want, but it's highly recommended to use the data-<custom-name> syntax to denote things added by you.

In this case, because I used Skeleton UI, I got used to their method of adding themes to HTML. It's a simple, but effective solution that just requires you to add the data-theme attribute to your <html> tag. For example: <html data-theme="forest">.

This method may not work for every type of site or app, but it can be easily altered via JavaScript if you wanted to add a UI theme switcher instead of manually altering the HTML.

CSS Custom Properties

CSS Custom Properties are CSS's answer to variables. They can contain virtually any value and you can get really deep with them if you wanted, but on the surface, they are still very powerful. For example, if you wanted a globally accessible padding value for all your elements that you could change from a single source, you could do something like:

Css
:root {
  --padding-base: 32px;
}

body {
  background: blue;
  padding: var(--padding);
}

.panel {
  padding: var(--padding);
}

Then, all you have to do is either change the original value or set it again in a parent element to affect all references.

CSS color-mix Values

The one big thing that we need that ties the whole thing together, and really makes it work. CSS has a relatively new feature called color-mix. You can use a color-mix as a value in a CSS property that uses color values.

The color-mix syntax is a bit odd, but it is very powerful.

Css
color-mix(
  <interpolation>, /* in srgb, in oklch, in lab, etc. */
    <color> <mix-percentage>,
    <color> <mix-percentage>,
);

There are a ton of interpolation methods and you can find a decent list here.

Here's a small example of its usage:

Css
color: color-mix(in srgb, red, white 50%);

The above example would output a pink-ish color!

So, let's mix all this stuff and make the generator!

Making the Generator (Finally!)

So, we gotta blend all that stuff together into a simple theme system.

First, at the top of your main CSS file, or (preferably) in a new CSS file for later importing, add a :root or [data-theme] selector to make the values within globally accessible by the rest of your CSS:

Css
[data-theme] {}

Now, we need to create some CSS custom properties to define our palette. We can add as many as we want in whatever colors we want, just keep in mind that the more colors we add, the more we'll have to copy/paste later. Keeping the conventions of things like Tailwind, we can prefix the color name with color- to keep it separate from other values that might have a similar name.

You could also suffix them with -base if you like, but it's entirely up to you how you name these things! (I'd just keep it consistent.)

Css
[data-theme] {
  --color-primary: orange;
}

Alright, now for the "magic" part. We can use color-mix with custom properties to automatically adjust the colors based on what parameters we want. That way, we only have to adjust a value or two instead of every single color.

At this point you can decide on your final naming convention. For simplicity, I'll use only three colors per set using the -light, -mid, -dark convention. You could also use Tailwind's: -50, -100, -200, -300, -400, etc.

You can literally do what you feel is best here as long as you have a way to indentify the range of color values.

Css
[data-theme] {
  /* Base colors */
  --color-primary: orange;
    
    /* Generator */
    --color-primary-light: color-mix(in srgb, var(--color-primary), white 60%);
    --color-primary-mid: var(--color-primary); /* Unchanged midtone */
    --color-primary-dark: color-mix(in srgb, var(--color-primary), black 60%);
}

If you went for more colors, the bad news here is that you'll need to copy/paste and rename for each color. But, this is the basis of the whole idea! Hopefully, some day we'll get functions/loops of some kind in CSS to shorten this more.

So, if you're wondering at this point what was the point of all this work? The benefit is now, you could transpant that code in any site, so if you had a consistent way you like to work, you can just move it from project to project.

Applying Themes To Your Site

As stated in the introduction, if you have not done so, add the data-theme attribute with a vlue of the theme name you plan to use to the <html> tag of your site.

You can also just use <html data-theme> if you just plan to use the base values you set up.

Then, if you created this as an external CSS file, import it at the top of your main CSS file:

Css
@import "/path/to/your/theme-generator.css";

And just apply the color variables anywhere you want to use them!

Css
@import "/path/to/your/theme-generator.css";

body {
  background: var(--color-primary-light);
}

The value in this is that if you ever wanted to change the primary color of your theme, you don't have to repeat the whole process to generatoe your colors, you just have to overwrite the base color!

There are a few way to do this, the first of whish is just adding another [data-theme] {} selector to your main CSS file and updating the values:

Css
@import "/path/to/your/theme-generator.css";

[data-theme] {
  --color-primary: rebeccapurple;
}

body {
  background: var(--color-primary-light); /* Changes to light purple */
}

The second way is to create a named theme! Simply pass-in a value to the data-theme and selector: <html data-theme="my-theme">.

Css
@import "/path/to/your/theme-generator.css";

[data-theme="my-theme"] {
  --color-primary: rebeccapurple;
}

body {
  background: var(--color-primary-light); /* Changes to light purple */
}

The second option gives you a way to create as many theme variations as you want and access them by name in HTML without touching the CSS at all unless you wanna adjust something.


Advanced Example

So, I mentioned before that I got very used to using Skeleton UI's conventions, so let's take a look at creating a very basic theme loosely based on how their design system operates. It's not too hard, but it might be a bit much if you're only just starting to learn custom properties.

The color palette will consist of 6 (as opposed to their 7 colors):

  • Primary - The main/brand color
  • Accent - A secondary color
  • Success - A green-ish color to denote positive feedback
  • Warning - A yellow-ish color to denote positive feedback
  • Error - A red-ish color to denote positive feedback
  • Surface - A general background color for the site

We can also add options to help with the color mixing process without manual editing:

  • Mode - The color interpolation of the mix
  • Light - The light base color of the mix
  • Dark - The dark base color of the mix
theme-generator.css
[data-theme] {
  /* Mix options */
    --color-mix-mode: srgb;
    --color-mix-light: white;
    --color-mix-dark: black;
    
    /* Base colors */
  --color-primary: skyblue;
    --color-accent: violet;
    --color-success: lime;
    --color-warning: goldenrod;
    --color-error: tomato;
    --color-surface: lightslategray;
    
    /* Generator */
    --color-primary-1: color-mix(in var(--color-mix-mode), var(--color-primary), var(--color-mix-light) 75%);
    --color-primary-2: color-mix(in var(--color-mix-mode), var(--color-primary), var(--color-mix-light) 25%);
    --color-primary-3: var(--color-primary); /* Unchanged midtone */
    --color-primary-4: color-mix(in var(--color-mix-mode), var(--color-primary), var(--color-mix-dark) 25%);
    --color-primary-5: color-mix(in var(--color-mix-mode), var(--color-primary), var(--color-mix-dark) 75%);
    
    --color-accent-1: color-mix(in var(--color-mix-mode), var(--color-accent), var(--color-mix-light) 60%);
    --color-accent-2: color-mix(in var(--color-mix-mode), var(--color-accent), var(--color-mix-light) 25%);
    --color-accent-3: var(--color-accent); /* Unchanged midtone */
    --color-accent-4: color-mix(in var(--color-mix-mode), var(--color-accent), var(--color-mix-dark) 25%);
    --color-accent-5: color-mix(in var(--color-mix-mode), var(--color-accent), var(--color-mix-dark) 60%);
    
    --color-success-1: color-mix(in var(--color-mix-mode), var(--color-success), var(--color-mix-light) 60%);
    --color-success-2: color-mix(in var(--color-mix-mode), var(--color-success), var(--color-mix-light) 25%);
    --color-success-3: var(--color-success); /* Unchanged midtone */
    --color-success-4: color-mix(in var(--color-mix-mode), var(--color-success), var(--color-mix-dark) 25%);
    --color-success-5: color-mix(in var(--color-mix-mode), var(--color-success), var(--color-mix-dark) 60%);
    
    --color-warning-1: color-mix(in var(--color-mix-mode), var(--color-warning), var(--color-mix-light) 60%);
    --color-warning-2: color-mix(in var(--color-mix-mode), var(--color-warning), var(--color-mix-light) 25%);
    --color-warning-3: var(--color-warning); /* Unchanged midtone */
    --color-warning-4: color-mix(in var(--color-mix-mode), var(--color-warning), var(--color-mix-dark) 25%);
    --color-warning-5: color-mix(in var(--color-mix-mode), var(--color-warning), var(--color-mix-dark) 60%);
    
    --color-error-1: color-mix(in var(--color-mix-mode), var(--color-error), var(--color-mix-light) 60%);
    --color-error-2: color-mix(in var(--color-mix-mode), var(--color-error), var(--color-mix-light) 25%);
    --color-error-3: var(--color-error); /* Unchanged midtone */
    --color-error-4: color-mix(in var(--color-mix-mode), var(--color-error), var(--color-mix-dark) 25%);
    --color-error-5: color-mix(in var(--color-mix-mode), var(--color-error), var(--color-mix-dark) 60%);
    
    --color-surface-1: color-mix(in var(--color-mix-mode), var(--color-surface), var(--color-mix-light) 60%);
    --color-surface-2: color-mix(in var(--color-mix-mode), var(--color-surface), var(--color-mix-light) 25%);
    --color-surface-3: var(--color-surface); /* Unchanged midtone */
    --color-surface-4: color-mix(in var(--color-mix-mode), var(--color-surface), var(--color-mix-dark) 25%);
    --color-surface-5: color-mix(in var(--color-mix-mode), var(--color-surface), var(--color-mix-dark) 60%);
}

This is a lot of setup, but it puts us in a good place later when we need to alter colors or other values. I'm sure there is a simpler way to handle the repetitive parts, but for the time being, this is what I've got for you.

Now, you can import the generator and create themes where you can also change the light/dark mixing colors and the interpolation mode without opening the generator file!

This is by no means a perfect system, but for doing things in a repeatable and simple way, this is pretty solid in my opinion. Once you have a generator file that you really like, you can just bring to any new project you like and just use the same theme variables you've been using!


Grawlich.art © Kevin Grawlich 2024 - 2026
App Version: 1.3.0-beta
Coded from scratch with by Grawlich.