Goodbye sizes Headaches, Hello Scroll Animations: Frontend Focus April 2026

Published on 30.04.2026

motyl.dev<div></div></>FRONTEND

TLDR

April 2026 delivered a batch of genuinely useful browser features landing in stable. The sizes="auto" attribute for lazy-loaded images finally solves a fourteen-year-old problem. Scroll-driven animation properties are now baseline across all major browsers. And a handful of creative tools, from a font that renders charts to a dev-tool that replaces port numbers with named URLs, round out a packed issue.

The End of Writing sizes Attributes by Hand

I've wanted this for a long time. Andy Ossorio, who literally chaired the Responsive Images Community Group and helped get srcset and sizes standardized, wrote a deeply satisfying post about why he always hated the sizes attribute, even while championing it. And now, finally, there's a way out.

Two patches landed in Gecko and WebKit that align them with Blink: support for sizes="auto" on lazy-loaded images. The deal is simple. Slap loading="lazy" on an <img>, set sizes="auto", and let the browser figure out the right source size from srcset on its own. The browser waits until layout is known before making the request, so it actually has the information it needs. No more hand-crafting (min-width: 1040px) calc(24.64vw - 68px) strings. WordPress already shipped this via a patch from Joe McGill.

You can even add it as a progressive enhancement: sizes="auto, (min-width: 1040px) 650px, calc(94.44vw - 15px)" works today because browsers that don't understand auto skip it and fall through to the rest. Zero cost to ship now.

The catch: it only works with lazy-loaded images, which means your above-the-fold hero images still need a manually described sizes value. But those are easy. It's all the card grids and avatar clusters and sidebar thumbnails that were the real nightmare, and those are almost always lazy-loadable anyway.

The end of responsive images

Scroll-Driven Animations Are Finally Baseline

Josh Comeau published an excellent deep-dive into the CSS Animation Timeline API, and with Firefox 150 shipping animation-range-start, animation-range-end, and the animation-range shorthand, this feature set is now baseline across the board.

The core idea: @keyframes defines a 0-100% range of values, and normally time drives that range. With animation-timeline: view(), scroll position drives it instead. An element entering the viewport goes from 0% to 100%. You get fade-ins, slide-ins, parallax effects, all in CSS with no JavaScript scroll listeners. The animation-range property controls exactly when the animation begins and ends relative to the viewport crossing, with named ranges like entry, exit, cover, and contain.

What I find most elegant is the timeline-scope escape hatch. One element's scroll progress can drive another element's animation even when they're not in a parent-child relationship. You declare the timeline variable on a shared ancestor and both elements can reference it. This solves what would have been a painful JavaScript coordination problem.

The animation-timeline: scroll() variant, which measures total scroll progress on a page, is also there but less useful in practice. Reading progress bars are the main case. The real power is in per-element view timelines.

Scroll-Driven Animations

What Landed in Browsers in April 2026

The web.dev monthly roundup covers Chrome 147 and Firefox 150 stable releases, and there's quite a bit in there.

contrast-color() is now baseline. Pass it a background color and it returns either black or white depending on which has better contrast. It's the function we've all been writing in JavaScript for years, now built into CSS.

Chrome 147 shipped element-scoped view transitions via element.startViewTransition(). Rather than transitioning the whole document, you can scope the transition to a specific container. Multiple transitions can run concurrently and they respect ancestor clips and transforms. This opens up a lot of UI patterns that were awkward with document-level transitions.

Chrome 147 also added border-shape, which lets you create non-rectangular borders using polygon or circle shapes without resorting to clip-path hacks. And Math.sumPrecise is now baseline, which returns a precisely summed result from an iterable without floating-point accumulation errors.

Looking at betas: Chrome 148 brings name-only container queries (no size condition required), and Firefox 151 brings container style queries. Safari 26.5 adds the :open pseudo-class for <details>, <dialog>, <select>, and <input>.

New to the web platform in April

A Lot of CSS Hit Baseline Since October 2025

Adam Argyle put together a post cataloging CSS features that landed across all major browsers between October 2025 and April 2026. If you've been mentally filing these under "not ready yet," it's time to update that list.

Anchor Positioning is generally usable now, though some sub-features still have gaps. @scope for selector scoping (not style scoping, an important distinction) is across the board. Name-only container queries just hit Chrome, Safari, and Firefox simultaneously. The shape() function for responsive clipping paths is there. view-transition-class for applying a single animation rule to many elements at once is available.

The rcap, rch, rex, and ric typographic units are also now universally supported, giving you relative units tied to the root element's cap height, ch width, x height, and ic size. These matter if you're doing any serious typography work.

