Advanced Mixins: @content & Keyword Args
Pass style blocks into mixins, hand values back out with @content using(), and spread argument maps.
Basic mixins take arguments; advanced mixins take whole blocks of styles via @content, and can even pass data back to that block.
A block-accepting wrapper
@mixin hover-focus {
&:hover, &:focus-visible { @content; }
}
.link { @include hover-focus { color: #CC6699; } }Handing values to the block
@content(args) paired with @include name using ($x) lets the mixin compute something and pass it into the caller's block — the pattern behind theme-aware and loop-driven wrappers.
Keyword arguments & argument maps
A trailing $args... parameter captures extra keyword arguments as a map. Combined with the spread syntax @include thing($map...), you can forward a whole configuration map into a mixin in one call.
Example
@use 'sass:map';
$breakpoints: ('sm': 480px, 'md': 768px, 'lg': 1024px);
// @content(using) — the mixin computes the width and hands it back
@mixin each-breakpoint {
@each $name, $width in $breakpoints {
@media (min-width: $width) {
@content($name);
}
}
}
.container {
@include each-breakpoint using ($name) {
max-width: map.get($breakpoints, $name) - 32px;
&::after { content: 'bp: #{$name}'; }
}
}
// Keyword-arg capture + spread
@mixin button($args...) {
$bg: map.get(keywords($args), 'bg') or #CC6699;
$radius: map.get(keywords($args), 'radius') or 4px;
background: $bg;
border-radius: $radius;
}
$pill: ('bg': #6699CC, 'radius': 999px);
.btn-pill { @include button($pill...); }When to use it
- A layout mixin uses @content to wrap any grid rules the caller provides, separating the breakpoint logic from the component's specific column definitions.
- A developer uses keyword arguments in a complex animation mixin so callers can omit timing but explicitly set easing without positional confusion.
- A team passes an entire set of dark-mode overrides as a @content block to a theme() mixin, keeping light and dark rules side by side in each component.
More examples
@content with breakpoint wrapper
The at() mixin wraps any @content block in the correct breakpoint media query, keeping responsive overrides co-located with base styles.
@mixin at($bp) {
$widths: (sm: 640px, md: 768px, lg: 1024px, xl: 1280px);
@media (min-width: map-get($widths, $bp)) {
@content;
}
}
.hero {
font-size: 1.5rem;
@include at(lg) {
font-size: 3rem;
text-align: center;
}
}Keyword-only mixin arguments
Passes only the $easing keyword argument by name, leaving $duration and $delay at their defaults for a cleaner call site.
@mixin animate(
$name,
$duration: 0.3s,
$easing: ease-in-out,
$delay: 0s
) {
animation-name: $name;
animation-duration: $duration;
animation-timing-function: $easing;
animation-delay: $delay;
}
.spinner { @include animate(spin, $easing: linear); }Arglist for arbitrary keyword spreading
Accepts an argument list with keywords() to map any named arguments directly to CSS properties, building a generic property-spreader mixin.
@mixin box($args...) {
@each $prop, $val in keywords($args) {
#{$prop}: $val;
}
}
.flex-row {
@include box(
$display: flex,
$flex-direction: row,
$gap: 1rem
);
}
Discussion