SASS/SCSS

Advanced Mixin Patterns

20 min Lesson 10 of 30

Advanced Mixin Patterns

Now that you understand the basics of mixins, let's explore advanced patterns that will take your SASS skills to the next level. These patterns are used in production codebases and design systems to create powerful, flexible, and maintainable styling solutions.

Mixins with @content for Responsive Breakpoints

One of the most powerful uses of @content is creating a responsive breakpoint system. This pattern allows you to write media queries inline with your component styles, making your code more maintainable and readable.

Basic Breakpoint Mixin

// Define breakpoints
$breakpoints: (
  'mobile': 480px,
  'tablet': 768px,
  'desktop': 1024px,
  'wide': 1200px
);

@mixin respond-to($breakpoint) {
  @if map-has-key($breakpoints, $breakpoint) {
    @media (min-width: map-get($breakpoints, $breakpoint)) {
      @content;
    }
  } @else {
    @warn "Unknown breakpoint: #{$breakpoint}";
  }
}

// Usage
.container {
  width: 100%;
  padding: 15px;

  @include respond-to(tablet) {
    width: 750px;
    padding: 20px;
  }

  @include respond-to(desktop) {
    width: 970px;
  }

  @include respond-to(wide) {
    width: 1170px;
  }
}

// Compiled CSS
.container {
  width: 100%;
  padding: 15px;
}

@media (min-width: 768px) {
  .container {
    width: 750px;
    padding: 20px;
  }
}

@media (min-width: 1024px) {
  .container {
    width: 970px;
  }
}

@media (min-width: 1200px) {
  .container {
    width: 1170px;
  }
}

Building a Comprehensive Responsive Breakpoint System

A production-ready breakpoint system should handle both min-width and max-width queries, as well as range queries and custom media queries.

Advanced Breakpoint System

// Breakpoint definitions
$breakpoints: (
  'xs': 0,
  'sm': 576px,
  'md': 768px,
  'lg': 992px,
  'xl': 1200px,
  'xxl': 1400px
);

// Media query mixin with multiple strategies
@mixin media($breakpoint, $type: 'min') {
  @if $type == 'min' {
    // Mobile-first: min-width
    @media (min-width: map-get($breakpoints, $breakpoint)) {
      @content;
    }
  }
  @else if $type == 'max' {
    // Desktop-first: max-width
    @media (max-width: map-get($breakpoints, $breakpoint) - 1px) {
      @content;
    }
  }
  @else if $type == 'only' {
    // Only this breakpoint range
    $next-breakpoint: null;
    $breakpoint-keys: map-keys($breakpoints);
    $breakpoint-index: index($breakpoint-keys, $breakpoint);

    @if $breakpoint-index < length($breakpoint-keys) {
      $next-breakpoint: nth($breakpoint-keys, $breakpoint-index + 1);
    }

    @if $next-breakpoint {
      @media (min-width: map-get($breakpoints, $breakpoint)) and (max-width: map-get($breakpoints, $next-breakpoint) - 1px) {
        @content;
      }
    } @else {
      @media (min-width: map-get($breakpoints, $breakpoint)) {
        @content;
      }
    }
  }
}

// Usage examples
.element {
  // Default mobile styles
  font-size: 14px;

  // Tablet and up
  @include media(md) {
    font-size: 16px;
  }

  // Large desktop and up
  @include media(xl) {
    font-size: 18px;
  }

  // Only on tablets (md to lg range)
  @include media(md, only) {
    background: lightblue;
  }

  // Mobile only (max-width approach)
  @include media(sm, max) {
    display: none;
  }
}
Pro Tip: Use a mobile-first approach (min-width) as the default. This encourages progressive enhancement and typically results in cleaner, more maintainable code.

Conditional Logic in Mixins

Mixins can use @if, @else if, and @else statements to generate different output based on arguments. This allows you to create intelligent, adaptive mixins.

Conditional Mixin Example

@mixin theme-colors($theme) {
  @if $theme == 'light' {
    background-color: white;
    color: #333;
    border-color: #ddd;
  }
  @else if $theme == 'dark' {
    background-color: #333;
    color: white;
    border-color: #666;
  }
  @else if $theme == 'blue' {
    background-color: #3498db;
    color: white;
    border-color: #2980b9;
  }
  @else {
    @warn "Unknown theme: #{$theme}. Defaulting to light theme.";
    background-color: white;
    color: #333;
    border-color: #ddd;
  }
}

