SASS/SCSS

@media & Responsive Design with SASS

25 min Lesson 19 of 30

Introduction to Responsive Design with SASS

Responsive design is the practice of creating websites that adapt to different screen sizes, devices, and viewing contexts. SASS provides powerful features that make writing and managing responsive CSS significantly easier through nested media queries, reusable mixins, and systematic breakpoint management.

Why SASS Improves Responsive Design

SASS enhances responsive development by:

  • Nesting media queries: Write @media rules inside selectors for better organization
  • Reusable breakpoints: Define breakpoints once, use them everywhere
  • Mixins with @content: Create responsive mixins that accept content blocks
  • Mathematical calculations: Compute fluid layouts and proportional sizing
  • DRY principles: Eliminate repetitive media query code

Nesting @media Inside Selectors

Traditional CSS Approach

In plain CSS, media queries must contain selectors, leading to repetition:

Traditional CSS Media Queries

/* Plain CSS - repetitive */
.sidebar {
  width: 100%;
  padding: 1rem;
}

@media (min-width: 768px) {
  .sidebar {
    width: 300px;
    padding: 1.5rem;
  }
}

@media (min-width: 1024px) {
  .sidebar {
    width: 350px;
    padding: 2rem;
  }
}

SASS Nested Approach

SASS allows you to nest @media rules inside selectors, keeping related styles together:

SASS Nested Media Queries

// SASS - organized and maintainable
.sidebar {
  width: 100%;
  padding: 1rem;

  @media (min-width: 768px) {
    width: 300px;
    padding: 1.5rem;
  }

  @media (min-width: 1024px) {
    width: 350px;
    padding: 2rem;
  }
}

// Compiled CSS output:
// .sidebar { width: 100%; padding: 1rem; }
// @media (min-width: 768px) {
//   .sidebar { width: 300px; padding: 1.5rem; }
// }
// @media (min-width: 1024px) {
//   .sidebar { width: 350px; padding: 2rem; }
// }
Note: When you nest @media inside a selector, SASS automatically "bubbles up" the media query in the compiled CSS. The selector is wrapped inside the media query, not the other way around.

Complex Nesting Examples

Multiple Nested Media Queries

.card {
  background: white;
  padding: 1rem;
  margin-bottom: 1rem;

  .card-title {
    font-size: 1.25rem;
    margin-bottom: 0.5rem;

    @media (min-width: 768px) {
      font-size: 1.5rem;
    }
  }

  .card-content {
    font-size: 1rem;
    line-height: 1.5;
  }

  @media (min-width: 768px) {
    padding: 1.5rem;
    display: flex;
    gap: 1rem;

    .card-title {
      margin-bottom: 0.75rem;
    }
  }

  @media (min-width: 1024px) {
    padding: 2rem;
    margin-bottom: 2rem;
  }
}

Building a Breakpoint System

Define Breakpoint Variables

Create a centralized system for managing breakpoints:

Breakpoint Variables

// Simple variable approach
$breakpoint-sm: 576px;
$breakpoint-md: 768px;
$breakpoint-lg: 992px;
$breakpoint-xl: 1200px;
$breakpoint-xxl: 1400px;

.container {
  width: 100%;

  @media (min-width: $breakpoint-md) {
    max-width: 720px;
  }

  @media (min-width: $breakpoint-lg) {
    max-width: 960px;
  }

  @media (min-width: $breakpoint-xl) {
    max-width: 1140px;
  }
}

Breakpoint Map System

Use a map for more flexible breakpoint management:

Map-Based Breakpoints

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

// Get breakpoint value
@function breakpoint($name) {
  @if map-has-key($breakpoints, $name) {
    @return map-get($breakpoints, $name);
  }

  @warn "Breakpoint '#{$name}' not found in $breakpoints map.";
  @return null;
}

// Usage
.element {
  width: 100%;

  @media (min-width: breakpoint(md)) {
    width: 750px;
  }
}

Creating Responsive Mixins

Basic Responsive Mixin

Create a mixin that generates media queries with @content:

Simple Media Query Mixin

@mixin respond-above($breakpoint) {
  @media (min-width: $breakpoint) {
    @content;
  }
}

@mixin respond-below($breakpoint) {
  @media (max-width: $breakpoint - 1px) {
    @content;
  }
}

// Usage
.navigation {
  display: none;

  @include respond-above(768px) {
    display: flex;
    justify-content: space-between;
  }
}