CSS Recently In All Browsers

Compositing and Blend Modes, Explained Properly

Niklas Gadermann wrote one of the best explanations of CSS compositing and blend modes I've read. It starts from first principles: every pixel on your screen is the result of layers being composited together using Porter-Duff operators, a set of twelve mathematical rules developed at Lucasfilm in 1984. The browser uses "source over" by default, which is why elements stack the way you'd expect.

Blend modes are a separate step that happens before compositing, only in the region where source and backdrop overlap. The formula takes two colors, runs a blending function on each channel, and produces a new source color that then gets composited normally. Separable blend modes (like multiply, screen, lighten, darken) work independently per RGB channel. Non-separable ones (like hue, saturation, color, luminosity) operate on perceptual properties instead.

One practical trick from the post: use the lighten blend mode to merge overlapping borders from two separate elements into a single continuous outline. If borders are darker than their surrounding shapes, the darker one "wins" under lighten, making the overlap invisible. Useful for tooltip arrows and message bubbles with custom tails.

There's also a color space warning worth heeding. Blend modes are computed in the display's color space. On a MacBook with Display P3, sRGB colors get converted first, and that conversion changes their channel values enough to produce unexpected results. If you need precision, specify color(display-p3 ...) values directly.

Compositing & Blending

Recreating Apple's Vision Pro Animation in Pure CSS

This one is a proof of concept, but an impressive one. Taking Apple's Vision Pro product page animation (the scrollable hardware explode-then-flip sequence), someone rebuilt it entirely in CSS using scroll-driven animations, position: sticky, CSS Grid for z-index layering without pulling elements out of flow, and a sequence of background-image swaps as a frame-by-frame video substitute.

The frame animation uses about 60 background-image keyframes with preload hints to avoid choppiness. It's not production-ready in that form, but the architecture of the scroll-driven pieces is worth studying. The use of animation-range: contain cover to ensure animation starts only when the element is fully in view, and animation-range: cover 10% contain to delay start by 10% for the flip section, shows how granular animation-range control can be.

Firefox isn't supported because of the missing scroll-driven animation features at time of writing, but that gap is closing fast.

Recreating Apple's Vision Pro Animation in CSS

Constructable Stylesheets and Why They Matter for Web Components

If you're building web components (or using Lit), this Frontend Masters post is worth reading. The standard way to inject CSS into shadow roots was a <style> tag per instance, meaning 200 component instances meant 200 full CSS parses of the same rules.

Constructable Stylesheets fix that. You create a CSSStyleSheet object in JavaScript with new CSSStyleSheet(), populate it with replaceSync(), and then assign it to shadowRoot.adoptedStyleSheets. Every shadow root that references the same object shares a single parsed rule tree. The browser parses once. Always.

Lit's static styles = css\...`does all of this automatically. Thecsstag returns aCSSResultwrapper. On first instance connection, Lit creates the actualCSSStyleSheet` and caches it. Every subsequent instance just adds a reference to that same cached object. For a design system with dozens of shared style modules applied across many components, the memory footprint stays flat regardless of instance count.

The rough edges: no SSR serialization path (Lit SSR falls back to <style> tags on the server), no way to control which @layer an adopted stylesheet belongs to from outside the component, and live mutation via replaceSync() is all-or-nothing across all instances.

Constructable Stylesheets and adoptedStyleSheets

Session Timeouts Are an Accessibility Problem

This Smashing Magazine piece makes a point that should be obvious but often gets overlooked in security-versus-UX tradeoffs. Session timeouts disproportionately affect users with disabilities: motor impairments mean slower input, cognitive differences mean processing takes longer, and screen readers mean navigating a page takes more time than a visual scan would.

Concrete examples: a 30-second countdown warning read by a screen reader every second, making the page unusable. A government visa application form that silently expires after 20 minutes with no warning and no auto-save. A user with cerebral palsy who needs multiple attempts to register a keypress timed out mid-form.

The WCAG guidance here is clear. Warn users before timeout. Give them a way to extend. Auto-save form progress where possible. The article has a good comparison: the UK pension credit application warns two minutes in advance and lets you extend, while the US visa DS-260 just logs you out silently.

A practical implementation note: sessionStorage and localStorage for auto-saving progress, plus a dialog that pops up before expiry with a single "continue" action, covers most of the accessibility requirements at low implementation cost.

Session Timeouts: The Overlooked Accessibility Barrier In Authentication Design

Debugging WebAssembly in Chrome DevTools

