Media queries are good. Responsive web design has been an essential requirement for most web development projects for the better part of a decade now, and media queries are a simple and flexible solution for front-end developers who want to adapt web interfaces for different viewport sizes, pixel densities, device orientations, and even accessibility concerns.
Given their utility, UX devs find themselves writing a lot of media queries. (A quick search of my projects puts a rough estimate at ~75 per theme.) But the @media
syntax can sometimes be confusing and a bit cumbersome to write. But creating shorthand for complex code, and DRY-ing out repetitive style blocks is why SCSS is good, and mixins are even better.
Lets write a practical and concise little SCSS mixin to handle our breakpoints for us with some easy to use shorthand. Better yet, lets bake-in some best practices by default, by making it deigned to work mobile first and fail gracefully.
Example Breakpoint Mixin SCSS
First, lets define some breakpoint values. This lets us keep track of which viewport sizes we’re styling for using meaningful, semantic names instead of abstract pixel or em values.
// Named Breakpoint Values
$breakpoints: (
small: 320px,
medium: 640px,
large: 1024px,
x-large: 1600px,
xx-large: 2800px,
);
And here’s the mixin:
@mixin breakpoints($from: none, $to: none, $media: all) {
// Set some empty min + max values
$min-width: null;
$max-width: null;
// Determine 'from' min-width value
@if (type-of($from) == string) and (map-has-key($breakpoints, $from)) {
// If value is a string listed in the named breakpoints map
$min-width: map-get($breakpoints, $from);
}
@else if ((type-of($from) == number) and (unitless($from) == false)) {
// If value is a number with a unit
$min-width: $from;
}
@else if ($from == none) {
// If the value is blank
$min-width: false;
}
@else {
// If none are true, display compile error
@error '`breakpoints()` min-width value `#{$from}` was not found.';
}
// Determine 'to' max-width value
@if (type-of($to) == string) and (map-has-key($breakpoints, $to)) {
// If value is a string listed in the named breakpoints map
$max-width: map-get($breakpoints, $to);
}
@else if ((type-of($to) == number) and (unitless($to) == false)) {
// If value is a number with a unit
$max-width: $to;
}
@else if ($to == none) {
// If the value is blank
$max-width: false;
}
@else {
// If none are true, display compile error
@error '`breakpoints()` max-width value `#{$to}` was not found.';
}
// If both min-width & max-width values:
@if ($min-width != false) AND ($max-width != false) {
// Increment min-width value by 1px
$min-width: $min-width + 1px;
@media #{$media} and (min-width: #{$min-width}) and (max-width: #{$max-width}) {
// breakpoint mixin content:
@content;
}
}
// If only the min-width value is set:
@else if ($min-width != false) {
// Increment min-width value by 1px
$min-width: $min-width + 1px;
@media #{$media} and (min-width: #{$min-width}) {
// breakpoint mixin content:
@content;
}
}
// If only the max-width value is set:
@else if ($max-width != false) {
@media #{$media} and (max-width: #{$max-width}) {
// breakpoint mixin content:
@content;
}
}
// If min-width & max-width are empty, but special media type set:
@else if ($media != all) {
@media #{$media} {
// breakpoint mixin content:
@content;
}
}
// If no custom media query values are set:
@else {
// Thanks for playing
@content;
}
}
The mixin accepts a “from” value, a “to” value and a media type value as parameters. For each value, we use SASS @if
/@else
operators to determine what min-width
and max-width
values to write out to the compiled @media
rule. Input values can be a named breakpoint from our array, a number with a unit value, or omitted entirely. The mixin also outputs a helpful error message if a named value can’t be found when compiling.
This lets us write mobile styles first and then specify overrides for larger viewports using a simple breakpoints cascade. For example:
.responsive {
// Mobile-First Styles
@include breakpoints(medium) {
// Overrides for 640px and up
}
@includes breakpoints(large) {
// Overrides for 1024px and up
}
}
The conditional logic used for viewport widths and media types can also be expanded to include other media features like resolution
or orientation
if needed.
More Examples
For a more detailed overview and examples, take a look at the the mixin examples in this GitHub Gist.