mrkeyoor.com_
Sun 20 Sept 12:41 UTC
npmWeb Frontendupdated 20 Sept 2026

postcss review

PostCSS 8.5.26 parses CSS into a source-aware node tree, runs JavaScript visitors over it, and serializes the result. The core package does not add prefixes, lower future syntax, nest selectors, lint, or minify; separate plugins perform each of those jobs. Version 8.5.26 repairs a list.split regression and tracks symlinks while protecting source-map paths. Our full import produced 56.5 KB minified and 18.2 KB gzipped. That cost is normal in build tooling but harder to justify inside an end-user browser feature.

207.7Mdownloads / wk
Verdict

PostCSS 8.5.26 installed in 0.4 seconds as 7 packages and bundled to 18.2 KB gzipped in our sandbox, with 0 audit findings. Add it for a named plugin chain or AST task; if the framework already owns CSS or a compiled transformer covers the whole job, a second PostCSS setup is unnecessary.

We installed it

Lab card: what happened when we installed postcssScreenshot of postcss documentation
Install✓ · 0.4s7 packages on disk · 1 MB
ImportESM import works · require() works · CommonJS package with exports map
Browser18.2 KBgzipped (56.5 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does postcss install cleanly?

Yes. In a fresh container with an empty cache, npm install postcss finished in 0.4s, leaving 7 packages and 1 MB on disk. npm audit reported no known vulnerabilities.

How much does postcss add to a browser bundle?

18.2 KB gzipped (56.5 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does postcss work with both ESM and CommonJS?

Yes. Both import 'postcss' and require('postcss') worked in Node 22 in our run. The package is published as CommonJS with an exports map.

Does postcss include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

postcss or lightningcss: which should you use?

lightningcss: Choose it for compiled prefixing, syntax transforms, and minification without a JavaScript visitor chain. PostCSS 8.5.26 installed in 0.4 seconds as 7 packages and bundled to 18.2 KB gzipped in our sandbox, with 0 audit findings.

When should you not use postcss?

The framework already emits the desired CSS and offers no custom transform requirement; a direct PostCSS dependency would add unused configuration.

API stability4/5PostCSS 8.5.26 retains the version 8 plugin object, postcssPlugin identifier, node visitors, LazyResult, and established node classes. Its current patch fixes regressions in list.split, while 8.5.25 fixed another visitor regression, showing that patch releases can affect plugin behavior. Ordinary configs remain stable, but plugin authors should test traversal and serialization on every upgrade because they depend on lower-level mutation semantics.
Docs4/5postcss.org documents processors, nodes, containers, parsing, source maps, warnings, and syntax errors, and the repository includes a version 8 plugin-author guide with visitor examples. The README is clear that the 200+ plugin catalog supplies features absent from core. Configuration loading and compatibility belong to bundler adapters and individual plugins, so the official material cannot serve as one complete production setup guide.
Maintenance5/5The unarchived repository was pushed on August 26, 2026, and GitHub showed 24 open issues and pull requests. Version 8.5.26 arrived on August 6 with a list.split regression fix and stricter symlink-aware source-map path handling. Version 8.5.25 had already repaired a visitor regression, indicating active correction of core traversal and utility behavior within the current major line.
Ecosystem5/5npm recorded 279,293,478 downloads from August 19 through 25, 2026, while GitHub reported 28,983 stars. The project lists more than 200 plugins, with Autoprefixer and Stylelint among prominent consumers, and common bundlers understand its config convention. That range is PostCSS's main advantage and its main maintenance cost because adapters and plugins release independently of the core package.

Discussed on

  1. hnRails 6 with Webpacker 6, Tailwind 2 with JIT, Postcss 8 and some default setup169 points
  2. hnA PostCSS plugin for people who prefer to write English properly84 points
  3. hnHow PostCSS became 1.5x faster by changing 2 lines of code55 points
  4. hnCSSNano: A modular minifier based on the PostCSS ecosystem31 points
  5. hnShow HN: Rucksack – CSS superpowers, built on PostCSS27 points

Use it if

  • You are writing a CSS codemod, linter, or migration that needs nodes with file, line, and formatting information.
  • A build requires an ordered set of specific PostCSS plugins such as Autoprefixer or postcss-preset-env.
  • The current framework already loads a PostCSS config and one additional transform fits that established pipeline.
  • A tool must parse SCSS-like, Less-like, HTML-embedded, or CSS-in-JS syntax through a compatible parser without evaluating the source language.
Skip it if

Setup reality

We installed PostCSS 8.5.26 in a clean Node 22 Bookworm sandbox. npm completed in 0.4 seconds and left 7 packages using 1 MB. PostCSS itself was 352 KB unpacked with 3 direct dependencies and no peers. npm audit reported 0 known vulnerabilities. The package is CommonJS behind an exports map, bundles TypeScript declarations, and worked through both require() and ESM import. Its engine range is Node ^10, ^12, or >=14.

Core discovers no plugins and reads no config by itself. Pass plugins to postcss([...]) or use a bundler adapter that invokes postcss-load-config. Config extensions and export syntax must match that adapter and the project's module mode. Plugin sequence affects output, particularly when syntax lowering runs before prefix generation or minification. No credentials are involved unless an individual plugin introduces them.

Pass from for real source so warnings, error frames, URL resolution, and maps name the correct file; add to for a known destination. The object returned by process() is lazy. Await it even when today's plugins are synchronous, because a later async plugin makes direct lazy.css access throw. A successful result can still contain warnings, so builds need an explicit result.warnings() policy.

PostCSS 8 schedules changed nodes for another visit. A visitor that inserts a matching node or repeatedly mutates the same declaration can loop until memory is exhausted; make transforms idempotent or mark handled nodes. Custom syntax packages may parse foreign notation without compiling its semantics. Our esbuild namespace import measured 56.5 KB minified and 18.2 KB gzipped, and many plugins also assume filesystem or Node resolution, so browser execution needs individual compatibility checks.

Patterns

Process CSS with one plugin run-autoprefixer

import postcss from 'postcss';
import autoprefixer from 'autoprefixer';

const result = await postcss([autoprefixer]).process(css, {
  from: 'src/main.css',
  to: 'dist/main.css',
});

from supplies file identity to warnings and maps. Add to when output has a real destination.

Write a declaration visitor create-declaration-plugin

const pxToRem = () => ({
  postcssPlugin: 'px-to-rem',
  Declaration(decl) {
    if (decl.value.includes('px')) {
      decl.value = decl.value.replace(/(\d+)px/g, (_, n) => `${n / 16}rem`);
    }
  },
});
pxToRem.postcss = true;

A PostCSS 8 plugin creator returns an object named with postcssPlugin. Make the replacement idempotent because changed nodes can be visited again.

Rewrite matching nodes walk-selected-nodes

const root = postcss.parse(css);
root.walkDecls('color', decl => { decl.value = 'var(--brand)'; });
root.walkRules(/^\.button/, rule => { rule.selector += ':not([disabled])'; });

Filtered walkers reduce calls. A mutation that still matches must settle on the next visit.

Read parsed fields inspect-node-data

const root = postcss.parse('a { color: black }', { from: 'input.css' });
const rule = root.first;
console.log(rule.type, rule.selector);
console.log(rule.first.prop, rule.first.value);

Semantic values use named fields, while spacing and punctuation details live under raws.

Build a media rule construct-at-rule

const rule = postcss.rule({ selector: '.card' });
rule.append(postcss.decl({ prop: 'padding', value: '1rem' }));
const media = postcss.atRule({ name: 'media', params: '(min-width: 600px)' });
media.append(rule);
root.append(media);

New nodes have no neighboring formatting context. Clone an existing node when a codemod should minimize whitespace changes.

Configure a shared pipeline declare-plugin-order

// postcss.config.js
export default {
  plugins: {
    'postcss-preset-env': { stage: 2 },
    autoprefixer: {},
  },
};

A build adapter loads this through postcss-load-config. Confirm its ESM, CommonJS, and filename rules.

Write an external source map emit-source-map

const result = await postcss(plugins).process(css, {
  from: 'src/main.css', to: 'dist/main.css', map: { inline: false },
});
await writeFile('dist/main.css', result.css);
await writeFile('dist/main.css.map', result.map.toString());

When inline is false, application code must write the map beside the CSS and preserve its sourceMappingURL relationship.

Handle asynchronous plugins await-lazy-result

const lazy = postcss([plugin]).process(css, { from: 'input.css' });
const result = await lazy;
console.log(result.css);

Direct lazy.css access throws when any plugin is asynchronous. Awaiting works for both synchronous and async pipelines.

Print a syntax error frame format-css-error

try {
  postcss.parse(broken, { from: 'theme.css' });
} catch (error) {
  if (error instanceof postcss.CssSyntaxError) console.error(error.showSourceCode(true));
  else throw error;
}

CssSyntaxError also exposes file, line, and column for machine-readable build output.

Mark inserted fallbacks prevent-revisit-loop

const fallback = () => ({
  postcssPlugin: 'inset-fallback',
  Declaration(decl) {
    if (decl.prop !== 'inset' || decl.raws.handled) return;
    decl.cloneBefore({ prop: 'top', value: decl.value, raws: { handled: true } });
    decl.raws.handled = true;
  },
});

PostCSS revisits changed nodes, so a visitor that keeps inserting matches can run indefinitely.

Promote warnings to build failures fail-on-warnings

const result = await postcss(plugins).process(css, { from: input });
for (const warning of result.warnings()) console.error(warning.toString());
if (result.warnings().length > 0) process.exitCode = 1;

A transform can finish successfully and still carry plugin warnings. The build wrapper decides whether they are fatal.

Insert a compatible color clone-fallback-declaration

root.walkDecls('color', decl => {
  if (decl.value === 'oklch(60% 0.2 30)') decl.cloneBefore({ value: '#c44' });
});

cloneBefore retains source and formatting metadata. Matching only the original value keeps the new fallback out of the visitor.

Alternatives

PackageRegistryPick it when
lightningcssnpmChoose it for compiled prefixing, syntax transforms, and minification without a JavaScript visitor chain.
sassnpmChoose it when Sass evaluation, modules, mixins, and functions define the source language.
lessnpmChoose it for an existing Less codebase that depends on its variables, imports, and mixins.

More web frontend guides

react · react-dom · tailwindcss · htmlparser2 · tailwind-merge · class-variance-authority · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.