// Usage
.card-light {
  @include theme-colors(light);
}

.card-dark {
  @include theme-colors(dark);
}

.card-blue {
  @include theme-colors(blue);
}

Complex Conditional Logic

@mixin button-style($size: medium, $variant: primary, $outline: false) {
  // Base styles
  display: inline-block;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  text-align: center;
  transition: all 0.3s ease;

  // Size variants
  @if $size == small {
    padding: 5px 10px;
    font-size: 12px;
  }
  @else if $size == medium {
    padding: 10px 20px;
    font-size: 14px;
  }
  @else if $size == large {
    padding: 15px 30px;
    font-size: 18px;
  }

  // Color variants
  $bg-color: null;
  $text-color: white;

  @if $variant == primary {
    $bg-color: #3498db;
  }
  @else if $variant == success {
    $bg-color: #2ecc71;
  }
  @else if $variant == danger {
    $bg-color: #e74c3c;
  }
  @else if $variant == warning {
    $bg-color: #f39c12;
  }

  // Outline vs filled
  @if $outline {
    background: transparent;
    color: $bg-color;
    border: 2px solid $bg-color;

    &:hover {
      background: $bg-color;
      color: white;
    }
  }
  @else {
    background: $bg-color;
    color: $text-color;

    &:hover {
      background: darken($bg-color, 10%);
    }
  }
}

// Usage
.btn-small-primary {
  @include button-style(small, primary);
}

.btn-large-danger-outline {
  @include button-style(large, danger, true);
}

Mixins That Generate Multiple Selectors

Advanced mixins can generate multiple CSS selectors and rules, creating entire component systems with a single include.

Generating Selector Variants

@mixin generate-spacing-utilities($property, $prefix, $sizes) {
  @each $name, $value in $sizes {
    .#{$prefix}-#{$name} {
      #{$property}: $value;
    }

    // Also generate responsive variants
    @each $breakpoint-name, $breakpoint-value in $breakpoints {
      @media (min-width: $breakpoint-value) {
        .#{$prefix}-#{$breakpoint-name}-#{$name} {
          #{$property}: $value;
        }
      }
    }
  }
}

// Define spacing scale
$spacing-scale: (
  '0': 0,
  '1': 0.25rem,
  '2': 0.5rem,
  '3': 0.75rem,
  '4': 1rem,
  '5': 1.5rem,
  '6': 2rem,
  '8': 3rem,
  '10': 4rem
);

// Generate margin utilities
@include generate-spacing-utilities(margin, 'm', $spacing-scale);

// Generate padding utilities
@include generate-spacing-utilities(padding, 'p', $spacing-scale);

// This generates classes like:
// .m-0, .m-1, .m-2, etc.
// .m-md-0, .m-md-1, etc. (responsive variants)
// .p-0, .p-1, .p-2, etc.
// .p-md-0, .p-md-1, etc.

Component Generator Mixin

@mixin generate-color-variants($component, $colors) {
  .#{$component} {
    // Base component styles
    padding: 15px;
    border-radius: 4px;
    margin-bottom: 10px;

    @each $name, $color in $colors {
      &--#{$name} {
        background-color: lighten($color, 40%);
        border-left: 4px solid $color;
        color: darken($color, 20%);

        .#{$component}__title {
          color: $color;
          font-weight: bold;
        }

        .#{$component}__icon {
          color: $color;
        }
      }
    }
  }
}

// Define color palette
$alert-colors: (
  'info': #3498db,
  'success': #2ecc71,
  'warning': #f39c12,
  'danger': #e74c3c
);

// Generate alert component variants
@include generate-color-variants('alert', $alert-colors);

// This generates:
// .alert--info, .alert--success, .alert--warning, .alert--danger
// with nested .alert__title and .alert__icon variants

Mixin Libraries and Frameworks

Many popular CSS frameworks and libraries are built using SASS mixins. Understanding these can help you build your own reusable component libraries.

Popular Mixin Libraries:
  • Bourbon: A lightweight SASS toolkit with useful mixins
  • Compass: Comprehensive SASS framework (now deprecated)
  • Bootstrap: Uses extensive mixin systems for its components
  • Foundation: Provides powerful mixins for responsive design
  • Susy: Grid system built entirely with mixins

Example: Bootstrap-Style Grid Mixin

@mixin make-container($padding: 15px) {
  width: 100%;
  padding-right: $padding;
  padding-left: $padding;
  margin-right: auto;
  margin-left: auto;
}

@mixin make-row($gutter: 30px) {
  display: flex;
  flex-wrap: wrap;
  margin-right: -($gutter / 2);
  margin-left: -($gutter / 2);
}

@mixin make-col($size, $columns: 12, $gutter: 30px) {
  flex: 0 0 percentage($size / $columns);
  max-width: percentage($size / $columns);
  padding-right: $gutter / 2;
  padding-left: $gutter / 2;
}

// Usage
.container {
  @include make-container;
}

.row {
  @include make-row;
}

.col-6 {
  @include make-col(6);
}

.col-4 {
  @include make-col(4);
}

.col-3 {
  @include make-col(3);
}

Mixins vs @extend vs Functions

Understanding when to use mixins, @extend, or functions is crucial for writing efficient SASS. Each has its strengths and appropriate use cases.

Comparison Table

// MIXINS - Copy styles to each selector
@mixin button-base {
  padding: 10px 20px;
  border-radius: 4px;
}

.button-1 { @include button-base; }
.button-2 { @include button-base; }

// Compiled (duplicated styles):
.button-1 {
  padding: 10px 20px;
  border-radius: 4px;
}
.button-2 {
  padding: 10px 20px;
  border-radius: 4px;
}

// @EXTEND - Groups selectors together
%button-base {
  padding: 10px 20px;
  border-radius: 4px;
}

.button-1 { @extend %button-base; }
.button-2 { @extend %button-base; }

// Compiled (grouped selectors):
.button-1, .button-2 {
  padding: 10px 20px;
  border-radius: 4px;
}

// FUNCTIONS - Return values
@function calculate-rem($px) {
  @return $px / 16 * 1rem;
}

.element {
  font-size: calculate-rem(18); // 1.125rem
}
When to Use Each:
  • Mixins: When you need to pass arguments or include dynamic styles. Use for most reusable patterns.
  • @extend: When you have identical styles shared by multiple selectors. Use sparingly as it can create complex selector chains.
  • Functions: When you need to calculate or return a value, not generate styles. Use for math, color manipulation, etc.

Building a Typography Mixin System

Typography is a perfect use case for a comprehensive mixin system. Let's build a production-ready typography system.

Complete Typography System

// Typography configuration
$font-family-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
$font-family-heading: Georgia, "Times New Roman", serif;
$font-family-mono: "Courier New", Courier, monospace;

$font-weights: (
  'light': 300,
  'normal': 400,
  'medium': 500,
  'semibold': 600,
  'bold': 700
);

$type-scale: (
  'xs': 0.75rem,   // 12px
  'sm': 0.875rem,  // 14px
  'base': 1rem,    // 16px
  'lg': 1.125rem,  // 18px
  'xl': 1.25rem,   // 20px
  '2xl': 1.5rem,   // 24px
  '3xl': 1.875rem, // 30px
  '4xl': 2.25rem,  // 36px
  '5xl': 3rem      // 48px
);

