Making Context-Aware Components: How CSS inherit() Could Simplify Design Systems
Spotted on social media an ‘intent to ship’ post from the Blink team for a new inherit()
CSS function. A quick read of what is within the Chrome Platform Status post, and what was in the W3C specification had my mind buzzing on how this can help us to build better, more ‘context aware’ components for our Design Systems moving forward.
So, with some coffee and the ideas popping I thought I’d write a short post on the Johnny Ball of it all.
CSS custom properties already let developers expose and use values from a Design System (often surfaced as design tokens). A child element can read a value set on an ancestor simply by referencing the same custom property via the cascade, but that isn’t a direct way to reference the parent’s computed custom property for in‑place derivation. The new inherit()
function looks to help fill that gap. It will explicitly take the parent element’s computed custom‑property value so the child can derive a related value (for example, scale, offset, or colour mix) relative to the parent without duplicating tokens or relying on extra overrides.
inherit()
is a CSS Values Level 5 feature that reads a parent’s computed custom property (with an optional fallback, e.g. inherit(--foo, 16px)
). It returns the parent’s resolved value, and if the immediate parent doesn’t set one, the value comes from the nearest ancestor that does.
This parent-focused behaviour could be exactly what design systems need: components can derive spacing, density, colour and motion from their container, so you avoid duplicating tokens and get a predictable, context-aware user interface.
But, why not make more custom properties?
It’s tempting to create Design Tokens for everything you think you’d ever need and add bespoke custom properties everywhere, but inherit() could give us a cleaner alternative. Rather than proliferating tokens, derive local values from a small set of region-level properties so components can stay simple and predictable.
- A single source of truth: Region or page tokens (spacing, density, accent, motion) stay authoritative; components derive what they need instead of duplicating tokens.
- Context‑aware defaults: Components can adapt to their container (compact toolbar vs spacious area), padding, size and spacing update with no extra classes or JS.
- Fewer overrides: Set one token on a wrapper and everything inside can adapt with it, avoiding many special-case rules or variants.
A practical example, making “the card”: from custom properties to inherit()
Let’s look at a focused example where we can replace a multitude of bespoke custom properties with the new inherit()
function.
First, let’s take a simple card component:
<article class="card"><img src="" alt=""><div class="card__content"><h2>A Card Title</h2><p>Lorem ipsum, dolor sit amet consectetur adipisicing elit. Unde tempore veritatis illum voluptas.</p><a href="">Click here</a></div></article>
And traditionally (however, more common recently) we would make use of existing custom properties in our code (perhaps from Design Tokens), something like:
:root {--spacing-200: .5rem;--spacing-400: 1rem;--spacing-600: 1.5rem;}
Custom Properties Everywhere
The first version of our card will set every spacing value explicitly with a custom property.
.card {--card-spacing: var(--spacing-400);--card-gap: var(--spacing-200);--card-heading-gap: var(--spacing-400);padding: var(--card-spacing);}.card__content > * + * {margin-top: var(--card-gap);}.card__content h2 + * {margin-top: var(--card-heading-gap);}
One Custom Property to rule all spacing
We could tidy this up and reduce the duplication of custom properties inside the card by using some maths. The relationships are now explicit but also local.
.card {--card-spacing: var(--spacing-400);max-width: 300px;padding: var(--card-spacing);border: 1px solid #000;background: #fff;}.card__content > * + * {margin-top: calc(var(--card-spacing) * 0.5);}.card__content h2 + * {margin-top: var(--card-spacing);}
Or we could start using inherit()
.card {--card-spacing: var(--spacing-400);max-width: 300px;padding: inherit(--card-spacing);border: 1px solid #000;background: #fff;}.card__content > * + * {margin-top: calc(inherit(--card-spacing) * 0.5);}.card__content h2 + * {margin-top: inherit(--card-spacing);}
You can swap var()
for inherit()
here, but doing so on the card itself is redundant unless the parent provides a token.
Inheriting from the parent
Where this could truly be revolutionary on how we author CSS in component libraries and Design Systems is when can make use of an element’s parent custom properties. Say we have a generic container regions that has three padding options.
:root {--spacing-200: .5rem;--spacing-400: 1rem;--spacing-600: 1.5rem;}.region--compact { --gap: var(--spacing-200); }.region--default { --gap: var(--spacing-400); }.region--comfortable { --gap: var(--spacing-600); }
With inherit()
we can make use of the --gap
custom property so that our cards match the look and feel of the parent container.
.card {/* this will inherit from the parent --gap value */padding: inherit(--gap, 1rem);}.card__content > * + * {margin-top: calc(inherit(--gap, 1rem) * 0.5);}.card__content h2 + * {margin-top: inherit(--gap, 1rem);}
The container region sets the --gap
, the card makes use of it with inherit()
, cards inside .region--compact
become tighter, those in .region--comfortable
become more spacious automatically. There are no ‘per card’ overrides like .card--compact
or .card--comfortable
required.
We could, although I think it would make things more complex, also mix all options together. Making use of inherit()
for context-aware design decisions and local component specific custom properties.
:root {--spacing-200: .5rem;--spacing-400: 1rem;--spacing-600: 1.5rem;}.region--compact { --gap: var(--spacing-200); }.region--default { --gap: var(--spacing-400); }.region--comfortable { --gap: var(--spacing-600); }.card {--card-pad: inherit(--gap, 1rem);--card-gap: calc(inherit(--gap, 1rem) * 0.5);--card-heading-gap: inherit(--gap, 1rem);padding: var(--card-pad);}.card__content > * + * {margin-top: var(--card-gap);}.card__content h2 + * {margin-top: var(--card-heading-gap);}
Choices and Fallbacks
When you adopt inherit()
treat CSS custom properties as the runtime surface your components read from — not the canonical design-token source. Keep the runtime surface small: expose a few region-level properties that express layout and theme intent, and let components derive their implementation values from those.
At the region level expose a tight set of properties for values that change by context: —gap
, —density
, —base-size
, —accent
. These are the values components should inherit from their container and treat as authoritative for regional behaviour.
Inside components, keep implementation property names scoped and minimal (for example —card-gap
, —btn-pad
, —chip-accent
). Derive those values from the nearest ancestor using inherit()
rather than adding new global properties. Example:
.card {/* derive a card-scoped value from the region token */--card-gap: calc(inherit(--gap, 16px) * 0.75);padding: var(--card-gap);}
Use consistent units (rem
or px
) so calc()
is predictable, and include sensible fallbacks in inherit()
so components work when a region property is missing or in older browsers. Document ownership: list which properties belong on root, which on page/region wrappers, and which are component implementation details. That keeps naming clear and makes it obvious what inherit()
will read and what components compute.
Dealing with non‑supporting browsers
inherit()
will be a runtime cascade feature and support will vary while browsers implement it. The goal of any front-end developer is to make components that behave well everywhere today while progressively enhancing where CSS features like inherit()
become available.
Here’s a couple of ways to approach using inherit()
We could use @supports()
to detect the lack of inherit()
and provide a simple fallback path.
@supports (not (padding: calc(inherit(--gap)))) {.card { padding: var(--fallback-card-pad, 12px); }}
We could provide some defaults first and then place the inherit()
rule afterwards
.card {/* baseline for old browsers */padding: var(--card-pad, 1rem);/* enhanced when inherit() is available (overrides the baseline) */padding: inherit(--gap, var(--card-pad, 1rem));}
Where to "+1" implementation
If you want this CSS feature implemented into web browsers you can upvote them here:
To end…
The new inherit()
CSS feature can make your components naturally context-aware. For Design Systems I think it promises the opportunity for fewer design tokens, fewer overrides and simpler CSS-first adaptations to layouts, typography, theming, and motion.