@media & Responsive Design with SASS
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; }
// }
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;
}
}
@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;
}
}
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:
- Create mobile menu (hidden hamburger menu, full-screen overlay)
- Create tablet menu (horizontal with dropdowns)
- Create desktop menu (full navigation with mega-menu)
- Use respond-above mixins for breakpoint management
- Add smooth transitions between states
- Support for reduced-motion preference
Exercise 2: Fluid Grid System
Create a flexible grid system with SASS:
- Define breakpoints map (xs, sm, md, lg, xl)
- Create column classes: .col-1 through .col-12
- Create responsive classes: .col-md-6, .col-lg-4, etc.
- Add offset classes: .offset-md-2, .offset-lg-3
- Include order utilities: .order-first, .order-last, .order-md-1
- Add display utilities that work with breakpoints
Exercise 3: Responsive Typography Scale
Build a typography system that scales with viewport:
- Define base font sizes for each breakpoint in a map
- Create heading sizes (h1-h6) that scale proportionally
- Use SASS calculations to maintain ratios across breakpoints
- Include line-height adjustments for different screen sizes
- Add fluid typography using calc() and viewport units
- 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.