Getting started
CSS Post Processor (css-pp) is a small JavaScript library that runs in the browser and extends CSS. You add one script, point it at a stylesheet, and that stylesheet can use variables, mixins, nesting, per-element values and selectors that native CSS does not have.
There is no build step, no Node.js and no configuration file. Ordinary CSS passes through unchanged, so you can adopt it one rule at a time.
Install#
Pick whichever fits your project. All three serve the same file.
<script src="/js/css-pp.min.js"></script><script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/css-pp.min.js"
integrity="sha384-T1bI8VjOVeN7L8QkEg/v3zEkxyzVreA9qchbx+0gd9tOItYJlbVFgRiodhzslST/"
crossorigin="anonymous"></script>npm install css-ppThe minified build is 29.5 KB (10.4 KB gzipped). You can download it from the download page.
Open a stylesheet#
Call css.open() with the path to your stylesheet. The library fetches the file, processes it and injects the result into a <style> element.
<script src="/js/css-pp.min.js"></script>
<script>
css.defaults.debug = 0; // don't throw on mistakes in production
css.open('/css/site.css');
</script>The stylesheet is fetched with XMLHttpRequest, so it must come from the same origin as the page, or from a server that allows cross-origin requests.
The injected <style> element gets an id taken from the file name without its extension. /css/site.css becomes <style id="site">. You use that name later to change variables at runtime with css.set().
Inline styles#
To process CSS that is already in the page, give the element an id and pass it with a leading #:
<style id="main">
$brand{#0ea5e9}
.button { background: $brand; }
</style>
<script>css.open('#main');</script>The browser applies the raw <style> first and skips anything it doesn't understand. css-pp then replaces the contents with the processed CSS.
Your first stylesheet#
/* variables */
$brand{#0ea5e9}
$radius{12px}
/* nesting */
.card {
border-radius: $radius;
&:hover { outline: 2px solid $brand; }
h2 { color: $brand; }
}
/* a different image for every slide */
.slider > div { background-image: url(/img/slide-$[index].jpg); }
/* fade cards in the first time they are seen */
.card:not(:seen) { opacity: 0; translate: 0 1.5rem; }
.card { transition: opacity .6s, translate .6s; }The first block compiles to ordinary CSS: .card{border-radius:12px}, .card:hover{outline:2px solid #0ea5e9} and so on. The $[index] rule becomes one rule per matched slide. :seen becomes an attribute selector that an IntersectionObserver switches on.
Options#
Set options on css.defaults before the first css.open() call.
| Option | Default | What it does |
|---|---|---|
debug |
1 |
Throws an error for undefined variables and similar mistakes, and exposes window.css_vars. Set it to 0 in production. |
invisible |
1 |
Hides the page (visibility: hidden on <html>) until every opened stylesheet is processed, so visitors never see unstyled content. |
localCache |
0 |
Caches the stylesheet source in localStorage. The next visit renders from the cache immediately and refreshes it in the background. |
resizing |
100 |
Milliseconds to throttle the recalculation of auto-updating variables while the window is resized. |
Know when styles are ready#
css-pp fires a css_ready event on document for each stylesheet once it is processed and the DOM is ready. The event's id property is the stylesheet name.
document.addEventListener('css_ready', function (e) {
console.log('styles ready:', e.id);
});Browser support#
The core (variables, mixins, nesting, $[index]) is plain ES5 and runs in every browser in use today. The state and content selectors rely on IntersectionObserver, MutationObserver and ResizeObserver, which every evergreen browser has supported since 2020.
If IntersectionObserver is missing, :seen and :in-view behave as if the element is visible, so hidden-until-seen content is never lost.
Next steps#
- Variables, mixins and JavaScript: values, parameters, expressions and responsive variables
- Nesting:
&,&:hover,.dark &and@mediainside rules $[index]: per-element values, even insideurl()- State selectors:
:seen,:in-view,:stuckand:overflowing - Scroll and navigation:
:current,:scrolled,:scrolling-down,:scroll-endand scrollspy - Media, layout and forms:
:loaded,:broken,:wrapped,:filled,:dirty,:offlineand:idle - Pseudo-elements: click, hover, style and measure
::beforeand::after - Content selectors:
:contains()and numeric attribute comparisons - Recipes: copy-paste solutions for common UI problems