Using PostCSS Nesting to Keep CSS Readable Without Paying a Runtime Cost
Learn how the postcss-nested plugin lets you write Sass‑like CSS nesting that compiles to plain CSS, improving readability while adding zero runtime overhead.
17 Jul 2025, 00:31 UTC

The problem: flat CSS gets noisy fast
When a component grows, writing plain CSS often leads to long selector chains like .card .card__title or repeated prefixes. Keeping those selectors readable while staying compatible with browsers that only understand flat CSS becomes a maintenance chore.
Thesis: a lightweight nesting plugin lets you write Sass‑like syntax and get plain CSS out the other side, with no browser‑side overhead
PostCSS itself is just a processor; it needs plugins to understand new syntax. The widely used postcss-nested plugin adds nesting that mirrors Sass & references. Because the plugin runs during your build, the output is ordinary CSS that browsers already know how to parse.
How nesting works in the pipeline
The plugin parses a rule such as:
.card {
background: #fff;
&__title {
font-size: 1.2rem;
}
}
and transforms it into flat selectors while preserving source order:
.card {
background: #fff;
}
.card__title {
font-size: 1.2rem;
}
Because the transformation is purely syntactic, there is no extra work for the browser at runtime.
Worked example: adding nesting to a simple build
- Initialize a project and install PostCSS with the nesting plugin:
- Create a source CSS file
src.csswith nested rules: - Add a basic PostCSS configuration that runs nesting first, then Autoprefixer for vendor prefixes:
- Process the file and inspect the output:
- Add
postcss-nestedto your dev dependencies. - Place it early in your PostCSS plugin list so later plugins receive standard CSS.
- Run your build and check the output for flat selectors.
- Keep an eye on specificity—run the output through a selector‑specificity tool if you notice unexpected overrides.
# run in your terminal
npm init -y
npm install --save-dev postcss postcss-nested
/* src.css */
.button {
background: #0066ff;
color: #fff;
&--large {
padding: 1rem 2rem;
}
&:hover {
background: #0052cc;
}
}
/* postcss.config.js */
module.exports = {
plugins: [
require('postcss-nested'),
require('autoprefixer')
]
};
# run in your terminal
npx postcss src.css --dir out
The generated out/src.css should contain flat selectors similar to:
/* out/src.css */
.button {
background: #0066ff;
color: #fff;
}
.button--large {
padding: 1rem 2rem;
}
.button:hover {
background: #0052cc;
}
You can verify that the output contains no nesting syntax and that the selector order matches the source.
Trade‑off: specificity can creep up
Because & resolves to the full parent selector, deep nesting can unintentionally raise specificity. For example, .card { & .title { } } becomes .card .title, which is fine, but .card { & & { } } yields .card.card, doubling the class count. Reviewing the generated CSS (or using a linter) helps catch overly specific rules before they become problematic.
Limitation: you need a build step
Browsers cannot read nested CSS directly, so any project that wants to use this syntax must include a PostCSS build step. If your workflow already processes CSS (e.g., for minification or autoprefixing), adding nesting is usually just another plugin in the chain. For projects that currently ship plain CSS with no tooling, you’ll need to weigh the added complexity against the readability gain.
Actionable closing
If you’re tired of repeating prefixes or long selector chains, try nesting:
With this setup you get the ergonomic benefits of nesting without any runtime cost, and the generated CSS remains fully compatible with every browser.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.