Codeskill

Learn to code, step by step

Responsive mixins that stay readable

Breakpoint mixins that keep media queries consistent without burying your component logic three levels deep. The goal is responsive SCSS you can still read aloud..

Breakpoint map

Define breakpoints once:

$breakpoints: (
  'sm': 30rem,   // 480px
  'md': 48rem,   // 768px
  'lg': 64rem,   // 1024px
  'xl': 80rem,   // 1280px
);

Use em or rem for breakpoints where you can – they respect user font settings better than raw pixels.

A simple min-width mixin

@use 'sass:map';

@mixin bp-up($name) {
  $width: map.get($breakpoints, $name);
  @if $width {
    @media (min-width: $width) {
      @content;
    }
  } @else {
    @warn 'Unknown breakpoint: #{$name}';
  }
}

Usage keeps the default mobile-first styles outside the query:

.card-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: $space-md;

  @include bp-up('md') {
    grid-template-columns: repeat(2, 1fr);
  }

  @include bp-up('lg') {
    grid-template-columns: repeat(3, 1fr);
  }
}

Max-width and between

@mixin bp-down($name) {
  $width: map.get($breakpoints, $name);
  @media (max-width: ($width - 0.02rem)) {
    @content;
  }
}

@mixin bp-between($from, $to) {
  $min: map.get($breakpoints, $from);
  $max: map.get($breakpoints, $to);
  @media (min-width: $min) and (max-width: ($max - 0.02rem)) {
    @content;
  }
}

The tiny subtract avoids overlap glitches when min- and max-width queries meet at the same pixel.

Avoid mixin soup

Bad pattern – entire components defined only inside breakpoints:

// Hard to follow
.hero {
  @include bp-up('md') {
    @include bp-up('lg') {
      padding: $space-2xl;
    }
  }
}

Better: write the component top to bottom, one breakpoint block at a time, smallest to largest. If a file needs more than two nested @include bp-up calls, split the component or simplify the layout.

Prefer container queries where fit

Breakpoints tune the viewport. Container queries tune the component’s parent – often the right tool for cards and sidebars. SCSS can wrap them the same way:

@mixin container-up($width) {
  @container (min-width: #{$width}) {
    @content;
  }
}

.card {
  container-type: inline-size;

  @include container-up(25rem) {
    display: flex;
    gap: $space-md;
  }
}

Native CSS nesting (see a later tutorial) can reduce mixin noise for simple queries. Breakpoint mixins still earn their keep for shared names and consistent units.

Try it

  • Add a breakpoint map and bp-up mixin to your abstracts folder.
  • Refactor one component from raw @media to the mixin.
  • Read the file aloud – if you lose the thread, flatten nesting.
PreviousMaps and loops for design systems