The parent selector exists now :has()
For two decades CSS could only style down the tree. :has()
styles up and sideways: this card watches its own form fields and reacts. Type a bad email
and the whole card knows.
.signup:has(input:user-invalid) { border-color: red; }
.mood:has(#m-hyped:checked) .mood-box { background: conic-gradient(โฆ); }
Animations driven by scrolling scroll-driven animations
The gradient bar pinned to the top of this page is
animation-timeline: scroll(root) โ its keyframes play as you scroll, not as time
passes. The cards below use view(): each one animates as it enters the
viewport. This used to be an IntersectionObserver plus a scroll listener plus jank.
.card { animation: rise 0.7s ease-out both;
animation-timeline: view();
animation-range: entry 0% entry 80%; }
Animating the un-animatable @property
Gradients never animated โ the browser saw them as strings. Register the angle as a typed custom property and suddenly it interpolates. This is one element and one background.
@property --spin { syntax: '<angle>'; inherits: false; initial-value: 0deg; }
.plasma { background: conic-gradient(from var(--spin), โฆ);
animation: rotateGradient 6s linear infinite; }
Components that respond to their container container queries
Media queries ask "how wide is the screen?" Container queries ask "how much room do
I have?" โ which is what components always wanted to know.
Drag the resize handle (bottom-right corner of the box) and watch the card
restyle itself. Even the heading size is in container units (cqi).
Fiona Fox
Wide layout: avatar left, text right, this bio visible. Narrow: stacked, centered, bio hidden. The component decided โ not the page.
Color got good color-mix() & oklch
Nine swatches, one rule each: color-mix() blending our orange into our
blue through the perceptual oklch color space โ no muddy gray midpoint, no design-tool
export, no Sass.
background: color-mix(in oklch, #E44D26 50%, #2965f1);
Typography niceties
Small properties, big polish.
Gradient text is two lines of CSS.
This headline wraps with an awkward orphan word
text-wrap: initialThis headline wraps with an awkward orphan word
text-wrap: balanceDrop caps were a print-design flex that took absurd float hacks on the web.
Now it's initial-letter: 3 on a ::first-letter โ one declaration,
correctly aligned to your baseline grid, in every modern browser.
Shapes, charts, and frosted glass
A morphing clip-path (hover the pentagon), a donut chart that is one
conic-gradient and one mask, and a frosted pane of
backdrop-filter. Zero SVG, zero images, zero canvas.
Carousels without a carousel library
Swipe or scroll the row โ it settles on each card by itself.
scroll-snap is the physics engine.
.snap { scroll-snap-type: x mandatory; }
.snap > div { scroll-snap-align: center; }
CSS is accidentally a programming language
HTML stores state (checkboxes, radios). CSS reads state (:checked,
:has()) and computes over it (counters). That's memory, conditionals, and arithmetic โ
which is why people have built calculators and games in pure CSS.
Exhibit A: a quiz that grades itself.
Exhibit B: a 4-bit adder. Each lit bit adds its place value to a CSS counter โ the stylesheet is literally doing the math. Click the bits.
A = ยท B = ยท A + B =
.bit:has(#a8:checked) { counter-increment: aval 8 total 8; }
.readout .t::after { content: counter(total); }
The gallery
Bigger productions, still zero JavaScript: