The Power of Contextual Background Color

One of the most fun “OG” features of CSS is currentColor. It’s one of those features whose real value only really shows up when your application matures and you start building components that are actually reusable.

For example, when building generic components like <Badge>, currentColor helps make them incredibly adaptable. The border and background can just say “I want to be whatever the text color is”, and we only need to change the text color for all properties to follow.

.badge {
    color: currentColor;
    border: 1px solid currentColor;
    /* Use opacity to create a tint */
    background: color-mix(in srgb, currentColor, transparent 90%);
}
Success
Error
Info

currentColor gives us Contextual Styling: components that are flexible and adaptable to their surroundings. It’s like using em for sizing, but for color. Where you live changes how you look.

But that’s the easy case: color is an inherited property. So what do we do when we need this same “context awareness” for properties that don’t inherit, like background-color?

The Challenge

I recently wanted to create a “scroll shadow” effect for content blocks. That classic subtle fade at the top or bottom of a list indicating there is more to scroll.

To implement this, I needed a gradient that fades from transparent to the background-color of the card. But there’s a catch: there are multiple card styles. Some are white, some are off-white, some are blue-tinted.

A light card with a shadow at the bottom
Each card defined the background to itself.

And who said this component will always live in a card?

If I want a reusable ScrollShadows component, it must not know about white or #fef3c7. It needs to know the closest background color applied in its parent elements.

The Missing currentBackgroundColor

You might be thinking, “If only CSS had a currentBackgroundColor variable…” and indeed, there have been long discussions about this. But the community realized that a generic inherit() function would be a better choice for the long term.

A Solution that Works Today

Until we get a native keyword, we have to build it ourselves. Surprisingly (and luckily!), it’s pretty simple with CSS Variables.

Let’s define a new variable: --current-background-color. The strategy is simple: whenever you set a background color, you also set this variable.

.card-tinted {
    --current-background-color: #fef3c7;
    background-color: var(--current-background-color);
}

.card-blue {
    --current-background-color: #dbeafe;
    background-color: var(--current-background-color);
}

Now, any child component can use var(--current-background-color) to blend perfectly with its parent.

.scrollShadows {
    &::before,
    &::after {
        content: '';
        /* ... all other properties ... */
        background: linear-gradient(
            to var(--_gradient-direction),
            var(--current-background-color),
            transparent
        );
    }
}

Here is the <ScrollShadow> component in action, adapting to three different parents. The component doesn’t receive any color-related props; it simply adapts to its surroundings.

White Background

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Amber Background

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Blue Background

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

And of course, if the variable isn’t defined, it falls back to transparent (or whatever default you define), which effectively and gracefully disables the effect.

Handling Transitions

What if my card’s background-color changes on hover? The browser knows how to transition between different background colors. But our <ScrollShadow> uses gradients which are effectively images - and the browser can’t magically transition between two unrelated images.

But wait, how does the browser know how to transition between two background colors?

That’s because background-color expects values of type <color>, which the browser knows how to interpolate. Colors are just numbers in a defined color-space, so the browser calculates every value in between the start and end colors.

To the browser, the values in --current-background-color are just arbitrary, meaningless strings. They are implicitly accepted by background-color because the values are valid, but the browser can’t interpolate our values for this variable.

If only we could tell the browser that our variable is actually a color…

Well, we can!

@property --current-background-color {
    syntax: '<color>';
    inherits: true;
    initial-value: transparent;
}

By registering our variable as a color, the browser can now interpolate it, just like it interpolates any other color.

Now we can declare a transition on the variable itself, since the browser knows how to handle it.

.fade-overlay {
    /* ... */
    transition: --current-background-color 0.2s;
}

In the demo below, try changing the background color of the card. Thanks to the @property declaration, the scroll shadow gradient smoothly transitions along with it.
You can also toggle the property declaration to see how broken it looks when the browser does not know it’s a color.

Interactive Card

Try switching the colors above! Notice how the fade gradient at the bottom smoothly transitions to the new background color.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

A more philosophical take

Using variables makes relationships explicit, which makes the code feel self-documenting. If your page background is #eee (light gray) and you want your buttons to be brighter, you might use white.

But why white specifically?

And what about cases where the button is rendered in a colorful container?

And what about dark mode?

You can use that arbitrary white, or you can explicitly define the relationship between the button and its context.

To dynamically achieve the same white using only variables - capturing the meaning behind that gray, “the formula” of your design hierarchy - you can simply do:

.element {
    background-color: hsl(
        from var(--current-background-color)
        h s calc(l + 7)
    );
}

Your button is not white - it is 7% brighter than its background color. Always. Even if it’s rebeccapurple.


Last words

Writing CSS can sometimes feel like a pile of random declarations, colors, and lengths. That’s where people start calling CSS unreadable or unmaintainable. The moment we start talking in relationships, the whole equation changes.

You already know how powerful the em unit is for sizing. Your components become literally drop-in for any layout, with no compromises. Using --current-background-color gives you the same exact experience, but with colors. It’s something even Figma doesn’t allow designers to define yet - but we frontend developers have the power to do so.

Since adopting a variable like this is so easy, I highly encourage you to give it a try in your project.

Keep magic colors to a minimum, just as we do with magic numbers.