Ten platform features that shipped in the last two years — demonstrated live, not described. Every demo below runs on the real CSS/HTML/JS APIs. Click, scroll, drag, type. Then steal the code.
The browser morphs between UI states — no SPA, no router. Click a card: it physically grows into the detail view. That's document.startViewTransition() plus one CSS property.
Charts that explain themselves — scroll-driven reveals, animated counters, and narrative structure turn raw numbers into a guided story.
// JS — wrap the state change
const update = () => { grid.hidden = true; detail.hidden = false; };
if (document.startViewTransition) document.startViewTransition(update);
else update();
// CSS — name the shared element, browser morphs it
.vt-card, .vt-detail { view-transition-name: vt; }
::view-transition-group(vt) { animation-duration: .5s; }
You're already using this demo — the progress bar at the top of this page and every section fade-in are pure CSS. No IntersectionObserver, no scroll listeners. Runs on the compositor thread at 60-120fps.
This bar fills with your scroll position:
And every section on this page fades up as it enters the viewport — animation-timeline: view()
/* Scroll progress bar — 6 lines, zero JS */
.progress {
animation: grow linear both;
animation-timeline: scroll(); /* tied to page scroll */
}
@keyframes grow { from { transform: scaleX(0); } to { transform: scaleX(1); } }
/* Reveal on entry — replaces IntersectionObserver */
.reveal {
animation: rise linear both;
animation-timeline: view(); /* tied to element visibility */
animation-range: entry 0% entry 55%;
}
The #1 favorite feature in the State of CSS 2026 survey. Hover the buttons — the tooltips anchor to their trigger and flip automatically when they'd overflow the viewport. This killed Popper.js.
position-anchor — I flip when I'd overflow.position-try-fallbacks flips me inline automatically./* The trigger becomes a named anchor */
.trigger { anchor-name: --tip-anchor; }
/* The tooltip pins to it — and flips on overflow */
.tip {
position: fixed;
position-anchor: --tip-anchor;
top: anchor(bottom); left: anchor(center);
translate: -50% 10px;
position-try-fallbacks: flip-block, flip-inline; /* auto viewport collision */
}
Native overlays — no z-index wars, no focus traps, no positioning JS. The browser handles top-layer rendering, ESC-to-close, and outside-click dismissal. Even the toast is a popover.
Press ESC, click outside, or open another popover — I dismiss myself. Zero JavaScript.
I'm popover="manual" — only explicit actions close me. Classic toast behavior.
<!-- That's the whole API -->
<button popovertarget="pv1">Open popover</button>
<div id="pv1" popover> ... </div>
/* Backdrop + blur, pure CSS */
.pv::backdrop {
background: oklch(0% 0 0 / .35);
backdrop-filter: blur(3px);
}
"CSS has no parent selector" is over. Three live proofs: the submit button enables only when the form is valid, cards re-layout when they contain an image, and the list shows an empty state when it has no items.
/* Enable submit only when the form is valid */
.hform:has(input:valid) .submit { opacity: 1; pointer-events: auto; }
/* Card re-layouts when it contains an image */
.hcard:has(img) { display: grid; grid-template-columns: 70px 1fr; }
/* Empty state — hide the box when it has no items */
.hlist:not(:has(li))::before { content: "Empty state…"; }
The card responds to its container, not the viewport. Drag the slider — the card flips from stacked to side-by-side based on its own width, while the page stays the same size.
Wide enough? I go side-by-side. Narrow? I stack. The viewport never changed — only my container did. That's the component era.
/* Declare the container */
.cq { container-type: inline-size; }
/* Query it — like a media query, but for the component */
@container (min-width: 400px) {
.cq-card { flex-direction: row; align-items: center; }
}
Three color superpowers in one demo. Drag the sliders to mix a color in OKLCH (perceptually uniform — equal changes look equal). Hover the swatch: color-mix() derives the hover state. And the theme button in the nav? That's light-dark() — one function, whole page.
--bg: light-dark(#0c0c0a, #f5f2e9)
/* One brand color, every variant derived */
.swatch { background: oklch(var(--ml) var(--mc) var(--mh)); }
.swatch:hover {
background: color-mix(in oklch, oklch(var(--ml) var(--mc) var(--mh)), white 14%);
}
/* Dark mode in one line — no duplicate blocks */
:root {
color-scheme: light dark;
--bg: light-dark(#0c0c0a, #f5f2e9);
--text: light-dark(#ece9e2, #1d1b17);
}
Pinterest-style packing with one line: grid-template-rows: masonry. The browser's layout engine does the bin-packing — no Masonry.js, no measurement JS, no layout shift. (Falls back to CSS columns where unsupported.)
.masonry {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: masonry; /* the whole trick */
gap: 10px;
}
Native modal with focus trapping, ESC handling, and backdrop — plus @starting-style so it animates in on first render (previously impossible for elements entering from display:none).
<dialog id="dlg"> ... </dialog>
// JS: dlg.showModal() — that's it
/* Entrance animation for first render */
dialog[open] {
opacity: 1; translate: 0 0;
transition: opacity .25s, translate .25s, overlay .25s allow-discrete, display .25s allow-discrete;
}
@starting-style {
dialog[open] { opacity: 0; translate: 0 14px; }
}
Style arbitrary text ranges without touching the DOM. Type in the box — every match gets highlighted via ::highlight(), no spans injected, no re-render.
The frontier of web design in 2026 is not a framework — it's the platform itself. Container queries, view transitions, and scroll-driven animations mean the frontier is now one CSS property away. The best teams ship the frontier without a single new dependency.
// JS — build a range, register a highlight
const r = new Range(); r.setStart(node, 0); r.setEnd(node, 5);
const h = new Highlight(r);
CSS.highlights.set('search', h);
/* CSS — style the highlight, no DOM pollution */
::highlight(search) {
background: color-mix(in oklch, var(--accent2) 55%, transparent);
border-radius: 3px;
}
Beyond the platform APIs — the design techniques that make data feel alive. Every card below is a working pattern you can lift straight into WebChef reports.
blur(14px) saturate(1.4) — Apple's iOS 26 material, in 6 lines of CSS
Move your cursor over me — depth layers at translateZ(30px)
"The best prompt isn't the longest. It's the one that achieves the goal with the minimum necessary structure."
text-wrap: balance — every line roughly equal width
Charts that explain themselves — scroll-driven reveals turn numbers into narrative.
Interfaces that show what they're doing, why, and how to stop it. Visibility is the new trust.
Layouts that reshape by user context — not just personalization, but re-architecture.
Animation that communicates state — never decoration. Respect reduced-motion.
Depth, tilt, and 3D layers — interfaces that exist in space, not just on a screen.
Hover: lift + shine sweep · Press: scale down · Release: spring back