CSS is eating
JavaScript

ZurichJS 2026 | Kevin Powell

It’s easy to get stuck in our ways.

  • Simpler solutions
  • Easier to maintain
  • More performant
  • Simpler solutions
  • Easier to maintain
  • More performant
  • Simpler solutions
  • Easier to maintain
  • More performant
.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

  • Chrome115
  • Edge115
  • Firefox—
  • Safari26
.anchor {
anchor-name: --foo;
}
.item {
position: absolute;
position-anchor: --foo;
position-area: bottom;
position-try-fallbacks: flip-block;
}

Anchor positioning

Limited availability

  • Chrome—
  • Edge—
  • Firefox—
  • Safari—

These are amazing!

I believe some new features will cause an architectural shift.

Some of you might disagree with me...

no calc
no calc

calc()min()max()sin()cos()tan()rem()random()

no calc

Separation of concerns!

Client-side rendering

JavaScript

Fetches data from API

Processes data

Makes styling decisions

CSS

🥱
weather two
weather two
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",
},
],
};

We can make the flow of data more clear.

attr()

  • Can’t search for, or select
  • Internationalization

We can now use attr() with any property!

In Chromium (since 133), FireFox (since 155), and Safari TP
<div className="precipitation-info">
<span className="precipitation-label">
{precipitation}mm
</span>
<div
className="precipitation-visual"
style={{ "--precipitation": precipitation }}
/>
</div>

We can make decisions based on that data if() & @container style() range syntax.

Currently in Chromium browsers.

walmart server side injected code for the styling of a badge
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>
)
}
The custom props that walmart has, with over 1000 availalble, including the red value they are using
function Badge({ type, label }) {
return (
<span className="badge" data-promo={type}>{label}</span>
)
}
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),
};
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%
  );
}

JavaScript

Fetch data

Pass on values
(custom props now, attributes in the future)

CSS

Get & type values
attr()
Logic & styling
if()@container style()
calc()round()min()max()clamp()

They used to say :hover should something that’s controlled by JavaScript.

Thank you!

Slides: kevinpowell.co/talks/zurichjs

Start writing CSS with confidence: cssdemystified.com ZURICHJS for 20% off