Don’t hesitate to contact us if you have any feedback.

SCSS – Media & Container Queries

Introduction to Responsive Design Variables

To kickstart a responsive design, a set of predefined breakpoints is essential. These breakpoints serve as the foundation for media and container queries that adjust styles based on viewport sizes or container dimensions. The SCSS snippet below defines a map of breakpoints:

$grid-breakpoints: (
  xxs: 0,
  xs: 350px,
  sm: 600px,
  md: 800px,
  lg: 1000px,
  xl: 1200px,
  xxl: 1440px,
  fhd: 1920px,
  2th: 2000px
);

Responsive Design Snippet

The SCSS snippet provided offers a comprehensive set of functions and mixins for implementing media and container queries:

  • Functions like breakpoint-next, breakpoint-min, and breakpoint-max help to determine the next breakpoint, as well as minimum and maximum widths based on the breakpoint map.
  • Mixins such as media-breakpoint-up, media-breakpoint-down, media-breakpoint-between, and media-breakpoint-only allow for creating media queries that target specific ranges of viewport sizes.
  • Similar mixins for container queries are also provided, namely container-breakpoint-up, container-breakpoint-down, container-breakpoint-between, and container-breakpoint-only. These are tailored to style elements based on the size of their parent containers, a newer concept in responsive design.
@use 'sass:list';
@use 'sass:map';
@use 'variables';

// Breakpoint viewport sizes, media queries and container queries.
//
// Breakpoints are defined as a map of (name: minimum width), order from small to large:
//
//    (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px)
//
// The map defined in the `$grid-breakpoints` global variable is used as the `$breakpoints` argument by default.

// Name of the next breakpoint, or null for the last breakpoint.
//
//    >> breakpoint-next(sm)
//    md
//    >> breakpoint-next(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))
//    md
//    >> breakpoint-next(sm, $breakpoint-names: (xs sm md lg xl))
//    md
@function breakpoint-next($name, $breakpoints: variables.$grid-breakpoints, $breakpoint-names: map.keys($breakpoints)) {
  $n: list.index($breakpoint-names, $name);

  @if not $n {
    @error 'breakpoint `#{$name}` not found in `#{$breakpoints}`';
  }

  @return if($n < list.length($breakpoint-names), list.nth($breakpoint-names, $n + 1), null);
}

// Minimum breakpoint width. Null for the smallest (first) breakpoint.
//
//    >> breakpoint-min(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))
//    576px
@function breakpoint-min($name, $breakpoints: variables.$grid-breakpoints) {
  $min: map.get($breakpoints, $name);

  @return if($min != 0, $min, null);
}

// Maximum breakpoint width.
// The maximum value is reduced by 0.02px to work around the limitations of
// `min-` and `max-` prefixes and viewports with fractional widths.
// See https://www.w3.org/TR/mediaqueries-4/#mq-min-max
// Uses 0.02px rather than 0.01px to work around a current rounding bug in Safari.
// See https://bugs.webkit.org/show_bug.cgi?id=178261
//
//    >> breakpoint-max(md, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))
//    767.98px
@function breakpoint-max($name, $breakpoints: variables.$grid-breakpoints) {
  $max: map.get($breakpoints, $name);

  @return if($max and $max > 0, $max - 0.02, null);
}

// Returns a blank string if smallest breakpoint, otherwise returns the name with a dash in front.
// Useful for making responsive utilities.
//
//    >> breakpoint-infix(xs, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))
//    ""  (Returns a blank string)
//    >> breakpoint-infix(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))
//    "-sm"
@function breakpoint-infix($name, $breakpoints: variables.$grid-breakpoints) {
  @return if(breakpoint-min($name, $breakpoints) == null, '', '-#{$name}');
}

// Media of at least the minimum breakpoint width. No query for the smallest breakpoint.
// Makes the @content apply to the given breakpoint and wider.
@mixin media-breakpoint-up($name, $breakpoints: variables.$grid-breakpoints) {
  $min: breakpoint-min($name, $breakpoints);

  @if $min {
    @media (width >= #{$min}) {
      @content;
    }
  } @else {
    @content;
  }
}

// Container of at least the minimum breakpoint width. No query for the smallest breakpoint.
// Makes the @content apply to the given breakpoint and wider.
@mixin container-breakpoint-up($parent-container-name, $name, $breakpoints: variables.$grid-breakpoints) {
  $min: breakpoint-min($name, $breakpoints);

  @if $min {
    @container #{$parent-container-name} (width >= #{$min}) {
      @content;
    }
  } @else {
    @content;
  }
}

// Media of at most the maximum breakpoint width. No query for the largest breakpoint.
// Makes the @content apply to the given breakpoint and narrower.
@mixin media-breakpoint-down($name, $breakpoints: variables.$grid-breakpoints) {
  $max: breakpoint-max($name, $breakpoints);

  @if $max {
    @media (width <= #{$max}) {
      @content;
    }
  } @else {
    @content;
  }
}

