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

Example · css
@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.

Example · css
@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.

Example · css
@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.

Example · css
@mixin box($args...) {
  @each $prop, $val in keywords($args) {
    #{$prop}: $val;
  }
}

.flex-row {
  @include box(
    $display: flex,
    $flex-direction: row,
    $gap: 1rem
  );
}

Discussion

  • Be the first to comment on this lesson.