CSS Post Processor vs Sass, LESS, PostCSS and native CSS

Sass, LESS and PostCSS transform CSS before it reaches the browser. CSS Post Processor transforms it in the browser, where it can see the page. The two approaches aren't competing for the same job, and the table below shows where each one fits.

At a glance#

CSS Post Processor Native CSS (2026) Sass / SCSS LESS PostCSS
Runs In the browser In the browser At build time At build time (or browser) At build time
Build step needed No No Yes Usually Yes
Variables Yes, plus JavaScript expressions Custom properties Yes Yes Via plugins
Variables inside url() Yes No Yes (static only) Yes (static only) Via plugins
Nesting Yes Yes Yes Yes Via plugins
Mixins with parameters Yes No Yes Yes Via plugins
Values from the live page (viewport, element sizes) Yes Partly (vw, container units) No No No
Per-element values in any property $[index] sibling-index(), numbers only No No No
Seen / in view selector :seen, :in-view No (Chromium-only triggers) No No No
Sticky "stuck" selector :stuck Chromium only No No No
Overflow / truncation selector :overflowing No No No No
Select by text :contains() No No No No
Numeric attribute comparison [data-x<5] No No No No
Active link selector :current No (:local-link unimplemented) No No No
Scroll position and direction :scrolled, :scrolling-down Chromium only No No No
Image loaded / broken state :loaded, :broken No No No No
Flex-wrap detection :wrapped No No No No
Fit text, equal heights fit(), same No No No No
Pseudo-element clicks, hover, styling from JS css.on(), ::after:hover, css.pseudo() No (Chrome-only element.pseudo(), no events) No No No
Parent selection :has() plus the selectors above :has() :has() :has() :has()
Loops, modules, functions JavaScript expressions Limited Yes Partial Via plugins
Size added to the page 10.4 KB 0 0 0 0

File size: with and without css-pp#

This site is styled with css-pp itself. Its stylesheet uses nesting, variables and mixins, and your browser compiled it a moment ago. Here is how big the same stylesheets are when written with css-pp and as the plain CSS you would have to write without it:

Stylesheet Written with css-pp Same CSS without it Less to write
This website 17.8 KB 19.7 KB 10%
A production theme with heavy nesting and mixins 60.4 KB 77.9 KB 22%

Sizes are minified, with comments removed. You can compare the two files for this site yourself: /assets/site.css is the css-pp source your browser processes, and /assets/site.plain.css is the same stylesheet compiled at build time. Browsers without JavaScript get the plain one.

The difference is in what you maintain. In this site's stylesheet, the border style 1px solid var(--border) is written once and appears 28 times in the output, so changing it is a one-line edit. Selectors are written once per component instead of once per rule, and the output has 227 rules.

What css-pp does not do is save bandwidth. Gzip removes repetition too, so the compressed files end up almost the same size (5.0 KB and 5.0 KB for this site), and the library itself adds 10.4 KB (cached after the first page). Choose it for shorter, easier-to-change stylesheets and for the runtime features plain CSS lacks, not for smaller downloads.

vs native CSS#

Modern CSS has caught up with much of what preprocessors used to offer. Custom properties, nesting, :has(), container queries, clamp(), aspect-ratio and, since 2026, sibling-index() all work in every current browser. If native CSS does what you need, use native CSS.

What it still can't do, and what css-pp adds:

  • Know what the user has seen. There is no selector for "has been scrolled into view". Scroll-triggered animations exist only in Chromium and only drive animations, not arbitrary styles.
  • Know that a sticky element is stuck. scroll-state(stuck) queries are Chromium-only.
  • Know that content overflows. There is no native truncation selector. The CSS Working Group discussion is ongoing.
  • Read text or compare numbers. :contains() was dropped from the CSS3 drafts and never shipped, and attribute selectors only compare text.
  • Build URLs from values. url() doesn't accept variables or functions, so image sequences need one rule per image.

vs Sass and LESS#

Sass and LESS are excellent at what they do: modules, loops, functions and build-time variables, compiled once into static CSS. They run before the page exists, so they can't use anything that depends on it: how many slides there are, how wide the window is, whether a header is stuck, what text a badge contains.

css-pp needs no Node.js toolchain, which matters for sites without a build pipeline: plain HTML, WordPress themes, server-rendered PHP or Perl, or a CMS where you can only edit a stylesheet.

Using both: you can compile Sass to CSS and still open a separate, smaller stylesheet with css-pp for runtime features. Don't run css-pp syntax through the Sass compiler; both use $ for variables.

vs PostCSS#

PostCSS is a build-time toolkit: autoprefixing, future-syntax polyfills, minification and hundreds of plugins. css-pp is not a PostCSS replacement, and you can use both: PostCSS for your main build, css-pp for the runtime features that need the live page.

vs JavaScript libraries#

Many css-pp features replace a small library or a hand-written script:

Instead of Write
Scroll-reveal libraries, or a custom IntersectionObserver that adds classes .card:not(:seen) { opacity: 0 }
A scroll listener that toggles a "stuck" class header:stuck { box-shadow: ... }
JavaScript that compares scrollWidth to clientWidth .title:overflowing { ... }
jQuery :contains() plus class toggling .badge:contains("Sold out") { ... }
A loop that sets a background image on each slide url(/img/slide-$[index].jpg)
A script that adds an active class to the current nav link nav a:current { ... }
Hide-on-scroll header libraries html:scrolling-down header { translate: 0 -100% }
Bootstrap scrollspy .toc a:target-in-view { ... }
FitText, fitty h1 { font-size: fit(24px, 120px) }
matchHeight .card h3 { height: same }
Guessing whether a click landed on an ::after from its coordinates css.on('click', '.chip::after', fn)
onload / onerror handlers on images img:not(:loaded) { opacity: 0 }, img:broken { ... }

The CSS version is shorter, keeps styling in the stylesheet, and updates as the DOM changes.

Verdict#

  • Use native CSS for everything it covers.
  • Use Sass or PostCSS when you already have a build pipeline and need build-time features.
  • Use CSS Post Processor when you need selectors and values that depend on the live page, or you want preprocessor features without a build step.