The PostCSS Guide to Improving Selectors and Media Queries
Recent changes to the CSS specifications have introduced quite a few interesting features. Unfortunately, some of them are still in a draft state and the ones that aren’t lack broad browser support. As usual, it will take a while for the new suggestions to be reviewed, accepted and implemented by the browsers. However, we can spare ourselves the long wait and try out some of these features already using PostCSS.
PostCSS has a variety of plugins aiming at implementing polyfills for the newest CSS features. Since the spectrum of these plugins is broad, it would be difficult to cover all of them in a single article. Instead, we will narrow our gaze to a selection of plugins that focus on adding new capabilities to selectors and media queries. Many of them will allow us to significantly improve the structure of our stylesheets while others may just add some sweet syntax sugar on top.
We won’t get into the details of configuring and installing PostCSS in this article. This has already been covered in “An introduction to PostCSS” and “Improving the quality of your CSS with PostCSS“. For a quick reference you can also check out the PostCSS page on GitHub.
Rule Nesting
Let’s start with what is likely to be the most basic feature of all — and certainly familiar to every pre-processor user — nesting. The postcss-nesting plugin implements style rule nesting according to the W3C nesting module proposal.
The proposal introduces a new &
nesting selector which references the parent selector. As opposed to Less or Sass, according to the specification, this selector is mandatory and must be the first basic selector in a chain of selectors to enable nesting. Any selectors which do not contain the nesting selector will be ignored. For example:
.article {
color: #333;
&.popular {
background: #DDD;
}
& .title {
font-weight: bold;
}
}
Will be translated into:
.article {
color: #333
}
.article.popular {
background: #DDD
}
.article .title {
font-weight: bold
}
Note that the following code is invalid, since it doesn’t use the &
selector as specified.
.article {
color: #333;
/* no nested-selector */
.popular {
background: #DDD;
}
/* the nested selector is not the fist selector in the chain */
.latest & {
border: 1px solid red;
}
}
To allow inserting of the parent selector into any place in a selector (instead of just the beginning), the proposal defines an alternative syntax using the nesting at-rule @nest
. We can fix the .latest &
selector from the previous example in the following way:
.article {
color: #333;
@nest .latest & {
border: 1px solid red;
}
}
Which will compile into:
.article {
color: #333
}
.latest .article {
border: 1px solid red
}
The @nest
syntax is also a tad more expressive then just &
.
Custom Selectors
When writing CSS, we tend to write a lot of repeating selectors. This may be simple boilerplate code to select all links or any button, or a more complex selector that needs to be repeated over and over. This can introduce a lot of code duplication along with all the related problems of code maintenance. The new CSS extensions specification introduces a way to store selectors in variables and reference them from other parts of the stylesheets. Thus, a repeating selector can be defined only once and safely reused elsewhere.
PostCSS has a postcss-custom-selectors plugin that implements this feature. Here’s a simple example that defines a selector for all header elements:
@custom-selector :--heading h1, h2, h3, h4, h5, h6;
:--heading {
font-weight: bold;
}
It will compile into:
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: bold;
}
Custom selectors are implemented as pseudo-classes, hence the odd looking :--
syntax.
Custom selectors can be effectively used together with basic selectors. For example:
.article :--heading .author {
color: blue;
}
Gets compiled into:
.article h1 .author,
.article h2 .author,
.article h3 .author,
.article h4 .author,
.article h5 .author,
.article h6 .author {
color: blue;
}
You can even combine multiple custom selectors in a single selector to achieve more complex permutations.
@custom-selector :--links a, a:focus, a:visited, a:hover, a:active;
article :--heading :--links {
color: #333;
}
Will result in:
article h1 a,
article h2 a,
article h3 a,
article h4 a,
article h5 a,
article h6 a,
article h1 a:focus,
article h2 a:focus,
article h3 a:focus,
article h4 a:focus,
article h5 a:focus,
article h6 a:focus,
article h1 a:visited,
article h2 a:visited,
article h3 a:visited,
article h4 a:visited,
article h5 a:visited,
article h6 a:visited,
article h1 a:hover,
article h2 a:hover,
article h3 a:hover,
article h4 a:hover,
article h5 a:hover,
article h6 a:hover,
article h1 a:active,
article h2 a:active,
article h3 a:active,
article h4 a:active,
article h5 a:active,
article h6 a:active {
color: #333;
}
This example might be slightly exaggerated, but it gives a nice demonstration of the power of this feature.
New Pseudo-Classes
The Selectors Level 4 specification introduced a whole bunch of new pseudo-classes, but due to the dynamic nature of most of them, only several are available as PostCSS plugins.
The :matches()
Pseudo-class
The postcss-selector-matches plugin implements the new :matches()
pseudo-class. It is a functional class that filters only the elements that match the selectors in the argument. If you pass multiple selectors, then the element has to match at least one of them. In short:
button:matches(:hover, :focus) {
color: red;
}
Is compiled into:
button:hover, button:focus {
color: red;
}
The :not()
Pseudo-class
The :not()
pseudo-class filters elements that do not match any of the given arguments. It is implemented in the postcss-selector-not plugin.
section:not(:first-child, :last-child) {
background: white;
}
Results in:
section:not(:first-child):not(:last-child) {
background: white;
}
The :any-link
Pseudo-class
The postcss-pseudo-class-any-link plugin implements the :any-link
pseudo-class. It was introduced to help resolve the confusion around the :link
pseudo class. Unlike the latter, it matches all links — including visited ones.
a:any-link {
color: blueviolet;
}
Is compiled into:
a:link,a:visited {
color: blueviolet;
}
Improvements to Media Queries
There’s a couple of plugins in the arsenal of PostCSS designed to make media queries easier to use. They are postcss-custom-media and postcss-media-minmax.
Custom Media
Writing media queries yields the same problem as regular selectors — they are repeated frequently across stylesheets. Probably even more frequently than plain selectors. Luckily, there’s a solution similar to custom selectors. The postcss-custom-media plugin implements the custom media query specification which adds the ability to save media queries into variables.
The syntax closely resembles custom selectors. Here’s a simple example:
@custom-media --medium-viewport (min-width: 768px) and (max-width: 992px);
@media (--medium-viewport) {
/* your styles here */
}
Is compiled into:
@media (min-width: 768px) and (max-width: 992px) {
/* your styles here */
}
Of course, you can use multiple custom media queries at once:
@custom-media --landscape (orientation: landscape);
@media (--medium-viewport) and (--landscape) {
/* your styles here */
}
Will result in:
@media (min-width: 768px) and (max-width: 992px) and (orientation: landscape) {
/* your styles here */
}
As you can see, now it’s much easier to change your definition of a “medium viewport” as well as look for any related CSS code.
Min and Max Syntax
Although media queries are a great thing, the min-
and max-
syntax has received a lot of hatred from the community. W3C responded by introducing a more intuitive syntax using comparison operators. The postcss-media-minmax plugin adds support for >
, >=
, <
and <=
operators. It also allows us to use the value-in-the-middle syntax, that is, min-value < property < max-value
.
We can simplify the previous examples using the new syntax, which will yield the same result.
@custom-media --medium-viewport (768px <= width <= 992px);
@media (--medium-viewport) {
/* your styles here */
}
Of course, the plugin will also work without using custom media.
Don’t Stop Here
Unfortunately, not many of the new CSS features can be implemented as a PostCSS plugin because of their dynamic nature and dependency on the DOM. We’ll need to wait for native support for most of them. But there are still plenty of plugins to explore and test in your workflow. This article focused on the plugins related to selectors and media queries, but we hope to bring you overviews of other novelties as well. If you’re curious and impatient, you can find more of them at postcss.parts. Keep exploring!