CSS that knows your page. CSS Post Processor (css-pp) is a 10.4 KB, dependency-free script that extends CSS in the browser. It adds variables with JavaScript, nesting, per-element values that work even inside url(), and the selectors native CSS still lacks: :seen, :in-view, :stuck, :overflowing, :contains() and numeric attribute comparisons.
No build step. Ordinary CSS passes through unchanged.
Website and full documentation: csspp.dev
/* a different image for every slide, any number of slides */
.slider > div { background-image: url(/img/slide-$[index].jpg); }
/* fade cards in the first time they scroll into view */
.card:not(:seen) { opacity: 0; translate: 0 2rem; }
/* shadow only while the sticky header is actually stuck */
header:stuck { box-shadow: 0 2px 12px rgb(0 0 0 / .2); }
/* "Read more" only when the excerpt is really truncated */
.post:has(.excerpt:overflowing) .more { display: inline; }
/* active nav link, header that hides on the way down */
nav a:current { font-weight: 700; }
html:scrolling-down header { translate: 0 -100%; }
/* style by text and by numbers */
.product:has(.badge:contains("sold out" i)) { opacity: .5; }
.product:has([data-stock<5]) .low-stock { display: block; }<script src="https://cdn.jsdelivr.net/npm/css-post-processor@3.2.1/dist/css-pp.min.js"
integrity="sha384-T1bI8VjOVeN7L8QkEg/v3zEkxyzVreA9qchbx+0gd9tOItYJlbVFgRiodhzslST/" crossorigin="anonymous"></script>
<script>
css.defaults.debug = 0;
css.open('/css/site.css'); // fetch, process and inject your stylesheet
</script>or npm install css-post-processor and include dist/css-pp.min.js. Process inline styles with css.open('#id').
| Feature | Syntax | Docs |
|---|---|---|
| Seen / in view | :seen, :seen(30%), :in-view, :in-view(50%) |
State selectors |
| Sticky element is pinned | :stuck |
State selectors |
| Content is truncated | :overflowing |
State selectors |
| Select by text | :contains("text"), :contains("text" i) |
Content selectors |
| Compare numbers | [data-x<5], [data-x>=100] |
Content selectors |
| Active link, scroll state | :current, :scrolled(80px), :scrolling-down, :scroll-end, :target-in-view |
Scroll & navigation |
| Images, layout, forms | img:loaded, img:broken, :wrapped, :filled, :dirty, :offline, :idle(30s) |
Media, layout & forms |
| Per-element values | $[index], $[random(1,5)], $[attr(data-bg)], $[count], $[year], even inside url() |
Values |
| Pseudo-elements from JS | css.on('click', '.chip::after', fn), css.pseudo(el, '::after').set('color', 'red'), .chip::after:hover |
Pseudo-elements |
| Fit text, equal heights | font-size: fit(24px, 120px), height: same |
Layout helpers |
| Variables, mixins, JavaScript | $brand{#0ea5e9}, $m(1px){border:&a solid}, $half{css.vars.w()/2} |
Variables |
| Responsive variables | $vw!{css.vars.w()}, recalculated on resize |
Variables |
| Nesting | &:hover, .dark &, &-title, @media inside rules |
Nesting |
Every selector compiles to an attribute selector with the same specificity, updates live as the DOM changes, and works inside native :has().
29.5 KB minified, 10.4 KB gzipped. The core is ES5 and runs in every browser. The state and content selectors use IntersectionObserver, MutationObserver and ResizeObserver, which are available in all evergreen browsers. A 74 KB stylesheet with variables and nesting compiles in about 9 ms on a desktop.
See how it works, performance and limitations, including Content Security Policy notes.
MIT © 2016–2026 Alex Bobrov