// Container of at least the minimum breakpoint width. No query for the smallest breakpoint.
// Makes the @content apply to the given breakpoint and wider.
@mixin container-breakpoint-down($parent-container-name, $name, $breakpoints: variables.$grid-breakpoints) {
  $max: breakpoint-max($name, $breakpoints);

  @if $max {
    @container #{$parent-container-name} (width <= #{$max}) {
      @content;
    }
  } @else {
    @content;
  }
}

// Media that spans multiple breakpoint widths.
// Makes the @content apply between the min and max breakpoints
@mixin media-breakpoint-between($lower, $upper, $breakpoints: variables.$grid-breakpoints) {
  $min: breakpoint-min($lower, $breakpoints);
  $max: breakpoint-max($upper, $breakpoints);

  @if $min != null and $max != null {
    @media (#{$min} <= width  <= #{$max}) {
      @content;
    }
  } @else if $max == null {
    @include media-breakpoint-up($lower, $breakpoints) {
      @content;
    }
  } @else if $min == null {
    @include media-breakpoint-down($upper, $breakpoints) {
      @content;
    }
  }
}

// Container that spans multiple breakpoint widths.
// Makes the @content apply between the min and max breakpoints
@mixin container-breakpoint-between($parent-container-name, $lower, $upper, $breakpoints: variables.$grid-breakpoints) {
  $min: breakpoint-min($lower, $breakpoints);
  $max: breakpoint-max($upper, $breakpoints);

  @if $min != null and $max != null {
    @container #{$parent-container-name} (#{$min} <= width  <= #{$max}) {
      @content;
    }
  } @else if $max == null {
    @include container-breakpoint-up($lower, $breakpoints) {
      @content;
    }
  } @else if $min == null {
    @include container-breakpoint-down($upper, $breakpoints) {
      @content;
    }
  }
}

// Media between the breakpoint's minimum and maximum widths.
// No minimum for the smallest breakpoint, and no maximum for the largest one.
// Makes the @content apply only to the given breakpoint, not viewports any wider or narrower.
@mixin media-breakpoint-only($name, $breakpoints: variables.$grid-breakpoints) {
  $min: breakpoint-min($name, $breakpoints);
  $next: breakpoint-next($name, $breakpoints);
  $max: breakpoint-max($next);

  @if $min != null and $max != null {
    @media (#{$min} <= width  <= #{$max}) {
      @content;
    }
  } @else if $max == null {
    @include media-breakpoint-up($name, $breakpoints) {
      @content;
    }
  } @else if $min == null {
    @include media-breakpoint-down($next, $breakpoints) {
      @content;
    }
  }
}

// Container between the breakpoint's minimum and maximum widths.
// No minimum for the smallest breakpoint, and no maximum for the largest one.
// Makes the @content apply only to the given breakpoint, not viewports any wider or narrower.
@mixin container-breakpoint-only($parent-container-name, $name, $breakpoints: variables.$grid-breakpoints) {
  $min: breakpoint-min($name, $breakpoints);
  $next: breakpoint-next($name, $breakpoints);
  $max: breakpoint-max($next);

  @if $min != null and $max != null {
    @container #{$parent-container-name} (#{$min} <= width <= #{$max}) {
      @content;
    }
  } @else if $max == null {
    @include container-breakpoint-up($name, $breakpoints) {
      @content;
    }
  } @else if $min == null {
    @include container-breakpoint-down($next, $breakpoints) {
      @content;
    }
  }
}

Media Query usage example

The following SCSS example demonstrates how to apply different background colors to the <main> element based on various viewport widths using media queries:

body {
  main {
    @include media-breakpoint-up(sm) {
      background-color: red;
    }

    @include media-breakpoint-down(md) {
      background-color: blue;
    }

    @include media-breakpoint-between(md, lg) {
      background-color: green;
    }

    @include media-breakpoint-only(lg) {
      background-color: yellow;
    }
  }
}

Container Query usage example

This SCSS example shows how to style <article> elements differently based on the size of their parent <section> container, thanks to container queries:

section {
  container-type: inline-size;
  container-name: section;

  article {
    @include container-breakpoint-up(section, sm) {
      background-color: red;
    }

    @include container-breakpoint-down(section, md) {
      background-color: blue;
    }

    @include container-breakpoint-between(section, md, lg) {
      background-color: green;
    }

    @include container-breakpoint-only(section, lg) {
      background-color: yellow;
    }
  }
}

The accompanying HTML structure provides a basic setup to see these styles in action:

<body>
  <main>
    <section>
      <article></article>
      <article></article>
      <article></article>
    </section>
  </main>
</body>

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *