Custom media queries are a bit like CSS custom properties (CSS variables), but for Media Queries (spec).
They are part or the not-yet-standardized CSS Media Queries specs level 4 and 5. Currently there’s zero browser support for them, but they’re usable right now thanks to PostCSS Preset Env or the dedicated (PostCSS Custom Media plugin).
- Declaration
- Use
- Queries combinations
- Comparison chart
- What’s Next
- Example: dark mode
- Example: complex query
Declaring a custom media query must be done outside selectors rulesets, using the @custom-media
at rule.
/* @custom-media <name> <value> */
@custom-media --large-text (min-width: 87em);
(This verbose syntax is shorter with Double Dash ✌️.)
The declared --large-text
can be used in any @media
rules:
@media (--large-text) {
.quotes { font-size: 3.5rem }
}
Let’s try to target a wearable watch using its viewport width and height:
/* Media queries for max-width and max-height */
@custom-media --watch-max-w screen and (max-width: 270px);
@custom-media --watch-max-h screen and (max-height: 270px);
This queries the wearable:
@media (--watch-max-w) and (--watch-max-h) { … }
Instead of composing the features list in @media
, let’s combine it at declaration:
@custom-media --watch (--watch-max-w) and (--watch-max-h);
This shorten the use in @media
to:
@media (--watch) { … }
. | Plain MQ | SCSS | Custom MQ | Custom MQ + Double Dash |
---|---|---|---|---|
Standardized | ✅ | ❌ | ☑️ (ongoing) | - |
Don’t need SCSS | ✅ | ❌ | ✅ | ❌ |
Don’t need PostCSS now | ✅ | ✅ | ❌ | ❌ |
Won’t need PostCSS in the future | ✅ | ✅ | ✅ | ✅ |
Concise declaration | no declaration | ❌ (mixins and/or variables) | ❌ | ✅ partly invisible |
Concise use | ❌ | ☑️ (almost) | ✅ | ✅ |
Combining queries | easy but low readability | nesting or advanced mixins | easy and readable | easy and readable |
Explicit naming | ❌ | ☑️ (a lot of efforts for queries combinations) | ✅ | ✅ |
No naming conflict with SCSS variables | ✅ | ❌ (when using breakpoint variables) | ✅ | ✅ |
Lighter CSS bundle in the future | ❌ | ❌ | ✅ | ✅ |
- Media query ranges, another cool thing from the CSS Media Queries specs level 4 and 5.
- Supercharge your custom media queries workflow with Double Dash!!
@media (prefers-color-scheme: dark) {
:root {
--primary: white;
--background: black;
}
}
@custom-media --dark (prefers-color-scheme: dark);
@media (--dark) {
:root {
--primary: white;
--background: black;
}
}
(Double Dash has already declared --dark
and many more for you ✌️.)
(Todo: provide Codepen link.)
For this example, we style a navigation menu:
- sometimes sticky, sometimes not;
- sometimes with a background, sometimes without.
Words are not the best way to shape a clear understanding of what we try to achieve. But let’s try.
The navigation menu:
- is sticky on small viewports (except when the height is too small);
- has a transparent background when sticky;
- has a background image on greater viewport.
The served background image varies depending on:
- viewport pixels density;
- color scheme.
In other words, this advanced scenario relies on 4 factors:
- the viewport size;
- the screen pixel density;
- the operating system color schemes;
- the stickiness state of the navigation menu.
The @media
features lists are long and painful to understand:
/* Sticky with transparent background */
@media (min-width: 320px) and (max-width: 799px) and (min-height: 500px) {
.nav {
position: sticky;
background: transparent;
}
}
/* Not sticky with dark background for high-resolution screens */
@media (min-width: 800px) and (prefers-color-scheme: dark) and (min-resolution: 124.8dpi), (min-width: 800px) and (prefers-color-scheme: dark) and (min-resolution: 1.3dppx) {
.nav {
background-image: url('bg-dark_2x.png');
}
}
By splitting and naming the features using @custom-media
, the @media
become readable and maintainable.
@custom-media --sticky-nav (min-width: 320px) and (max-width: 799px) and (min-height: 500px);
@custom-media --nav-expanded (min-width: 800px);
@custom-media --dark (prefers-color-scheme: dark);
@custom-media --hidpi (min-resolution: 1.3dppx), (min-resolution: 124.8dpi);
@media (--sticky-nav) {
.nav {
position: sticky;
background: transparent;
}
}
@media (--nav-expanded) and (--dark) and (--hidpi) {
.nav {
background-image: url('bg-dark_2x.png');
}
}
💡 Media queries ranges can make this last example even more readable.