Live showcase · V2 · the deeper cuts

What V1 missed

Ten more platform features from the 2026 frontier — the ones that didn't make the first cut. Subgrid, cascade layers, style queries, auto-height animation, self-sizing fields, auto-contrast text, interaction-aware validation, media state styling, hint popovers, and animatable custom properties. All live, all hand-built, zero dependencies.

Scroll
01

Subgrid

Universal · Baseline 2023

Nested grids that inherit their parent's tracks. Toggle it on: the card headers, bodies, and footers align perfectly across all three cards — even though each body has different content length. Before subgrid, that required a table or JS measurement.

Toggle subgrid — watch the rows align
off — headers misaligned
Header A
Short body. Just one line of text here.
Footer A
Header B
A much longer body that wraps across several lines and pushes the footer of this card down, breaking the alignment with its neighbours.
Footer B
Header C
Medium body. Two lines of text, roughly.
Footer C
View code
/* The card inherits the parent grid's row tracks */
.sg-grid { display: grid; grid-template-columns: repeat(3, 1fr); }
.sg-card {
  display: grid;
  grid-template-rows: subgrid;   /* inherit parent's rows */
  grid-row: span 3;               /* span header/body/footer */
}
02

@layer — cascade control

Universal

The end of specificity wars. Styles are ordered by layer, not by selector weight. Click "shuffle" — the same button, same selectors, but the layer order changes which rule wins. No !important, no hacks.

Shuffle the layer order — the winner changes
View code
/* Declare the order once — later layers win */
@layer reset, base, components, utilities;

@layer base      { .btn { background: #c96442; } }
@layer components{ .btn { background: #8b7cf6; } }
@layer utilities { .btn { background: #7fb069; } }

/* Same specificity — the LAYER decides, not the selector */
03

Container Style Queries

Chrome · Safari 18+ · Firefox · Interop 2026

Size queries respond to width — style queries respond to custom properties. The card restyles itself based on a theme flag on its container. This is how components become theme-aware without context classes.

Set the container's theme — the card re-skins itself

I read my container's style

@container style(--sq-theme: dark) — my colors come from a custom property on my parent, not from a class on me.

container queries can now query VALUES, not just sizes
View code
/* The container carries a theme flag */
.sq-container { container-type: inline-size; }

/* Query the VALUE, not the size */
@container style(--sq-theme: dark) {
  .sq-card { background: #1a1a1a; color: #eee; }
}
@container style(--sq-theme: accent) {
  .sq-card { border-color: var(--accent); }
}
04

interpolate-size — animate to auto

Chrome 129+ · Safari 18.2+ · Firefox flag

The oldest CSS pain: you could never animate height from 0 to auto. `interpolate-size: allow-keywords` finally makes it buttery. This accordion is pure CSS — no max-height hacks, no JS measurement.

Click the header — height animates 0 → auto
Browsers couldn't interpolate between a fixed length and auto — the computed value was unknown until layout. Teams hacked it with max-height guesses (janky) or JS measurement (expensive). interpolate-size: allow-keywords tells the browser to treat auto as an animatable value. One line, real physics, 60fps.
interpolate-size: allow-keywords + transition: height .45s
View code
.acc-body {
  height: 0;
  interpolate-size: allow-keywords;   /* the magic line */
  transition: height .45s cubic-bezier(.22,1,.36,1);
}
.acc.open .acc-body { height: auto; }   /* animates smoothly */
05

field-sizing: content

Chrome 123+ · Firefox 128+ · Safari 18.2+

Form fields that size themselves to their content. Type in the first box — it grows as you type, shrinks as you delete. The second box is a normal textarea for comparison. No JS, no autoresize library.

Type — the first box grows with its content
one CSS property replaces every autoresize script
View code
textarea {
  field-sizing: content;   /* size = content, not a fixed height */
  resize: none;             /* the content IS the resize handle now */
}
06

contrast-color()

Safari · Firefox · Chrome · Interop 2026

The browser picks black or white — whichever reads best on your background. Pick any color: the text stays readable automatically. No more hand-pairing every background with a text color.

Pick a color — text contrast resolves itself
#8b7cf6

Readable, automatically

contrast-color() chose this text color for me.

color: contrast-color(var(--cc-bg))
View code
.cc-box {
  background: var(--cc-bg);
}
.cc-box h4, .cc-box p {
  color: contrast-color(var(--cc-bg));  /* black or white, browser decides */
}
07

:user-valid / :user-invalid

Universal · Baseline 2024

Validation styling that waits for the user to actually interact. The left input screams red the moment you type a wrong character. The right input stays calm until you've finished and moved on — then it judges. That's the difference between :invalid and :user-invalid.

Type in both — feel the difference

:invalid (immediate)

red the moment it's wrong

:user-invalid (after interaction)

judges only after you've engaged
:user-invalid only fires after the user has interacted — no premature red flashing
View code
/* Immediate — red as soon as the value is invalid */
input:invalid { border-color: #d9534f; }

/* User-aware — red only after the user has engaged */
input:user-invalid { border-color: #d9534f; }
input:user-valid   { border-color: #7fb069; }
08

:playing / :paused — media state styling

Safari 18.4+ · Chrome · Interop 2026

CSS can now style elements based on whether a media element is playing. This equalizer is driven entirely by :playing — no JS animation, no class toggling. The browser's own media state is the selector.

Press play — the equalizer is pure CSS
Idle
.mp-visual:playing .eq i { animation: eqb 1s infinite } — the state IS the selector
View code
/* Style based on media playback state — no JS */
.mp-visual:playing .eq i { animation: eqb 1s ease-in-out infinite; }
.mp-visual:paused  .eq i { animation: none; }

/* Also: :buffering, :muted, :volume-locked, :seeking */
09

popover="hint"

Chrome 130+ · Interop 2026

The 2026 popover addition: subordinate popovers that don't dismiss their parents. Hover the tooltip trigger, then open the popover — the tooltip stays. Before "hint", opening one popover closed everything else. Tooltips and menus can now coexist.

Hover the tooltip, then open the popover — both stay
I'm a hint popover — I don't dismiss anything.

I'm an auto popover

Normally I'd close the tooltip. With popover="hint", the tooltip survives.

popover="hint" — subordinate popovers that don't dismiss auto popovers
View code
/* The tooltip becomes a hint — it never dismisses others */
<div id="tooltip" popover="hint"> ... </div>

/* Auto popovers still close on ESC/outside-click, */
/* but hints stay put — perfect for tooltips */
10

@property — animatable custom properties

Universal · Baseline 2024

Custom properties were never animatable — the browser didn't know their type. @property registers the type, and suddenly gradients, angles, and colors can animate. This dial spins via an animated custom property. Drag the hue slider: the whole palette re-derives from one number.

The dial spins via @property — drag hue to re-derive the palette
Base hue 260
@property --spin { syntax: "<angle>" } — registered types make custom properties animatable
View code
/* Register the type — now it can animate */
@property --spin {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}
.pr-dial {
  background: conic-gradient(from var(--spin), ...);
  animation: spin 6s linear infinite;
}
@keyframes spin { to { --spin: 360deg; } }  /* animates the variable */
+

Bonus — 6 more, code-only

The 2026 baseline

Not every frontier feature needs a stage. These six are the quiet workhorses of the modern baseline — one-liners that replace entire libraries.

CSS NestingUniversal
Sass-style nesting, native. One less build dependency.
.card {
  padding: 1rem;
  &:hover { transform: translateY(-2px); }
  & .title { font-size: 1.5rem; }
}
@scopeInterop 2026
Scoped styles — selectors can't leak outside the subtree.
@scope (.card) {
  a { color: var(--accent); }  /* only inside .card */
}
content-visibilityUniversal
Skip rendering off-screen content. Instant long-page speedup.
.section {
  content-visibility: auto;
  contain-intrinsic-size: 0 600px;
}
text-wrap: balanceUniversal
Headlines with balanced line lengths. No manual breaks.
h1, h2 {
  text-wrap: balance;
}
color-mix()Universal
Derive every shade from one brand color.
.btn:hover {
  background: color-mix(in oklch, var(--brand), white 15%);
}
Speculation RulesChrome · Edge
Prerender on hover — next page in milliseconds.
<script type="speculationrules">
{ "prerender": [{ "where": { "href_matches": "/*" },
  "eagerness": "moderate" }] }
</script>
+

UX Upgrades V2 — three more patterns

Design layer

The engagement patterns that make interfaces feel alive — command palettes, skeleton loading, and drag-to-reorder. All hand-built, all liftable into WebChef reports.

Command Palette

⌘K pattern · fuzzy filter

Type to filter, ↑↓ to navigate, Enter to run

Skeleton Loading

shimmer · perceived performance

Pure CSS shimmer — no library

Drag to Reorder

HTML5 drag & drop · no library
Anchor Positioning #1
View Transitions #2
Scroll-driven Animations #3
Container Queries #4
Popover API #5
Drag to reorder — ranks update live