Native CSS Mixins: Chromium Implementation Begins (Edge Team)
Technical Deep-Dive · July 2026

Native CSS Mixins:
Edge Team Begins Chromium Implementation

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.

Oleg Maximov July 12, 2026 15 min read

Introduction

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.

What Are CSS Mixins?

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.

The @mixin Syntax: Parameters, Defaults, Types

Defining Parameters

Parameters in the @mixin prelude use a comma-separated list of dashed idents. Each parameter can optionally have:

/* 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

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.

CSS Mixins vs Sass Mixins: Key Differences

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.

@function Alongside @mixin

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.

Current Browser Support (July 2026)

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.

Real-World Use Cases

Button Themes

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); }

Gradient Text Effect

@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);
}

Responsive Component Wrapper

@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); }

Migration Strategy: From Sass to Native CSS Mixins

Native CSS mixins won't replace your entire Sass setup overnight — they're a gradual replacement. Here's a practical migration order:

  1. Replace parameterless @mixin with @macro — fixed-style blocks are a direct 1:1 mapping.
  2. Replace color/length parametric mixins — button themes, card styles, text effects that accept simple typed arguments.
  3. Replace responsive shorthand mixins — media query wrappers, grid presets, container query templates.
  4. Keep Sass for imperative logic — @if/@else, @each loops, and complex calculations that build up intermediate Sass variables. These may never get a CSS-native equivalent.
  5. Keep Sass for build-time constants — Sass variables that must be fixed at compile time (design tokens that should never cascade).

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.

FAQ

What are CSS mixins and how do they differ from Sass mixins?
CSS mixins are native browser-level at-rules (@mixin/@apply) that define reusable style blocks with parameters. Unlike Sass mixins, CSS mixins support cascaded custom properties as arguments, can contain conditional at-rules (@media and @supports) inside @result blocks, and keep local custom properties private (scoped to the mixin body). They also use declarative logic rather than imperative control flow — no @if/@each loops like Sass.
What browsers support CSS mixins right now?
As of July 2026, CSS mixins only work in Chrome Canary with the --enable-features=CSSMixins flag enabled. The Microsoft Edge team has begun implementing the feature in Chromium (tracking bug issued July 8, 2026). Firefox and WebKit have not publicly signaled intent to implement. Custom CSS functions (@function) have partial support in Chrome.
What is the difference between @mixin and @macro in CSS?
@mixin defines a reusable block with optional parameters — you pass arguments to customize the output. @macro is a parameterless variant of @mixin: a fixed block of styles with no arguments. Both use @apply to expand their contents in place. Think of @macro as a named snippet and @mixin as a parametric template.
What syntax does the CSS mixins spec use?
Mixins are defined with @mixin followed by a dashed-ident name and optional parameters. The @result block inside contains the actual declarations and rules to emit. Parameters can have typed syntax (via type()) and default values. The mixin is invoked with @apply --name(args) inside any ruleset. Local custom properties in the mixin body stay scoped and never leak to the element.
Can CSS mixins replace pre-processors entirely?
CSS mixins can replace many common Sass/Less patterns — reusable component styles, responsive shorthands, gradient text effects, button themes — but they are declarative, not imperative. Sass features like @if/@each loops, nesting (already natively supported in CSS), and Sass variables (which compile at build time) work differently. Mixins integrate with the cascade and custom properties, making them more powerful for runtime-adaptive styles but different from build-time pre-processor features.
Who is implementing CSS mixins in Chromium?
The implementation is led by Microsoft Edge engineers: John Jansen, Kevin Babbitt, and Leo Lee. The Chromium tracking bug (issue 406935599) was created on July 8, 2026. The feature is in the 'Prototype a solution' stage — early development, no shipping milestone yet. The spec is at CSS Functions and Mixins Module Level 1 (drafts.csswg.org/css-mixins-1/).
How do CSS mixins relate to CSS custom functions (@function)?
Both are part of the same CSS Functions and Mixins Module Level 1 spec, but they serve different purposes. Functions (@function) return CSS values (colors, lengths, strings) and are used inside property values. Mixins (@mixin/@apply) return CSS declarations and entire rule blocks — they are applied inside rulesets. Functions have slightly more browser support (Chrome Canary) while mixins just entered early implementation.

What's Next?

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:

Native 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.

Contact

Building modern CSS architecture?

I build production web applications using modern CSS and JavaScript. Let's discuss your project — free consultation.