// Base typography mixin
@mixin typography-base {
  font-family: $font-family-base;
  line-height: 1.6;
  color: #333;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

// Heading mixin
@mixin heading($level: 1) {
  font-family: $font-family-heading;
  font-weight: map-get($font-weights, 'bold');
  line-height: 1.2;
  margin-top: 0;
  margin-bottom: 0.5em;

  @if $level == 1 {
    font-size: map-get($type-scale, '5xl');
    @include media(md) {
      font-size: map-get($type-scale, '4xl');
    }
  }
  @else if $level == 2 {
    font-size: map-get($type-scale, '4xl');
    @include media(md) {
      font-size: map-get($type-scale, '3xl');
    }
  }
  @else if $level == 3 {
    font-size: map-get($type-scale, '3xl');
  }
  @else if $level == 4 {
    font-size: map-get($type-scale, '2xl');
  }
  @else if $level == 5 {
    font-size: map-get($type-scale, 'xl');
  }
  @else if $level == 6 {
    font-size: map-get($type-scale, 'lg');
  }
}

// Text style mixin
@mixin text-style($size: 'base', $weight: 'normal', $line-height: null) {
  font-size: map-get($type-scale, $size);
  font-weight: map-get($font-weights, $weight);

  @if $line-height {
    line-height: $line-height;
  }
}

// Responsive text mixin
@mixin fluid-type($min-size, $max-size, $min-width: 320px, $max-width: 1200px) {
  font-size: $min-size;

  @media (min-width: $min-width) {
    font-size: calc(#{$min-size} + (#{strip-unit($max-size)} - #{strip-unit($min-size)}) * ((100vw - #{$min-width}) / #{strip-unit($max-width - $min-width)}));
  }

  @media (min-width: $max-width) {
    font-size: $max-size;
  }
}

// Helper function
@function strip-unit($number) {
  @return $number / ($number * 0 + 1);
}

// Usage
body {
  @include typography-base;
}

h1 { @include heading(1); }
h2 { @include heading(2); }
h3 { @include heading(3); }

.text-small {
  @include text-style('sm', 'normal');
}

.text-large-bold {
  @include text-style('xl', 'bold');
}

.hero-title {
  @include fluid-type(2rem, 4rem);
}

Performance Considerations with Mixins

While mixins are powerful, they can impact the size of your compiled CSS if not used carefully. Understanding performance implications helps you make better decisions.

Performance Gotchas:
  • Mixin Duplication: Each @include copies the entire mixin's CSS, which can bloat your file size
  • Deep Nesting: Mixins with nested selectors can create specificity issues
  • Complex Calculations: Heavy computation in mixins can slow compilation
  • Media Query Duplication: Each responsive mixin creates separate @media blocks

Good vs Bad Practices

// BAD - Will duplicate all styles 10 times
@mixin heavy-component {
  padding: 20px;
  margin: 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  // ... 50 more lines of styles
}

.item-1 { @include heavy-component; }
.item-2 { @include heavy-component; }
// ... item-3 through item-10
// Result: Massive CSS file with duplicated styles

// GOOD - Use @extend or classes for identical styles
%component-base {
  padding: 20px;
  margin: 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

.item-1 { @extend %component-base; }
.item-2 { @extend %component-base; }
// Result: Grouped selectors, smaller file

// BEST - Use mixins for parameterized variations
@mixin component-variant($bg-color) {
  background: $bg-color;
  border-color: darken($bg-color, 10%);
}

.item-1 {
  @extend %component-base;
  @include component-variant(blue);
}

.item-2 {
  @extend %component-base;
  @include component-variant(green);
}
Optimization Tips:
  • Use @extend for identical styles shared across selectors
  • Use mixins when you need parameters or dynamic styles
  • Keep mixins focused and single-purpose
  • Consider using utility classes instead of mixins for simple, common patterns
  • Use CSS custom properties (variables) for values that change frequently

Exercise 1: Build a Complete Breakpoint System

Create a comprehensive responsive breakpoint mixin system:

  • Define breakpoints for xs, sm, md, lg, xl, xxl
  • Create mixins for: up($size), down($size), between($size1, $size2), only($size)
  • Add support for custom media queries (print, screen, etc.)
  • Build a component that uses all breakpoint types
  • Add helpful @warn messages for invalid breakpoints

Exercise 2: Component Generator System

Build a mixin system that generates complete component families:

  • Create a @mixin generate-button-family that creates base, variant, size, and state classes
  • Support color themes (primary, secondary, success, danger, warning, info)
  • Support sizes (xs, sm, md, lg, xl)
  • Support states (hover, active, focus, disabled)
  • Support outline and ghost variants
  • Generate all combinations with a single mixin call

Exercise 3: Design System Utilities

Create utility mixins for a design system:

  • @mixin spacing-utilities($properties, $scale) that generates margin/padding classes
  • @mixin color-utilities($colors) that generates text, background, and border color classes
  • @mixin typography-utilities($scale, $weights) that generates font size and weight classes
  • Make all utilities responsive (generate breakpoint-specific variants)
  • Add a toggle to enable/disable responsive variants for performance