2026 CSS wishlist
In 2024, I started the year with my CSS wishlist. Since then, many of those wishes have been fulfilled, particularly in 2025. Style queries, the @scope rule, anchor positioning, and view transitions are all (almost) available in all browsers. This year I want to switch it up a bit and describe a few features that I’m looking forward to and ultimately what I think they will allow us to do with CSS.
Mixins
I want to be able to create presentational abstractions that only exist in CSS. “Wait,” you might say, “isn’t that how CSS already works?” While, yes, we do use CSS to create rules for various selectors, which can be seen as their own abstraction, those are dependant on the matching HTML. Today, there’s no real way to create a ruleset abstraction in CSS, unless you want to abuse @keyframes.
CSS Mixins would allow me to create a presentational abstraction, then apply it to multiple selectors throughout my stylesheet.
<style>
@mixin --button() {
/* Button styles */
}
button {
@apply --button();
}
.button {
@apply --button();
}
</style>
<button>A real button</button>
<a href>A link that looks like a button</a>
<my-button>
<template shadowrootmode=open>
<style>
:host {
@apply --button();
}
</style>
</template>
</my-button>
Currently, spec work for CSS Mixins has already begun and they have an early prototype in Chromium-based browsers.
Functions
I want to be able to include the algorithms that I use to create typescales, colour palettes, etc. directly in my CSS. I don’t want to need to use static custom properties for this. I want to be able to get the fifth step of my typescale with my own custom CSS function.
@function --my-typescale(--step <integer>) returns <length> {
result: calc(var(--step) * 0.25rem);
}
@function --my-palette(
--color type(indigo | blue | green),
--step <integer>
) returns <color> {
/* Complex colour palette logic */
}
.component {
font-size: --my-typescale(5);
color: --my-palette(indigo, 500);
}
Today, CSS functions are available in Chromium-based browsers.
The if() function
I want to be able to colocate all of the variations for a single property in one declaration rather than in different selectors, at-rules, etc.
@mixin --component() {
font-size: --my-typescale(if(
style(--component-size, large): 7;
style(--component-size, small): 3;
else: 5;
));
color: --my-palette(indigo, if(
style(--component-state, hover): 300;
style(--component-state, active): 700;
else: 500;
));
}
.component {
@apply --component();
&:hover {
--component-state: hover;
}
&:active {
--component-state: active;
}
}
if() function is used to set a parameter of the CSS functions mentioned in Figure 2 for the font-size and color properties based on the value of a --component-state property. This latter custom property is set when the mixin is applied to a concrete selector.Today, the if() function is available in Chromium-based browsers.
A CSS-powered DSL for design systems
Features like these allow us to effectively create our own DSL (domain-specific language) for a design system.
Functions allow us to encode some of the algorithms behind our design decisions into our stylesheets. They ultimately can reduce the amount of custom properties that are necessary since they allow us to generate values using parameters.
Mixins allow us to create presentational abstractions that can be flexibly applied to many framework implementations and can even be used by consumers in ad hoc way where they previously might have either misapplied a component in a semantically inappropriate scenario or even fork styles to work for their own non-matching markup.
The if() function allows us to more powerfully leverage an element’s own custom properties and colocate similar rules, avoiding needing to deal with various selectors with differing specificity along the way.
A renewed separation of concerns
Empowering CSS in these ways makes it easier to keep markup concerns separate from presentational concerns. There will always need to be an implemenetation layer where presentation is matched to markup using selectors, but with these features, that layer will become much clearer and descriptive.
button {
@apply --button();
&[aria-expanded="true"] {
--button-state: open;
}
}
cta-button {
@apply --button(large);
&:state(open) {
--button-state: open;
}
}
Features that we have today, such as @scope reduce the need for complicated selector naming conventions or even highly specific complex/compound selectors. Paired with these new features, we can easily avoid needing to touch our markup to apply the styles, yet have maintainable styles that are descriptive.
@scope (body) to (main) {
header :any-link {
@apply --pill();
}
}
header element that’s scoped to the body and not a sectioning element like main).With features like these, separation of concerns becomes much easier to achieve. Markup can describe our content’s semantics and CSS can describe presentation. We don’t need to endlessly come up with new class name conventions (whether human or machine readable) or tweak our favourite utility class generator with every new CSS feature that’s introduced.
Optimism and gratitude
It’s an exciting time to be writing CSS. It’s come a very long way from when I made my first web page twenty years ago. I’m grateful for those behind the scenes doing the hard work of both slugging away at bugs, optimizing performance, and coming up with new features. Cheers!