# sass-mq: media queries from a map of named breakpoints

> sass-mq is a single Sass mixin that takes named breakpoints and turns them into em-based media queries, so a stylesheet says `$from: tablet` instead of a pixel value. Version 7 is the modern `@use` rewrite of a library that the Guardian built in house, and it is still the shortest piece of code in most responsive projects.

**sass-mq/sass-mq** — A Sass mixin that helps you compose media queries in an elegant way.

- Repository: https://github.com/sass-mq/sass-mq
- Website: https://sass-mq.github.io/sass-mq
- Stars: 1,984 · Forks: 143
- Language: SCSS
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/sass-mq-sass-mq

## A map in, media queries out

The whole library is one mixin, `mq()`, and one configuration, a map of breakpoint names to widths.

```scss
@use 'mq' as * with (
  $breakpoints: (
    mobile: 320px,
    tablet: 740px,
    desktop: 980px,
    wide: 1300px,
  )
);

.foo {
  @include mq($from: mobile, $until: tablet) {
    background: red;
  }
}
```

The point of the map is that the names are shared. A stylesheet says `$from: tablet` and the value 740px appears in exactly one place in the project, so changing the tablet width is a one line change and the name in the conversation with a designer is the same word that is in the code.

The compiled output is worth reading, because it is where two design decisions become visible:

```css
@media (min-width: 20em) and (max-width: 46.24em) {
  .foo {
    background: red;
  }
}
@media (min-width: 46.25em) {
  .foo {
    background: green;
  }
}
```

The 320px breakpoint became 20em, and the tablet boundary became 46.25em, because 740px is 46.25em at the default 16px root font size. The library compiles keywords and `px` or `em` values into em-based queries, and the README links an article on why that is a good thing, since a media query in pixels does not change when the reader increases their font size and a query in em does.

The second detail in the compiled output is the 46.24em and 46.25em pair, which is the exclusive boundary discussed later in this article.

sass-mq was crafted in-house at the Guardian, and the README notes that many more companies and developers use it since.

## Why em, and why the browser fallbacks disappeared

Two decisions in the library's history explain most of its behaviour, and both are stated plainly in the README.

The first is that it compiles keywords and `px` or `em` values into em-based queries. A media query in `px` measures the viewport in device pixels regardless of the reader's font size, so a reader who has increased the text size to compensate for a small screen still gets the layout chosen for a small screen, with the text on top of it. A query in `em` is measured against the root font size, so the layout follows the reader's preference. That is the reason for the conversion, and it is a reason that applies to the whole site rather than to one stylesheet.

The second is a removal. For version 6 and up, fallbacks for older browsers were removed, and the README links the Guardian's developer blog post on mobile-first responsive web design and IE8 for the history. Removing the fallbacks is what makes the em conversion safe to do, because a fallback for a browser that cannot parse em-based queries would have to be a pixel query, and mixing the two would produce two different breakpoints in two different browsers.

The cost of that decision is that sass-mq 6 and later assume a browser that understands modern media queries, and the cost of the em conversion is that the width at which a query fires is not the number in the map. A team that thinks in pixels, and whose design specification is written in pixels, will be mildly surprised by 46.25em, and it is worth deciding deliberately rather than discovering it in a review.

The releases show the pace of the change: v6.0.0 on 2024-10-24, v7.0.0 on 2025-06-19 and v7.0.1 on 2026-06-19.

## The @use migration is where the work is

The longest section of the README is about the difference between `@use` and `@import`, and it is there because moving to Sass's module system is the actual upgrade a team performs.

With the old `@import` statement, any variable, function or mixin was exposed in the global scope, so a file could define a variable in the main stylesheet and use it anywhere as long as the main file had been loaded. The drawback was that the library had to make sure it did not pollute the global scope with common names, so the fix was mostly a prefix on variables, functions and mixins.

With `@use`, nothing is placed in the global scope, everything is scoped within the file, and the partial has to be included in every file that uses its members, much like an ES module import. The README shows the before and after side by side, and the difference in the second file is the whole point: `typography.scss` now needs its own `@use 'mq';` and its calls become `mq.mq($from:tablet)`.

The other rules in that section are the ones that surprise people. The file is only imported once, no matter how many times it is used in a project. Members whose names start with an underscore or a hyphen are considered private and are not imported. Members from a used file are available locally but are not passed along to further imports. `@extends` only applies up the chain, so a file can extend selectors in the files it imports but cannot extend files that import it. And all imported members are namespaced by default.

The prefix problem is worth a second look, because it explains the shape of the v7 API. The old global-scope approach needed `$mq-` prefixes to avoid collisions, and the module approach removed the need for them, which is why the current API is `mq.mq(...)` with a namespace rather than a prefixed global.

The README links an introduction to Sass modules for the wider picture, and the project is on version 7.0.1, published on 2026-06-19, with the last push to the repository on 2026-10-17.

## Resolution by package export, and the Vite 8 problem it solved

Installing a Sass library through npm has historically been awkward, because Sass resolves imports through the file system and a package in `node_modules` is not a path the importer knows how to find. sass-mq's answer is the package export mechanism.

When you install sass-mq from npm, you import it by its package name and Sass resolves it from `node_modules`:

```scss
@use 'sass-mq' as mq;

.foo {
  @include mq.mq($from: tablet) {
    color: red;
  }
}
```

The mechanism behind that is declared in the package metadata, which has a `sass` field, a `main` field and an `exports` map whose root entry offers a `sass` condition and a `default` condition, both pointing at `_mq.scss`. The files array also ships `sass/index.scss` alongside the partial, the changelog, the licence and the readme.

The export map is what makes it work with modern tooling, and the README names the tools explicitly: it resolves out of the box with Vite, including Vite 8 which dropped the `main` field fallback, with webpack's `sass-loader`, and with the dart-sass `pkg:` importer. The Vite 8 case is linked to an issue in the repository, and there is a matching example directory at `examples/vite8`.

So the package ships four example directories, `basic`, `advanced`, `custom` and `vite8`, and a build script that compiles the first three with the sass binary and no source map. That is a small thing, and it is a good sign: a library whose examples are compiled in continuous integration is a library whose examples still work.

The other installation routes are still documented, bower, yarn, and downloading `_mq.scss` directly into a project, and the README also suggests trying the mixin in SassMeister before installing anything.

## Responsive mode and the exclusive boundary

The `mq()` mixin takes up to three optional parameters, and the semantics of the middle one is the detail that catches people.

`$from` is the inclusive `min-width` boundary. `$until` is the exclusive `max-width` boundary. `$and` is for additional custom directives. The catch is stated in the README: `$until` as a keyword is a hard limit, that is, the breakpoint minus one.

That is why the compiled output in the earlier example has `max-width: 46.24em` for the tablet boundary and the next query starts at `min-width: 46.25em`. The two are designed to be adjacent and not to overlap, so a viewport exactly at the tablet width is in one query and not in the other, and no element is styled twice at the boundary.

The `$and` parameter takes a raw media feature, so a query can add an orientation condition or any other feature alongside the width:

```scss
@use 'mq';

.responsive {
  @include mq.mq($until: tablet, $and: '(orientation: landscape)') {
    color: hotpink;
  }
  @include mq.mq(tablet, desktop) {
    color: green;
  }
}
```

There is also a verbose notation, which the README says is for cases such as developing a library on top of sass-mq, and which passes every parameter explicitly with `$from: false`, `$until: desktop`, `$and: false` and the media type, which defaults to `all`. The shorthand is the recommended form for readability in a codebase, and the README is explicit that all of the forms produce exactly the same output.

For development there is one more feature. The `$show-breakpoints` setting takes the breakpoint names ordered by width, and the library renders an overlay in the top right corner of the page showing the currently active breakpoint, which is how a developer can see which query a rule is actually living in. The repository carries a recording of it in `show-breakpoints.gif`.

## Where sass-mq is the wrong tool

The limits are small, and they come from the fact that this is a convenience layer rather than a system.

The first is that it needs Sass. A project that has moved to plain CSS with custom properties cannot use a Sass mixin, and the trend in the platform is towards the browser doing the breakpoint work with container queries and `clamp()`. The alternative approach in that direction is to write the media queries by hand with a handful of custom properties for the spacing scale, which is less clever and has no build step.

The second is that the library models a stepped design. A named breakpoint with a boundary is a good fit for a design that changes at a few specific widths, and a poor fit for a fluid one, where a value should interpolate between widths rather than switch. If the design is fluid, the breakpoints are decoration, and a decoration is not worth a dependency.

The third is the upgrade cost, which is the same for every Sass library that has lived through the module migration. Moving from the global-scope API to the namespaced one is a change to every file that uses the mixin, and it is not automatable in a way that a team can trust. Version 6 and version 7 are both major versions with breaking changes, and a project that adopted the library early has to do that work more than once.

The fourth is the dependency itself, and it is worth stating that sass-mq is one file. `_mq.scss` is the entire library, and the npm package exists mostly to publish that one file with the metadata that makes it resolvable. A team that wants the behaviour can copy the partial, which removes the dependency and the upgrade problem at the cost of forking a file that the project maintains, which is a trade that is reasonable for a single file and less reasonable for anything bigger.

## Conclusion

sass-mq fits a team that writes Sass, that has more than one breakpoint, and that wants the same breakpoint names in every stylesheet and in the design conversation. It is the wrong choice for a project that has moved to plain CSS, since the mixin is a compile-time convenience with no runtime behaviour, and the wrong choice for a design that has become fluid rather than stepped, because the library's unit of expression is a named boundary. Before adopting it, check that the em conversion is what your design assumes, because a pixel-to-em conversion changes the width at which a query fires, and read the `@use` guidance in the README, because the move from `@import` to `@use` is where most of the migration work is.

## FAQ

### How do I install sass-mq?

With npm using `npm install sass-mq --save`, with yarn using `yarn add sass-mq`, with bower, or by downloading the _mq.scss file into your Sass project. The README also suggests trying the mixin on SassMeister before installing anything.

### How do I define breakpoints in sass-mq?

Pass a $breakpoints map to the @use directive, for example mobile: 320px, tablet: 740px, desktop: 980px and wide: 1300px, and name them in a way that gives the team a shared vocabulary. The library compiles the pixel values into em-based queries, so 320px becomes 20em.

### What parameters does the mq() mixin take?

Up to three optional ones: $from as the inclusive min-width boundary, $until as the exclusive max-width boundary, and $and for additional custom directives such as an orientation condition. A keyword $until is a hard limit, the breakpoint minus one, which is why the compiled queries are adjacent rather than overlapping.

### How do I use sass-mq with Vite or webpack?

Import it by its package name with `@use 'sass-mq' as mq;` and Sass resolves it from node_modules, because the package declares a sass package export. It works with Vite, including Vite 8 which dropped the main field fallback, with webpack's sass-loader and with the dart-sass pkg: importer, and there is a matching example under examples/vite8.

## Sources

- [License: MIT](https://github.com/sass-mq/sass-mq/blob/main/LICENSE)
- [Project website](https://sass-mq.github.io/sass-mq)
- [README](https://github.com/sass-mq/sass-mq/blob/main/README.md)
- [Releases](https://github.com/sass-mq/sass-mq/releases)
- [sass-mq/sass-mq on GitHub](https://github.com/sass-mq/sass-mq)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/sass-mq-sass-mq