.mobile-menu {
  display: block;

  @include respond-above(768px) {
    display: none;
  }
}
Tip: The @content directive allows mixins to accept a block of styles. This is essential for responsive mixins, as it lets you pass different styles for each breakpoint.

Advanced Breakpoint Mixin System

Build a comprehensive system with named breakpoints:

Complete Breakpoint Mixin System

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

// Respond above breakpoint (mobile-first)
@mixin respond-above($name) {
  $breakpoint: map-get($breakpoints, $name);

  @if $breakpoint {
    @if $breakpoint > 0 {
      @media (min-width: $breakpoint) {
        @content;
      }
    } @else {
      @content; // No media query for xs
    }
  } @else {
    @warn "Breakpoint '#{$name}' not found.";
  }
}

// Respond below breakpoint (desktop-first)
@mixin respond-below($name) {
  $breakpoint: map-get($breakpoints, $name);

  @if $breakpoint {
    @media (max-width: $breakpoint - 1px) {
      @content;
    }
  } @else {
    @warn "Breakpoint '#{$name}' not found.";
  }
}

// Respond between two breakpoints
@mixin respond-between($min, $max) {
  $min-bp: map-get($breakpoints, $min);
  $max-bp: map-get($breakpoints, $max);

  @if $min-bp and $max-bp {
    @media (min-width: $min-bp) and (max-width: $max-bp - 1px) {
      @content;
    }
  }
}

// Usage examples
.component {
  padding: 1rem;

  @include respond-above(md) {
    padding: 1.5rem;
  }

  @include respond-above(lg) {
    padding: 2rem;
  }

  @include respond-between(sm, md) {
    background: lightblue;
  }
}

Mobile-First vs Desktop-First

Mobile-First Approach

Start with mobile styles and progressively enhance for larger screens:

Mobile-First Pattern

// Mobile-First: Base styles for mobile, enhance upward
.grid {
  // Mobile (default)
  display: block;

  // Tablet and up
  @include respond-above(md) {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 1rem;
  }

  // Desktop and up
  @include respond-above(lg) {
    grid-template-columns: repeat(3, 1fr);
    gap: 1.5rem;
  }

  // Large desktop and up
  @include respond-above(xl) {
    grid-template-columns: repeat(4, 1fr);
    gap: 2rem;
  }
}

Desktop-First Approach

Start with desktop styles and progressively simplify for smaller screens:

Desktop-First Pattern

// Desktop-First: Base styles for desktop, simplify downward
.grid {
  // Desktop (default)
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 2rem;

  // Below large desktop
  @include respond-below(xl) {
    grid-template-columns: repeat(3, 1fr);
    gap: 1.5rem;
  }

  // Below desktop
  @include respond-below(lg) {
    grid-template-columns: repeat(2, 1fr);
    gap: 1rem;
  }

  // Below tablet
  @include respond-below(md) {
    display: block;
  }
}
Note: Mobile-first is generally recommended because it aligns with progressive enhancement, results in smaller CSS for mobile devices, and matches how most users access the web today.

Building a Complete Breakpoint System

Professional Responsive Architecture

Complete Responsive System

// _breakpoints.scss
$breakpoints: (
  xs: 0,
  sm: 576px,
  md: 768px,
  lg: 992px,
  xl: 1200px,
  xxl: 1400px
) !default;

// Breakpoint mixins
@mixin respond-above($name) {
  $bp: map-get($breakpoints, $name);
  @if $bp {
    @if $bp == 0 {
      @content;
    } @else {
      @media (min-width: $bp) {
        @content;
      }
    }
  }
}

@mixin respond-below($name) {
  $bp: map-get($breakpoints, $name);
  @if $bp {
    @media (max-width: $bp - 1px) {
      @content;
    }
  }
}

@mixin respond-between($min, $max) {
  $min-bp: map-get($breakpoints, $min);
  $max-bp: map-get($breakpoints, $max);
  @if $min-bp and $max-bp {
    @media (min-width: $min-bp) and (max-width: $max-bp - 1px) {
      @content;
    }
  }
}

@mixin respond-only($name) {
  @if $name == xs {
    @include respond-below(sm) { @content; }
  } @else {
    $keys: map-keys($breakpoints);
    $index: index($keys, $name);
    $next-name: nth($keys, $index + 1);
    @include respond-between($name, $next-name) { @content; }
  }
}

// Practical usage
.header {
  padding: 1rem;
  background: white;

  @include respond-above(md) {
    padding: 1.5rem 2rem;
    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  }

  .logo {
    height: 40px;

    @include respond-above(md) {
      height: 50px;
    }
  }

  .nav {
    display: none;

    @include respond-above(lg) {
      display: flex;
      gap: 2rem;
    }
  }

  .mobile-toggle {
    display: block;

    @include respond-above(lg) {
      display: none;
    }
  }
}

Combining Multiple Media Conditions

Complex Media Queries

Combine multiple conditions using and, or, and not:

Advanced Media Query Combinations

// Orientation-specific styles
.gallery {
  @media (orientation: landscape) and (min-width: 768px) {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
  }

  @media (orientation: portrait) {
    display: flex;
    flex-direction: column;
  }
}

// High-resolution displays
.icon {
  background-image: url("icon.png");

  @media (min-resolution: 2dppx) {
    background-image: url("icon@2x.png");
  }
}

// Print styles
.page {
  @media print {
    color: black;
    background: white;

    .no-print {
      display: none;
    }
  }
}

// Dark mode support
.theme {
  @media (prefers-color-scheme: dark) {
    background: #222;
    color: #eee;
  }
}

// Reduced motion preference
.animated-element {
  transition: all 0.3s ease;

  @media (prefers-reduced-motion: reduce) {
    transition: none;
  }
}

Container Queries with SASS

Modern Container Query Support

CSS Container Queries allow elements to respond to their parent's size:

Container Queries

// Define container
.card-grid {
  container-type: inline-size;
  container-name: card-grid;
}

// Container query mixin
@mixin container-above($name, $width) {
  @container #{$name} (min-width: #{$width}) {
    @content;
  }
}

// Usage
.card {
  padding: 1rem;

  @include container-above(card-grid, 400px) {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 1rem;
  }

  @include container-above(card-grid, 600px) {
    padding: 1.5rem;
    gap: 1.5rem;
  }
}

Exercise 1: Complete Navigation System

Build a fully responsive navigation:

  1. Create mobile menu (hidden hamburger menu, full-screen overlay)
  2. Create tablet menu (horizontal with dropdowns)
  3. Create desktop menu (full navigation with mega-menu)
  4. Use respond-above mixins for breakpoint management
  5. Add smooth transitions between states
  6. Support for reduced-motion preference

Exercise 2: Fluid Grid System

Create a flexible grid system with SASS:

  1. Define breakpoints map (xs, sm, md, lg, xl)
  2. Create column classes: .col-1 through .col-12
  3. Create responsive classes: .col-md-6, .col-lg-4, etc.
  4. Add offset classes: .offset-md-2, .offset-lg-3
  5. Include order utilities: .order-first, .order-last, .order-md-1
  6. Add display utilities that work with breakpoints

Exercise 3: Responsive Typography Scale

Build a typography system that scales with viewport:

  1. Define base font sizes for each breakpoint in a map
  2. Create heading sizes (h1-h6) that scale proportionally
  3. Use SASS calculations to maintain ratios across breakpoints
  4. Include line-height adjustments for different screen sizes
  5. Add fluid typography using calc() and viewport units
  6. Create utility classes for responsive text alignment

Best Practices

Media Query Organization Tips

  • Keep related styles together: Nest media queries within selectors rather than creating separate files
  • Use named breakpoints: Avoid magic numbers; use meaningful variable names
  • Prefer mobile-first: Start with mobile and enhance for larger screens
  • Limit breakpoints: Use 4-6 breakpoints maximum; more creates complexity
  • Test on real devices: Don't rely solely on browser dev tools
  • Consider content: Let content determine breakpoints, not devices

Summary

In this lesson, you learned:

  • Nesting @media queries inside selectors for better organization
  • How SASS "bubbles up" nested media queries in compiled CSS
  • Creating centralized breakpoint systems with variables and maps
  • Building reusable responsive mixins with @content
  • Mobile-first vs desktop-first approaches and their tradeoffs
  • Creating complete breakpoint mixin systems (above, below, between, only)
  • Combining multiple media conditions (orientation, resolution, preferences)
  • Modern container queries with SASS
  • Best practices for organizing responsive code

SASS transforms responsive design from a repetitive chore into an elegant, maintainable system. By leveraging nested media queries, reusable mixins, and centralized breakpoint management, you can create sophisticated responsive layouts with significantly less code and better organization than plain CSS.