:seen and :in-view
Animate elements when they scroll into view, once or every time, with an optional visibility threshold. No animation library needed.
State selectors →CSS Post Processor is a 8.8 KB script that extends CSS in the browser. It adds variables with JavaScript, nesting, per-element values like $[index], and the selectors native CSS still can't express: whether an element has been seen, is stuck, is cut off, or contains some text.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/css-pp.min.js"></script>/* one image per slide, any number of slides */
.slider > div {
background-image: url(/img/slide-$[index].jpg);
}
/* fade in the first time it scrolls into view */
.card:not(:seen) { opacity: 0; translate: 0 2rem; }
/* shadow only while the sticky header is stuck */
header:stuck { box-shadow: 0 2px 12px #0003; }
/* "Read more" only when the text is cut off */
.post:has(.excerpt:overflowing) .more {
display: inline;
}
/* style by text and by numbers */
.item:has(.badge:contains("sold out" i)) {
opacity: .5;
}
.item:has([data-stock<5]) .low { display: block; }What it adds
Every feature below replaces JavaScript you would otherwise write and maintain: an IntersectionObserver here, a scroll listener there, a loop that sets inline styles. With CSS Post Processor you write a selector and the browser does the rest.
Animate elements when they scroll into view, once or every time, with an optional visibility threshold. No animation library needed.
State selectors →Style a position: sticky header or sidebar only while it is pinned. Works at the top or bottom, in the page or inside a scrolling box.
Know when text is truncated or a box overflows. Show "read more" or a tooltip only when the content is actually cut off.
Overflow detection →Select elements by their text, with an optional case-insensitive flag. It stays live as the text changes and works inside :has().
Compare attribute values as numbers with <, <=, > and >=. Highlight prices, low stock, ratings and scores without a line of JavaScript.
A per-element counter you can use anywhere, even inside url(). Image sequences, staggered animations and color ramps take one rule.
A variable can be a value, a mixin with parameters, or a JavaScript expression, such as the viewport width or an element's height. Change it at runtime with css.set().
Add ! to a variable name and it is recalculated on resize. Pick the right image size for the screen in a single expression.
Nest rules with &:hover, .dark & or &-title, and put @media inside a rule. The output is flat CSS that works in every browser.
Highlight the active nav link, shrink the header after scrolling or hide it on the way down. Scroll shadows and scrollspy menus too.
Scroll & navigation →Fade images in once they load, hide broken ones, switch to a hamburger the moment the nav wraps, float labels on filled fields.
Media, layout & forms →Make a headline fill its box and give cards equal heights across rows. Both recalculate on resize and when fonts load.
Layout helpers →Works with :has()
Native :has() lets you style a parent based on its children. CSS Post Processor's selectors compile to plain attribute selectors, so they work inside :has(). You can style a header when the hero leaves the screen, or a card when its text is cut off.
:has(> .child), native CSS:has(> .x:seen):has(~ .child), native CSS.card:has([data-price>100])/* Solid header once the hero has scrolled away */
body:has(.hero:not(:in-view)) header {
background: #fff;
box-shadow: 0 1px 8px rgb(0 0 0 / .08);
}
/* Highlight the list item that holds the active link */
nav li:has(> a.active) { border-left: 3px solid; }
/* Every item before the selected one */
.steps li:has(~ li.current) { color: green; }How it works
CSS Post Processor reads your stylesheet in the browser and writes plain CSS into a <style> element. Its selectors become attribute selectors such as [data-css-seen], which small observers switch on and off. The browser's own engine still does the matching.
Self-host the 8.8 KB file or load it from a CDN. Nothing to install, and no bundler or config.
css.open('/css/site.css') fetches it, or css.open('#id') processes an inline <style>.
A 74 KB stylesheet with variables and nesting compiles in about 9 ms on a desktop. Rules update live as the DOM changes.
8.8 KB
gzipped (24.5 KB minified)0
dependencies, no build step~9 ms
to process a 74 KB stylesheet0
downloads from this siteHonest fit
'unsafe-eval' and you need JavaScript expressions in variables (nesting and the new selectors still work):has(), container queriesQuick start
Add the script, point it at your stylesheet and use the new syntax. Any CSS you already have keeps working unchanged.
<script src="/js/css-pp.min.js"></script>
<script>
css.defaults.debug = 0; // quiet errors in production
css.open('/css/site.css'); // fetch, process, inject
</script>Free and open source under the MIT License. Self-host it, load it from a CDN or install it from npm.