Short post, but practically useful. Chrome's DevTools WASM debugger is more capable than many people realize. You can set breakpoints in the disassembled WAT view, step through instructions, inspect local variable values, and view the call stack, just like debugging JavaScript.

The most valuable feature: "pause on exceptions." For ref.cast failures in WASM GC code, the debugger stops exactly at the failing instruction and shows the actual type of the reference in the Scope pane. Finding that with printf debugging in WASM is painful because strings are awkward to work with and GC references are opaque to the host. The debugger just takes you to the spot.

Worth bookmarking if you're compiling anything to WASM and running into mysterious traps.

Debugging WASM in Chrome DevTools

A Font That Renders Inline Charts

Datatype is a variable font that uses OpenType ligature substitution to turn text like {b:30,70,20,90} into a bar chart, {l:10,40,25,70} into a sparkline, and {p:65} into a pie chart showing 65% fill. No JavaScript, no SVG, no canvas. Just a font.

Two variable axes control density and weight, so the charts respond to those CSS properties like any other variable font. You can embed them in running prose and they scale with the surrounding text because they inherit font metrics.

The syntax is obvious enough that it's readable in source, which is a nice property. {l:68,82,55,90} doesn't require decoding. The stock watchlist demo on the project page shows sparklines in a table alongside text prices and it looks genuinely clean. It works in any context where fonts render, including tables, dashboards, and reports.

Datatype — variable font that turns text into charts

portless: Named localhost URLs for Dev

Vercel Labs shipped portless, a proxy tool that replaces localhost:3000 with https://myapp.localhost. You wrap your dev command: portless myapp next dev, and it assigns a random port internally, sets the PORT environment variable, starts an HTTPS proxy with HTTP/2, generates a local CA on first run, and trusts it automatically.

The git worktree support is the part I keep thinking about. In a linked worktree, it automatically prepends the branch name: fix-ui.myapp.localhost. Multiple worktrees, no URL collisions, no config changes needed. Put portless run in your package.json once and it works everywhere.

It also handles monorepos (reads pnpm-workspace.yaml), supports Tailscale for sharing with teammates, and auto-injects --port and --host flags for frameworks that ignore the PORT environment variable (Vite, Astro, Angular, etc.).

portless

view-transitions-mock: Write View Transitions Without Browser Guards

From the Chrome team: view-transitions-mock is a polyfill for the JavaScript API surface of Same-Document View Transitions, without the visual animation parts. It implements document.startViewTransition(), the ViewTransition class with all its promises, View Transition Types, and document.activeViewTransition. What it doesn't implement: the pseudo-element tree, CSS properties, and the actual animations.

The point is eliminating if (document.startViewTransition) guards from your codebase. Import, call register(), and write View Transitions code that runs in any browser without branching. In browsers with native support, native code runs. In others, the logic runs but there's no visual transition. The DOM still updates, promises still resolve.

view-transitions-mock

DESIGN.md: Design System Context for Coding Agents

Google Labs shipped a format spec called DESIGN.md: a file that combines YAML front matter (design tokens: colors, typography, spacing, border radii, components) with Markdown prose explaining the rationale behind them.

The idea is that an AI coding agent reading this file gets both the exact values and the "why." Colors, type scales, component token mappings, do's and don'ts, all in one place. A lint command validates token references, checks WCAG contrast ratios on component color pairs, and flags orphaned tokens. A diff command compares two versions and reports regressions. An export command converts tokens to Tailwind theme config or DTCG format.

It's in alpha and will change, but the concept is solid. Keeping design intent in a structured, version-controlled file rather than a Figma link or a long verbal description is something I've wanted for agent-assisted UI work for a while.

DESIGN.md

Key Takeaways

  • sizes="auto" with loading="lazy" is production-ready now, zero cost to ship, and eliminates the worst part of responsive image authoring
  • Scroll-driven animation range properties (animation-range-start, animation-range-end) are baseline as of Firefox 150
  • contrast-color(), element-scoped view transitions, border-shape, and Math.sumPrecise are all newly baseline in Chrome 147
  • CSS features like anchor positioning, @scope, name-only container queries, and shape() are now cross-browser and ready to use
  • Constructable Stylesheets via adoptedStyleSheets parse CSS once per component class, not once per instance
  • Session timeout accessibility is underdone in most apps and has a real impact on users with motor, cognitive, and vision impairments
  • Chrome DevTools has a real WASM debugger with breakpoints, step-through, and exception pause support
  • portless from Vercel Labs replaces port numbers with named .localhost URLs and handles git worktrees automatically