css-mixin-polyfill
v0.1.6
Published
A JavaScript polyfill for CSS @mixin, @macro, and @apply rules
Maintainers
Readme
CSS Mixin Polyfill
A JavaScript polyfill for CSS @mixin, @macro, and @apply rules with hybrid build-time and runtime processing. Define reusable style blocks with @mixin and @macro, then apply them anywhere with @apply.
Features
- ✅
@mixinrules with parameters, default values, type constraints, and@resultblocks - ✅
@macrorules for simple, argument-free reusable style blocks - ✅
@applyrules to invoke mixins and macros, with arguments and contents blocks - ✅
@contentsblocks for caller-provided style injection - ✅ Local variables scoped within mixin bodies, with hygienic renaming
- ✅ Nested
@apply— mixins and macros can invoke other mixins and macros - ✅ Build-time transformation via CLI for zero-runtime-cost output
- ✅ Runtime processing for dynamic stylesheets
- ✅ TypeScript support with full type definitions
- ✅ Zero dependencies — pure JavaScript implementation
- ✅ Multiple build formats (ES module, CommonJS, UMD)
Installation
npm install css-mixin-polyfillQuick Start
Build-time Transformation (Recommended)
npx css-mixin-polyfill input.css output.css --minify --statsRuntime Processing
import { init } from "css-mixin-polyfill";
init();Usage
Automatic Initialization
Simply import the polyfill and it will automatically process stylesheets containing @mixin, @macro, and @apply rules:
import "css-mixin-polyfill";Or include it via script tag:
<script src="https://cdn.jsdelivr.net/npm/css-mixin-polyfill/dist/index.umd.min.js"></script>Manual Initialization
import { init } from "css-mixin-polyfill";
const polyfill = init({
debug: true,
autoInit: true
});Processing CSS Text Directly
import { processCSSText } from "css-mixin-polyfill";
const css = `
@mixin --highlight(--color <color>: gold) {
@result {
background-color: var(--color);
font-weight: bold;
}
}
.alert {
@apply --highlight(tomato);
}
`;
const processed = processCSSText(css);
// .alert { background-color: tomato; font-weight: bold; }CSS Syntax
@mixin — Reusable Styles with Parameters
A @mixin defines a reusable block of styles that accepts parameters with optional types and default values. The styles to emit are wrapped in a @result block.
@mixin --gradient-text(
--from <color>: mediumvioletred,
--to <color>: teal,
--angle: to bottom right
) {
--gradient: linear-gradient(var(--angle), var(--from), var(--to));
@result {
color: var(--from);
@supports (background-clip: text) or (-webkit-background-clip: text) {
background: var(--gradient);
color: transparent;
-webkit-background-clip: text;
background-clip: text;
}
}
}
h1 {
@apply --gradient-text(pink, powderblue);
}- Parameters are declared as custom properties with optional
<type>constraints and: defaultvalues. - Local variables (custom properties outside
@result) are scoped to the mixin body and won’t leak into element styles. @resultmarks the styles that will be substituted at the@applysite.
@macro — Simple Reusable Styles
A @macro is a simplified variant of a @mixin that takes no arguments and has no local variables. Its body is directly substituted at the @apply site.
@macro --reset-list {
margin: 0;
padding: 0;
list-style: none;
}
@macro --visually-hidden {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
}
.nav {
@apply --reset-list;
}
.sr-only {
@apply --visually-hidden;
}@apply — Invoking Mixins and Macros
The @apply rule substitutes a mixin or macro’s result into the current style rule.
.foo {
/* No arguments */
@apply --reset-list;
/* With arguments */
@apply --gradient-text(hotpink, deepskyblue);
/* With a contents block */
@apply --responsive-wrapper {
display: flex;
flex-flow: column;
}
/* With both arguments and contents */
@apply --card(16px) {
background: white;
}
}@contents — Caller-Provided Styles
A mixin or macro can accept a contents block from the caller using @contents. This lets the caller inject styles into a predefined structure.
@macro --one-column {
@media (width <= 800px) {
@contents;
}
}
@macro --two-column {
@media (width > 800px) {
@contents;
}
}
body {
@apply --one-column {
display: flex;
flex-flow: column;
}
@apply --two-column {
display: grid;
grid-template-columns: 1fr 3fr;
}
}@contents can also specify a fallback block that is used when no contents block is passed:
@mixin --card() {
@result {
@contents {
padding: 1rem;
}
}
}
.default-card {
@apply --card;
/* Uses fallback: padding: 1rem */
}
.custom-card {
@apply --card {
padding: 2rem;
border-radius: 8px;
}
}Advanced Examples
Design Tokens with Mixins
@mixin --button(
--bg <color>: #3b82f6,
--text <color>: white,
--radius <length>: 6px
) {
@result {
background-color: var(--bg);
color: var(--text);
border: none;
border-radius: var(--radius);
padding: 0.5em 1em;
cursor: pointer;
}
}
.btn-primary {
@apply --button;
}
.btn-danger {
@apply --button(#ef4444, white, 8px);
}
.btn-outline {
@apply --button(transparent, #3b82f6);
border: 2px solid #3b82f6;
}Responsive Layout Mixin
@mixin --responsive-grid(--min-col-width: 250px, --gap: 1rem) {
@result {
display: grid;
gap: var(--gap);
grid-template-columns: repeat(
auto-fit,
minmax(var(--min-col-width), 1fr)
);
}
}
.product-grid {
@apply --responsive-grid(300px, 2rem);
}
.photo-gallery {
@apply --responsive-grid(200px);
}Nested Mixin Invocation
Mixins can invoke other mixins inside their @result blocks:
@mixin --squish(
--left-color <color>,
--right-color <color>: var(--left-color)
) {
@result {
&::before {
content: "▶";
background-color: var(--left-color);
}
&::after {
content: "◀";
background-color: var(--right-color);
}
}
}
@mixin --colorized-squish(--color <color>) {
@result {
background-color: var(--color);
border: 2px solid oklch(from var(--color) calc(l - 0.1) c h);
@apply --squish(
oklch(from var(--color) calc(l - 0.3) c h),
oklch(from var(--color) calc(l - 0.2) c h)
);
}
}
.decorated {
@apply --colorized-squish(tomato);
}Accessibility Helpers
@macro --focus-ring {
&:focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
}
}
@macro --prefers-reduced-motion {
@media (prefers-reduced-motion: reduce) {
@contents {
animation: none;
transition: none;
}
}
}
.interactive {
@apply --focus-ring;
@apply --prefers-reduced-motion;
}CLI
Transform CSS files at build time for zero-runtime-cost output:
npx css-mixin-polyfill <input.css> [output.css] [--minify] [--stats]Options:
| Option | Description |
| ---------- | ------------------------------- |
| --minify | Minify the output CSS |
| --stats | Print transformation statistics |
Examples:
# Transform and write to a new file
npx css-mixin-polyfill src/styles.css dist/styles.css
# Transform in place with minification
npx css-mixin-polyfill styles.css --minify
# Transform and print stats
npx css-mixin-polyfill input.css output.css --statsAPI Reference
init(options)
Initialize the polyfill. Processes existing stylesheets and watches for dynamically added ones.
import { init } from "css-mixin-polyfill";
const polyfill = init({
debug: false, // Enable debug logging
autoInit: true // Automatically process existing stylesheets
});Returns an object with instance methods:
// Re-process all stylesheets
polyfill.refresh();
// Check for native browser support
polyfill.hasNativeSupport();
// Process specific CSS text
polyfill.processCSSText(cssText);processCSSText(cssText)
Process a string of CSS containing @mixin, @macro, and @apply rules. Returns the transformed CSS with all mixins and macros resolved.
import { processCSSText } from "css-mixin-polyfill";
const result = processCSSText(`
@macro --bold { font-weight: bold; }
.title { @apply --bold; }
`);
// .title { font-weight: bold; }hasNativeSupport()
Check if the browser natively supports CSS @mixin, @macro, and @apply rules. When native support is available, the polyfill is not needed.
import { hasNativeSupport } from "css-mixin-polyfill";
if (hasNativeSupport()) {
console.log("Native CSS mixin support available!");
}buildTimeTransform(cssText)
Transform CSS at build time, resolving all @mixin, @macro, and @apply rules into plain CSS. Used internally by the CLI tool.
import { buildTimeTransform } from "css-mixin-polyfill";
const output = buildTimeTransform(inputCSS);Browser Support
The polyfill works in all modern browsers that support:
- ES6 (ECMAScript 2015)
- CSS Object Model
- MutationObserver
Tested browsers:
- Chrome 60+
- Firefox 55+
- Safari 12+
- Edge 79+
Performance
- The polyfill only activates when native CSS
@mixin/@macro/@applysupport is not available - Build-time transformation eliminates runtime cost entirely
- Efficient CSS parsing with minimal DOM manipulation
- Caches transformation results for repeated processing
Contributing
Please have a look at our CONTRIBUTION guidelines.
License
MIT License — see LICENSE file for details.
Credits
- Pure JavaScript implementation with custom CSS parsing
- Based on the W3C CSS Mixins specification
- Thanks to all contributors and testers
Related
- W3C CSS Mixins specification — the official specification
- postcss-transform-mixins — PostCSS plugin for build-time transformation
- stylelint-config-mixin — Stylelint configuration for CSS mixin usage
