Frequently asked questions
About the library#
What is CSS Post Processor?#
CSS Post Processor (css-pp) is a free, open-source JavaScript library that extends CSS in the browser. It adds variables with JavaScript expressions, mixins, nesting, per-element values through $[index], and selectors native CSS lacks: :seen, :in-view, :stuck, :overflowing, :contains() and numeric attribute comparisons such as [data-stock<5]. It is 10.4 KB gzipped, has no dependencies and needs no build step.
Is it a preprocessor or a postprocessor?#
Both words fit. It's called a post processor because it processes CSS after it reaches the browser, rather than in a build step like Sass or LESS. That timing is what lets it use the live page: element counts, sizes, visibility, sticky state and text.
Do I need Node.js, npm or a build step?#
No. Add one script tag and call css.open('/css/site.css'). It is also published on npm, as npm install css-pp, for projects that prefer packages.
Does it work with WordPress, Shopify, PHP or any CMS?#
Yes. It is plain JavaScript that runs on any page, whatever generates the HTML. Load the script in your theme and open your stylesheet.
How big is it?#
29.5 KB minified and 10.4 KB gzipped for version 3.2.1.
Which browsers are supported?#
All current browsers. The core features are ES5. The state and content selectors use IntersectionObserver, MutationObserver and ResizeObserver, which every evergreen browser has supported since 2020. Without IntersectionObserver, :seen and :in-view act as if the element is visible, so content is never hidden.
Is it free for commercial use?#
Yes. It's released under the MIT License, so you can use it in commercial and closed-source projects. Keep the copyright notice in the file.
Performance and SEO#
Does processing CSS in the browser slow the page down?#
Very little. In Chrome on a desktop, a 74 KB production stylesheet with variables and nesting compiles in about 9 ms, and a 107 KB plain stylesheet in about 6 ms. Set css.defaults.localCache = 1 so returning visitors render from a cached copy.
Will it hurt SEO?#
No. Your HTML and text aren't changed; only CSS is generated. Google renders JavaScript as part of indexing. Write hidden-until-seen styles as .x:not(:seen) { opacity: 0 }. Crawlers and browsers that don't run the script then skip the rule, so the content stays visible to them.
What about a flash of unstyled content?#
By default (css.defaults.invisible = 1), the page stays hidden until your stylesheets are processed, so nobody sees unstyled markup.
Does it work with a Content Security Policy?#
Partly. Variable expressions use eval(). Without 'unsafe-eval', plain values, mixins, nesting and the state and content selectors still work, but JavaScript expressions and $[index] don't.
How do I…#
How do I select a parent element in CSS?#
Use the native :has() pseudo-class: .card:has(img) selects cards that contain an image. :has(> .child) selects the direct parent of .child whatever element it is. Every current browser has supported it since December 2023. css-pp adds parent selection based on visibility, text, numbers and overflow, for example .post:has(.excerpt:overflowing). See Parent and sibling selectors.
How do I select previous siblings in CSS?#
Put the sibling combinator inside :has(): li:has(~ .current) selects every item before .current, and li:has(+ .current) selects only the one immediately before it.
How do I style a sticky header only when it is stuck?#
With css-pp: header:stuck { box-shadow: 0 2px 12px rgb(0 0 0 / .15) }. It works in every browser, at the top or bottom, in the page or in a scrolling container. Native CSS only has Chromium's scroll-state(stuck) container queries.
How do I select an element by its text content?#
With css-pp: .badge:contains("Sold out"), or :contains("sold out" i) for a case-insensitive match. Native CSS has no text selector.
How do I compare a number in an attribute selector?#
With css-pp: [data-stock<5], [data-price>=100], [data-score>4.5]. Native attribute selectors only compare text.
How do I detect truncated text with CSS?#
With css-pp: .title:overflowing matches when the content is wider or taller than the box, for example when text-overflow: ellipsis is cutting it off. Combine it with :has() to show a "Read more" link only when needed.
How do I reveal elements on scroll without JavaScript?#
Load css-pp and write .card:not(:seen) { opacity: 0; translate: 0 2rem } with a transition on .card. Each card fades in the first time it enters the viewport. Use :in-view to animate every time it enters.
How do I give every slide a different background image in one CSS rule?#
.slider > div { background-image: url(/img/slide-$[index].jpg) }. css-pp replaces $[index] with each element's position: 0, 1, 2 and so on. Native CSS can't put a computed value inside url().
How do I highlight the active navigation link with CSS?#
With css-pp: nav a:current { font-weight: 700 }. It matches links to the current page and to its parent sections, so "Docs" stays active on every docs page; :current(exact) matches the page alone. Native CSS specifies :local-link for this, but no browser supports it.
How do I shrink or hide a header when the page scrolls?#
With css-pp: html:scrolled(80px) header { height: 56px } shrinks it after 80px of scrolling, and html:scrolling-down header { translate: 0 -100% } hides it while scrolling down and brings it back on the way up.
How do I detect when flex items wrap to a new line?#
With css-pp: li:wrapped matches items that wrapped below the first row, so nav:has(li:wrapped) .menu-button { display: block } shows a hamburger menu exactly when the links stop fitting.
How do I fade images in when they finish loading?#
With css-pp: img { transition: opacity .4s } img:not(:loaded) { opacity: 0 }. img:broken matches images that failed to load, for fallbacks.
How do I make text fill its container?#
With css-pp: h1 { font-size: fit(24px, 120px) } sizes the text to fill the element's width on one line, between the two limits.
How do I make cards the same height?#
With css-pp: .card h3 { height: same } gives every matched element the height of the tallest one, across rows and containers. If the cards share one CSS grid, native subgrid works too.
How do I detect a click on a pseudo-element like ::after?#
Browsers report a click on ::after as a click on the element, so plain JavaScript can't tell them apart. With css-pp: css.on('click', '.chip::after', function (e) { ... }) fires only when the pointer is on the ::after, in every browser. It uses the browser's own hit-testing, so it is exact even for rotated or inline pseudo-elements.
How do I change a pseudo-element's style with JavaScript?#
With css-pp: css.pseudo(el, '::after').set('color', 'red') styles that one element's ::after, and css.pseudo(el, '::after').text = 'New' changes its text. Natively you can only read pseudo-element styles with getComputedStyle(el, '::after').
Can I use :hover on ::before or ::after?#
Not in native CSS: .x::after:hover is invalid. With css-pp it works: .chip::after:hover { color: red } applies only while the pointer is on the pseudo-element, and ::after:active while it is pressed.
Help#
Where do I report a bug or ask a question?#
Use the contact page. Include the browser, a short stylesheet that shows the problem, and what you expected to happen.
Who maintains it?#
Alex Bobrov, who created it and has maintained it since 2016. See About.