In my previous post, we explored why currentColor is one of the most powerful tools for keeping your stylesheets DRY. By inheriting the text color of a parent element, components can adapt to their surroundings instantly.
But what if we want to take that inherited color and modify it? What if we want a hover state that is 10% darker than the inherited color, or a background that is the inherited color at 15% opacity?
If you had told me a few years ago that I would be building dynamic, scalable themes to do exactly this without touching a single SASS color function, I wouldn't have believed you. Like many developers, darken() and lighten() were baked into my muscle memory. However, the build-step overhead of pre-processors is becoming harder to justify. By pairing the classic currentColor keyword with the new oklch() relative color syntax, we now have a way to calculate context-aware themes entirely natively.
The Problem with Pre-processors
The biggest flaw of build-time CSS pre-processors has always been that they are blind to the DOM. A SASS function can never evaluate the contextual color of an element at runtime. It compiles to a static hex code before the browser ever sees it.
If you are building a classless CSS framework or a highly portable component library, you want the user to drop in one base color and have the entire system react organically. Pre-processors cannot do this dynamically based on the cascade. Native CSS can.
Enter OKLCH
Before we manipulate our colors, we need to talk about the color space we are using. OKLCH is a perceptually uniform color space. This means a 10% drop in the lightness channel (l) looks identical to the human eye whether the base color is a bright yellow or a dark blue.
This makes it the absolute best color space for programmatic theming. You can confidently write a single rule to darken an element on hover, knowing it will look correct regardless of the underlying theme color.
The Relative currentColor Playbook
Modern CSS relative color syntax allows you to take an origin color and extract its channels. The syntax looks like this: oklch(from <origin-color> l c h).
When we use currentColor as our origin, the magic happens. Here are the two most practical ways to use this right now.
1. The Alpha Channel Trick
Creating a subtle, translucent background for a badge or active state usually requires creating a brand new custom property. With relative color syntax, we can just extract the inherited text color and apply an alpha transparency.
.badge {
color: var(--theme-primary);
/* Inherits the text color, but drops opacity to 15% */
background-color: oklch(from currentColor l c h / 15%);
border: 1px solid currentColor;
}If the .badge is placed inside a container with a different text color, the background tint automatically updates to match the new environment.
2. The Dynamic Hover State
You can use the CSS calc() function inside the relative color syntax to shift the lightness on the fly. This completely replaces the need for SASS darken().
.button-outline {
color: var(--theme-primary);
border: 2px solid currentColor;
transition: all 0.2s ease;
}
.button-outline:hover {
/* Darkens the inherited color by 10% on hover */
color: oklch(from currentColor calc(l - 0.1) c h);
/* The border automatically follows suit because it uses currentColor! */
}The "One-Variable Component"
By combining these concepts, you can build complex UI components, like cards with text, icons, background tints, and hover borders, that are entirely themed just by setting color: var(--theme-color) on the parent wrapper. Every internal element just feeds off currentColor and adjusts its own lightness or opacity accordingly.
Browser support for relative color syntax is solidifying rapidly across all major engines. We are finally reaching the point where native CSS provides everything we need to build robust, scalable design systems. It is time to rethink how we handle color on the web, and leave the pre-processors behind.
