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.
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.
/* 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 */
}
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.
/* 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 */
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.
@container style(--sq-theme: dark) — my colors come from a custom property on my parent, not from a class on me.
/* 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); }
}
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.
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.
.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 */
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.
textarea {
field-sizing: content; /* size = content, not a fixed height */
resize: none; /* the content IS the resize handle now */
}
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.
contrast-color() chose this text color for me.
.cc-box {
background: var(--cc-bg);
}
.cc-box h4, .cc-box p {
color: contrast-color(var(--cc-bg)); /* black or white, browser decides */
}
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.
/* 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; }
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.
/* 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 */
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.
Normally I'd close the tooltip. With popover="hint", the tooltip survives.
/* 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 */
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.
/* 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 */
Not every frontier feature needs a stage. These six are the quiet workhorses of the modern baseline — one-liners that replace entire libraries.
.card {
padding: 1rem;
&:hover { transform: translateY(-2px); }
& .title { font-size: 1.5rem; }
}
@scope (.card) {
a { color: var(--accent); } /* only inside .card */
}
.section {
content-visibility: auto;
contain-intrinsic-size: 0 600px;
}
h1, h2 {
text-wrap: balance;
}
.btn:hover {
background: color-mix(in oklch, var(--brand), white 15%);
}
<script type="speculationrules">
{ "prerender": [{ "where": { "href_matches": "/*" },
"eagerness": "moderate" }] }
</script>
The engagement patterns that make interfaces feel alive — command palettes, skeleton loading, and drag-to-reorder. All hand-built, all liftable into WebChef reports.
Type to filter, ↑↓ to navigate, Enter to run
Pure CSS shimmer — no library