ZurichJS 2026 | Kevin Powell
.entire-page-scroll-tracking { animation: animation-name both; animation-timeline: scroll();}
.viewport-scroll-tracking { animation: animation-name both; animation-timeline: view();}Scroll-driven animations
Limited availability
.anchor { anchor-name: --foo;}
.item { position: absolute; position-anchor: --foo; position-area: bottom; position-try-fallbacks: flip-block;}Anchor positioning
Limited availability



calc()min()max()sin()cos()tan()rem()random()
Fetches data from API
Processes data
Makes styling decisions


const data = { forecast: [ { day: "Wednesday", date: "May 27", high: 14, night: 7, chance: 70, precipitation: 8.2, condition: "rain", }, { day: "Thursday", date: "May 28", high: 12, night: 6, chance: 40, precipitation: 3.8, condition: "part", }, { day: "Friday", date: "May 29", high: 17, night: 9, chance: 10, precipitation: 0, condition: "sun", }, ],};const MAX_PRECIPITATION = 10;
const precipitationBar = Math.min( (precipitation / MAX_PRECIPITATION) * 100, 100,);<div className="precipitation-info"> <span className="precipitation-label"> {precipitation}mm </span> <div className="precipitation-visual" style={{ height: `${precipitationBar}%` }} /></div><div className="precipitation-info"> <span className="precipitation-label"> {precipitation}mm </span> <div className="precipitation-visual" style={{ "--precipitation": precipitation }} /></div>const MAX_PRECIPITATION = 10;
const precipitationBar = Math.min( (precipitation / MAX_PRECIPITATION) * 100, 100,);.precipitation-visual {}const precipitationBar = Math.min( (precipitation / MAX_PRECIPITATION) * 100, 100,);.precipitation-visual { --max-precipitation: 10;}.precipitation-visual { --max-precipitation: 10;
--precipitation-percentage: min( var(--precipitation) / var(--max-precipitation) * 100%, 100% );}.precipitation-visual { --max-precipitation: 10;
--precipitation-percentage: min( var(--precipitation) / var(--max-precipitation) * 100%, 100% );}.precipitation-visual { --max-precipitation: 10;
--precipitation-percentage: min( var(--precipitation) / var(--max-precipitation) * 100%, 100% );
block-size: var(--precipitation-percentage);}.precipitation-visual { --max-precipitation: 10;
--precipitation-percentage: min( var(--precipitation) / var(--max-precipitation) * 100%, 100% );
block-size: var(--precipitation-percentage);}attr()attr() with any property!<div className="precipitation-info"> <span className="precipitation-label"> {precipitation}mm </span> <div className="precipitation-visual" style={{ "--precipitation": precipitation }} /></div><div className="precipitation-info"> <span className="precipitation-label"> {precipitation}mm </span> <div className="precipitation-visual" data-precip={`$precipitation}`} /></div>.precipitation-visual { --max-precipitation: 10;
--precipitation-percentage: min( var(--precipitation) / var(--max-precipitation) * 100%, 100% );
block-size: var(--precipitation-percentage);}.precipitation-visual { --precipitation: attr(data-precip type(<number>)); --max-precipitation: 10;
--precipitation-percentage: min( var(--precipitation) / var(--max-precipitation) * 100%, 100% );
block-size: var(--precipitation-percentage);}.precipitation-visual { --precipitation: attr(data-precip type(<number>)); --max-precipitation: 10;
--precipitation-percentage: min( var(--precipitation) / var(--max-precipitation) * 100%, 100% );
block-size: var(--precipitation-percentage);}if() & @container style() range syntax.Currently in Chromium browsers.

const BADGE_STYLES = { rollback: { background: rgb(222, 28, 36), color: rgb(255, 255, 255) }, clearance: { background: rgb(255, 152, 0), color: rgb(255, 255, 255) }, reduced: { background: rgb(0, 115, 209), color: rgb(255, 255, 255) },}
function Badge({ type, label }) { const styles = BADGE_STYLES[type]
return ( <span style={{ background: styles.background, color: styles.color, borderRadius: styles.borderRadius, fontWeight: styles.fontWeight, }} > {label} </span> )}function Badge({ type, label }) { return ( <span className="badge" data-promo={type}>{label}</span> )}
function Badge({ type, label }) { return ( <span className="badge" data-promo={type}>{label}</span> )}function ProductCard({ name, price, promo, stock, rating }) { return ( <div className="product" data-promo={promo} data-price={price} data-stock={stock} data-rating={rating} > {promo && <Badge type={promo} />} <p>{name}</p> <p>{price}</p> </div> )}const sorts = { rating: (a, b) => num(b, "rating") - num(a, "rating"), "price-low-high": (a, b) => num(a, "price") - num(b, "price"), "price-high-low": (a, b) => num(b, "price") - num(a, "price"), availability: (a, b) => stock(a) - stock(b),};.product { order: if( style(--sort: rating): calc(var(--rating) * -10); style(--sort: price-low-high): calc(var(--price) * 10); style(--sort: price-high-low): calc(var(--price) * -10); style(--sort: availability): if(style(--stock > 0): var(--stock); else: 9999); else: 0 );}const MAX_PRECIPITATION = 10;
const precipitationBar = Math.min(
(precipitation / MAX_PRECIPITATION) * 100,
100,
);.precipitation-visual {
--max-precipitation: 10;
--precipitation-percentage: min(
var(--precipitation) / var(--max-precipitation) * 100%,
100%
);
}Fetch data
Pass on values
(custom props now, attributes in the future)
attr()if()@container style()calc()round()min()max()clamp():hover should something that’s controlled by JavaScript.Slides: kevinpowell.co/talks/zurichjs
Start writing CSS with confidence: cssdemystified.com ZURICHJS for 20% off