CSS mixins (@mixin/@apply) have entered early Chromium
implementation, led by Microsoft Edge engineers. This guide covers the syntax,
how it differs from Sass, what works now, and what's coming — with detailed
code examples.
On July 8, 2026, the Chrome Platform Status tracker registered a new feature: CSS mixins (@mixin / @apply / @macro). The entry, created by Microsoft Edge engineers John Jansen, Kevin Babbitt, and Leo Lee, marks the beginning of Chromium implementation for one of the most requested CSS features of the past decade.
For years, developers have relied on Sass, Less, and PostCSS to define reusable blocks of CSS. The CSS Functions and Mixins Module Level 1 spec (drafted by Miriam Suzanne with Tab Atkins) proposes a native browser-level solution — no build step, no dependency, just CSS itself.
This isn't a speculative proposal anymore. The Edge team has created the Chromium tracking bug, Chrome Canary supports mixins behind a flag, and Adam Argyle has been publicly demonstrating the syntax. Let's look at what's coming and how it will change the way we write CSS.
A CSS mixin is a reusable block of style declarations and
nested rules defined with the @mixin at-rule. You give it a name,
optionally accept parameters, and then expand it in place with @apply.
The simplest possible mixin — a parameterless one (@macro):
/* Define a macro: no parameters, just a reusable block */ @macro --box { aspect-ratio: 1; inline-size: 100px; block-size: 100px; background: teal; } /* Use it anywhere */ .avatar { @apply --box; border-radius: 50%; } .icon { @apply --box; }
With @mixin, you can add parameters — typed, with defaults,
fully cascadable:
/* Parameterized mixin with typed arguments */ @mixin --button ( --face type(color): teal, --text type(color): white, --radius type(length): 4px ) { @result { background: var(--face); color: var(--text); border-radius: var(--radius); padding: 0.5em 1.25em; border: none; cursor: pointer; } } /* Apply with arguments */ button.primary { @apply --button(dodgerblue); } button.danger { @apply --button(crimson); } button.ghost { @apply --button(transparent, currentColor); }
The @result block is where the actual output declarations live.
Local custom properties defined in the mixin body (outside @result)
stay scoped to the mixin and never leak to the element — a crucial privacy
boundary that Sass cannot provide.
Parameters in the @mixin prelude use a comma-separated list of
dashed idents. Each parameter can optionally have:
type() function (e.g., type(color), type(length), type(*)): 1rem)/* Various parameter patterns */ /* No parameters — use @macro */ @macro --clearfix { @result { content: ""; display: table; clear: both; } } /* Positional parameters with defaults */ @mixin --card ( --padding: 1rem, --bg: white, --radius: 8px ) { @result { padding: var(--padding); background: var(--bg); border-radius: var(--radius); box-shadow: 0 1px 3px rgba(0,0,0,0.1); } } /* Typed parameters */ @mixin --gradient-text ( --gradient type(image), --font-size type(length): 2rem ) { --bg: var(--gradient); @result { font-size: var(--font-size); background: var(--bg); background-clip: text; -webkit-background-clip: text; color: transparent; } }
The @result block is mandatory for every mixin — it's where the
actual styles-to-emit are declared. Everything outside @result
is local computation (custom properties used as intermediate values), which
never leaks to the calling element.
@mixin --responsive-padding (--scale: 1) { --base-pad: calc(1rem * var(--scale)); --lg-pad: calc(2rem * var(--scale)); @result { padding: var(--base-pad); } /* Conditional rules inside @result */ @result @media (min-width: 768px) { padding: var(--lg-pad); } }
Here --base-pad and --lg-pad are private to the
mixin. The calling element only receives the padding declaration.
This scoping is impossible in Sass — all variables there are global or block-scoped,
never element-scoped.
If you're coming from Sass, the mental model is familiar but the mechanics are fundamentally different. CSS mixins are declarative and cascadable; Sass mixins are imperative and build-time.
| Feature | Sass Mixins | CSS Mixins (@mixin/@apply) |
|---|---|---|
| When processed | Build-time (compiled to static CSS) | Runtime (in the browser's CSS engine) |
| Arguments | Imperative, any Sass type | Declarative, typed via type(), accepts custom properties |
| Control flow | @if, @else, @each, @while | None (use @media/@supports conditions inside @result) |
| Variable scoping | Lexical (global or block) | Custom properties scoped to the mixin body; cascaded values flow in |
| Output | Writes declarations into the stylesheet | Writes computed declarations into the cascade |
| Cascade interaction | None (output is static) | Full — arguments can be custom properties that update with the cascade |
| Conditional output | @if/@else at build time | @media/@supports inside @result at runtime |
| Dependencies | Sass compiler, build tooling | None — native browser feature |
The most important difference: because arguments can be CSS custom properties,
a mixin call like @apply --button(var(--theme-primary)) responds
to the cascade. Change the custom property value via a class toggle or media
query, and the mixin re-evaluates. That's something no pre-processor can do.
💡 Key Insight: CSS mixins don't just replace Sass mixins — they enable patterns that Sass cannot express. Runtime-adaptive styling via the cascade is the headline feature. Pre-processor mixins generate static output; CSS mixins generate live, cascade-aware styles.
The CSS Functions and Mixins Module Level 1 also includes custom functions
(@function). While mixins return declarations, functions return
values — colors, lengths, strings — that you use inside property values.
/* A CSS custom function that returns a value */ @function --negative (--value type(length)) { result: calc(-1 * var(--value)); } .box { margin-inline-start: --negative(var(--gap)); } /* A more complex function */ @function --fluid-clamp ( --min type(length), --max type(length), --min-vw type(length): 375px, --max-vw type(length): 1200px ) { --slope: calc((var(--max) - var(--min)) / (var(--max-vw) - var(--min-vw))); --intercept: calc(var(--min) - var(--slope) * var(--min-vw)); result: clamp(var(--min), var(--intercept) + var(--slope) * 100cqw, var(--max)); } h1 { font-size: --fluid-clamp(1.5rem, 3rem); }
Functions and mixins work together seamlessly. A function can compute a value
that a mixin uses in its @result block. Together, they eliminate
many of the remaining reasons to use a CSS pre-processor.
Here's the state of play right now:
| Feature | Chrome | Edge | Firefox | Safari |
|---|---|---|---|---|
| @mixin / @apply | Canary (flag) | In development | No signal | No signal |
| @function | Canary (flag) | In development | No signal | No signal |
| CSS Nesting | 120+ | 120+ | 117+ | 17.2+ |
| Custom Properties | 49+ | 15+ | 31+ | 9.1+ |
To try CSS mixins today in Chrome Canary, launch it with:
# macOS open -a "Google Chrome Canary" --args --enable-features=CSSMixins # Linux google-chrome-unstable --enable-features=CSSMixins # Windows start chrome --enable-features=CSSMixins
The CSSMixins flag enables both @mixin/@macro/@apply
and @function support. The Chromium tracking bug
(issue 406935599)
is the place to watch for implementation progress.
The classic use case — parameterized button styles that can be themed with a single mixin call:
@mixin --btn ( --bg type(color), --fg type(color): white, --hover-lift type(number): 0.05 ) { --hover-bg: color-mix(in srgb, var(--bg), black calc(var(--hover-lift) * 100%)); --active-bg: color-mix(in srgb, var(--bg), black calc(var(--hover-lift) * 150%)); @result { background: var(--bg); color: var(--fg); border: none; padding: 0.5em 1.25em; border-radius: 6px; cursor: pointer; transition: background 0.2s, transform 0.1s; } @result :hover { background: var(--hover-bg); } @result :active { background: var(--active-bg) transform: scale(0.97); } } /* Usage */ .btn-primary { @apply --btn(dodgerblue); } .btn-success { @apply --btn(seagreen); } .btn-danger { @apply --btn(crimson); } .btn-ghost { @apply --btn(transparent, currentColor); }
@mixin --gradient-text ( --from type(color), --to type(color), --angle type(angle): 135deg ) { --grad: linear-gradient(var(--angle), var(--from), var(--to)); @result { background: var(--grad); background-clip: text; -webkit-background-clip: text; color: transparent; -webkit-text-fill-color: transparent; } } .hero-title { @apply --gradient-text(teal, purple); }
@mixin --responsive-grid ( --min-col-size type(length): 250px, --gap type(length): 1rem ) { @result { display: grid; grid-template-columns: repeat(auto-fill, minmax(var(--min-col-size), 1fr)); gap: var(--gap); } } .card-grid { @apply --responsive-grid(300px, 1.5rem); } .thumbnail-grid { @apply --responsive-grid(150px, 0.5rem); }
Native CSS mixins won't replace your entire Sass setup overnight — they're a gradual replacement. Here's a practical migration order:
CSS Nesting (already supported in all modern browsers) eliminates another major Sass dependency. Together with nesting, container queries, and the new color functions, native CSS mixins complete the picture: a future where web developers can write vanilla CSS without any pre-processor at all.
For more on what modern CSS can do without JavaScript or pre-processors, see my CSS Is Eating JavaScript overview. For responsive container-based layouts, see my CSS Container Queries guide. And for the latest CSS features shipping in Chrome, check out my Chrome 150 New CSS Features guide.
The CSS mixins implementation in Chromium is at the Prototype stage — the earliest phase of the Blink launch process. There's no shipping milestone yet, and both Firefox and WebKit (Safari) have yet to signal their intent.
What you can do right now:
CSSMixins flagNative CSS mixins represent a major milestone for the platform. For the first time in CSS history, the most popular pre-processor feature is getting a native implementation — and it's not a simple port. The cascade-aware, private-scoped, conditional-output design is genuinely superior to what Sass offers. The next year of Chromium development will be fascinating to watch.
Need help modernizing your CSS architecture or preparing for native mixins? I'm available for frontend architecture consulting and development. View my services or contact me directly.
I build production web applications using modern CSS and JavaScript. Let's discuss your project — free consultation.