CSS Renders DOOM, Pretext Rethinks Text Layout, and axios Gets Poisoned
Published on 01.04.2026
CSS is DOOMed: Rendering DOOM in 3D with CSS
TLDR: A developer built a fully playable version of DOOM where every wall, floor, barrel, and enemy is a real HTML div positioned with CSS 3D transforms. The game logic runs in JavaScript, but the rendering is pure CSS. It works. And it is absolutely wild.
Summary: I have been doing this long enough to have seen a lot of "CSS does X" demos, and most of them are tricks. Fun tricks, sure, but tricks. This is not that. This is someone actually building a 3D renderer in CSS, extracting real map geometry from the original DOOM WAD file, and letting the browser's layout engine do the trigonometry. We are talking about walls whose width is calculated using the hypot() CSS function, whose rotation uses atan2(), all computed from raw DOOM coordinates passed in as custom properties. JavaScript sets four numbers, CSS figures out where everything goes.
The coordinate system translation is genuinely clever. DOOM uses a top-down 2D map where Y increases northward, which maps to CSS 3D with a translate3d of x, negative-z, negative-y. The author moves the entire world around the player rather than moving a camera, which turns out to be a classic trick from 3D rendering, just expressed entirely in CSS transforms on a scene container.
What I find most technically satisfying is how CSS custom properties and the @property declaration are doing real work here, not just theming variables. Registering --player-z as a typed number property is what enables smooth falling transitions when the player walks off a ledge. Flickering lights are CSS keyframe animations on a --light variable that every wall, floor, and sprite inherits through the cascade. When a door opens, toggling a data-state attribute triggers a CSS transition on a container transform. No JavaScript animation loop needed for any of that.
The experimental pure-CSS culling section is particularly inventive. Since there is no CSS if() with wide enough browser support yet, the author uses a type grinding trick with negative animation-delay on a paused animation to toggle visibility based on whether a computed number is above or below a threshold. It should not work as elegantly as it does. Anchor positioning handles the responsive HUD. The follow camera in spectator mode uses sin() and cos() inside transform values in CSS. The list of production-ready CSS features being used in ways their spec authors probably never imagined keeps growing.
There are real browser bugs documented here too. View Transitions in Safari flatten preserve-3d entirely during transitions. Setting background-image via a CSS custom property causes severe re-rasterization in both Safari and Chrome. These are not theoretical concerns, these are things that bit the author during development, and the workarounds are worth knowing about.
Key takeaways:
- CSS math functions like hypot(), atan2(), sin(), and cos() are now powerful enough for real geometry calculations
- @property registered custom properties unlock animations and transitions on arbitrary values, not just colors and lengths
- The CSS cascade is a genuine inheritance mechanism for things like per-sector lighting, not just a styling tool
- CSS anchor positioning solves real responsive layout problems that previously needed JavaScript
- Browser compositors are not optimized for thousands of 3D surfaces, so manual culling is necessary
- Multiple browser bugs with 3D transforms, View Transitions, and CSS variables in background-image were discovered and documented
Why do I care: Because it is not just a stunt. Every technique in here, the custom property animation, the anchor positioning, the type grinding hack, the @property declarations, these are things you can reach for in production UI work. The demo pushes into territory where the browser breaks down, and documenting exactly where those limits are is genuinely useful for anyone building complex CSS-driven interfaces. The separation of concerns between a game loop that sets four numbers and a CSS renderer that does everything else is also a clean architectural pattern worth thinking about.
You're Looking at the Wrong Pretext Demo
TLDR: The Pretext library went viral for its dragons and ASCII art, but those canvas-rendering demos obscure the library's actual innovation: predicting DOM text layout height without ever reading from the DOM, which solves one of the oldest performance problems in complex text-heavy web apps.
Summary: I want to be honest: this piece made me uncomfortable in a good way. The author is making a pointed argument that the frontend community optimizes for what looks impressive rather than what matters, and the evidence is pretty hard to argue with. Pretext hit seven thousand GitHub stars in three days largely on the strength of demos showing dragons parting text like water. That is a compelling GIF. It is also the least interesting thing the library does.
The actual breakthrough is in the two-phase architecture. The prepare() function runs once, measuring text using canvas.measureText() against the same font engine the DOM uses, but entirely outside the browser's layout process. That measurement gets cached. Then layout() is pure arithmetic over cached widths, predicting how a block of text will wrap at any given container width without touching the DOM at all. The author cites a roughly 0.09ms cost for layout on a 500-text batch, compared to around 19ms for prepare(). Once you have paid the prepare() cost, every subsequent layout is essentially free.
For a virtual scrolling list of chat messages, this means you can predict each message's rendered height before it enters the viewport and position it correctly the first time, with no layout recalculation, while still keeping the text as a real DOM node that screen readers can access, that users can select and copy, that find-in-page works with. The text is not painted pixels. It is actual text that happens to be positioned with math instead of browser layout.
The author is careful to distinguish this from the canvas rendering path, which is what went viral. Canvas text exists outside the accessibility tree. Screen readers cannot read it, users cannot select it, find-in-page skips it, keyboard navigation cannot move through it. That path has legitimate uses in games and data visualizations, but it is not a general-purpose text layout tool. Pretext's DOM path is the one that belongs alongside CodeMirror and ProseMirror in the tradition of editors that stay in the DOM even when it would be faster to leave.
There are honest caveats here. Font metrics need to be fully loaded before prepare() runs or results drift. Ligatures and some CJK composition rules can introduce small discrepancies between canvas measurement and DOM rendering. These are solvable problems, and the library handles many of them already.
Key takeaways:
- Pretext predicts DOM text layout height without DOM reads, eliminating layout thrashing for text-heavy UIs
- The two-phase prepare() and layout() architecture separates expensive measurement from fast arithmetic
- Text rendered via the DOM path retains full accessibility, selection, and find-in-page support
- The viral canvas demos are impressive but use the accessibility trade-off path, not the breakthrough path
- Fonts must be loaded before prepare() runs for accurate results
- The library traces its intellectual lineage to Sebastian Markbage's research at Meta on canvas font metrics as DOM measurement substitutes
Why do I care: Layout thrashing from text measurement is a real bottleneck in complex chat interfaces, feed renderers, and any virtual list with variable-height text rows. The existing solutions all involve either DOM reads, height estimation with shimmer states, or abandoning native text behavior. A library that predicts height mathematically while keeping text in the DOM is genuinely filling a gap that nobody had filled cleanly before. I want to see this in chat apps, not just in dragon demos.
You're Looking at the Wrong Pretext Demo
axios Compromised on npm: Malicious Versions Drop a Remote Access Trojan
TLDR: On March 30, 2026, two malicious versions of the axios npm package were published containing a hidden dependency that installs a cross-platform remote access trojan on macOS, Windows, and Linux. If you installed axios version 1.14.1 or 0.30.4, assume your machine is compromised.
Summary: This is a serious one, and I want to be clear about why it is particularly dangerous before getting into the details. There are zero lines of malicious code inside axios itself. The attack works by injecting a fake dependency called plain-crypto-js into the package metadata of those two specific versions. That dependency runs a postinstall script that contacts a live command-and-control server and drops platform-specific second-stage payloads. After execution, the malware deletes itself and replaces its own package.json with a clean decoy. A developer who inspects their node_modules folder after the fact finds no trace anything went wrong.
This was not opportunistic. The malicious dependency was staged 18 hours before the poisoned axios releases appeared, which suggests premeditation and preparation time. The attack targets a package with over 100 million weekly downloads. The blast radius on something like this is difficult to overstate.
The mechanism is a supply chain attack on the npm publish workflow rather than the source code itself. Maintaining clean source code and a legitimate GitHub repository does not protect against this. The attacker needed only to compromise the npm publish credentials for the axios maintainer account. Once those are compromised, they can publish any version with any dependencies, and the legitimacy of the source repository becomes irrelevant.
The practical response is clear: check your lock files and package.json files for those specific version strings. If you installed either version in a CI/CD environment or on a development machine, treat that machine as compromised and rotate any credentials that were present. For the longer-term lesson, this is exactly the argument for pinning exact versions in lock files, auditing postinstall scripts, and using tools that monitor what your packages are actually doing at install time, not just what they claim to be doing.
Key takeaways:
- axios 1.14.1 and 0.30.4 are malicious and should never be installed
- The attack injects a fake dependency via the npm publish workflow, not by modifying the source code
- The malware targets macOS, Windows, and Linux and self-deletes after execution to evade detection
- Any machine that installed these versions should be treated as fully compromised
- This attack required only compromising npm publish credentials, not the GitHub repository
- Lock file pinning and postinstall script auditing are the practical mitigations
Why do I care: Supply chain attacks against npm packages are not new, but hitting axios specifically, with its enormous install base, is a different order of magnitude than targeting a smaller utility. Every frontend team that runs npm install in CI without reviewing lock file diffs is exposed to this class of attack. This should prompt a real conversation about postinstall script policies and whether your tooling gives you visibility into what actually executes at install time.
axios Compromised on npm - Malicious Versions Drop Remote Access Trojan
What Is CSS Containment and How Can I Use It?
TLDR: CSS containment is an underused performance property that lets you tell the browser specific parts of your DOM are isolated, so it can skip work outside those boundaries when something inside changes. Harry Roberts walks through every value with real-world examples and honest gotchas.
Summary: I am going to say something that might sound obvious but often gets lost: the most expensive thing about a DOM change is usually not the change itself, it is how far the browser thinks the consequences might spread. Without containment, a small interaction inside an off-screen drawer can trigger a layout recalculation rooted at the document, touching thousands of nodes. That is what the OpenTable example in this article demonstrates, and the numbers are striking. Eleven milliseconds rooted at the document, touching 4,371 nodes to relayout 41 of them. Add contain: strict to the drawer's root element, and you get under two milliseconds, rooted at the drawer itself, touching 73 nodes. The change is one CSS declaration.
The four basic containment types each address a different kind of browser work. Layout containment tells the browser that nothing inside the element affects layout outside it. Paint containment tells it that nothing inside can paint outside the padding box, which also lets the browser skip painting the element's subtree entirely when it is off-screen. Size containment decouples the element's size from its children, which is a precondition for the more aggressive optimization strategies but requires you to provide an explicit or intrinsic size or you will collapse the box to zero. Style containment scopes CSS counters and similar side effects.
The content shorthand covering layout, paint, and style is the practical starting point for most use cases: cards in a grid, rows in a data table, article previews on an index page. It is broad enough to capture meaningful savings without the size containment gotchas. The strict shorthand adds size containment, which is appropriate when you have explicitly sized panels and nothing inside them needs to overflow visually.
The content-visibility: auto property builds on containment to let the browser skip layout and painting for off-screen elements entirely, essentially giving you the performance characteristics of a virtualised list without the JavaScript implementation overhead. The catch is you need to provide a reasonable contain-intrinsic-size estimate as a placeholder, and any JavaScript that queries layout on skipped subtrees will force them to render early.
Key takeaways:
- contain: content is a safe default for self-contained UI components like cards, list items, and article previews
- contain: strict requires explicit sizing but can deliver six-times or more speedup on interaction-heavy components
- content-visibility: auto provides virtualisation-like performance for long scrollable content without a JavaScript implementation
- Always pair size containment with contain-intrinsic-size or explicit dimensions or you will get zero-height boxes
- Layout and paint containment both establish new stacking contexts, which can affect z-index behavior and fixed-position children
- Container queries already apply layout and style containment under the hood on their containers
Why do I care: Containment is one of those performance tools that has been in the spec for years and consistently underutilised because the documentation is scattered and the gotchas are non-obvious. A single reference post that covers every value, the shorthands, the interaction with content-visibility, and a real before-and-after case study is exactly what the community needs to start using this in production.
What Is CSS Containment and How Can I Use It?
Astro 6.1: Global Image Codec Defaults, SmartyPants Config, and More
TLDR: Astro 6.1 ships global Sharp codec configuration so you can set JPEG, WebP, AVIF, and PNG encoding defaults once instead of on every image, along with full SmartyPants customization for non-English typography, i18n fallback route exposure for integrations, and several bug fixes.
Summary: The image codec defaults change is the kind of quality-of-life improvement that sounds small until you have actually worked on a project with a large image library. Sharp has always supported fine-grained encoding settings, but the only way to apply them consistently was to set quality parameters on every single image component. Astro 6.1 adds an image service config object in the main config file where you can declare codec-specific defaults for mozjpeg compression, WebP effort levels, AVIF chroma subsampling, PNG compression levels, all of it set once and applied to every processed image, with per-image overrides still taking precedence when you need them.
The SmartyPants change is directly useful for anyone building multilingual sites. The previous behavior assumed English punctuation conventions, which means French guillemets, German quotation marks, or oldschool em-dash handling required either disabling SmartyPants entirely or custom preprocessing. The full configuration is now exposed, so you can specify opening and closing quote characters per language, control ellipsis spacing, and toggle individual transformations on or off.
The i18n improvement is one that integration authors will care about most. Fallback routes, the extra routes Astro generates for locales that do not have their own content when using fallback rewrites, were previously invisible to integrations. Astro 6.1 exposes them through the routes:resolved hook, and the sitemap integration already uses this to automatically include fallback pages in generated sitemaps.
The view transition mobile fix addresses a real annoyance: when iOS Safari provides its own swipe-back animation, Astro's client router was stacking its own transition animation on top, causing a double-animation flicker. The router now detects when the browser is already providing a visual transition and skips its own animation.
Key takeaways:
- image.service.config in astro.config lets you set codec-specific Sharp defaults once for the entire project
- SmartyPants is now fully configurable for non-English quotation marks, dashes, and ellipsis handling
- Integration authors can now access i18n fallback routes through the routes:resolved hook
- The sitemap integration automatically picks up fallback routes as a result
- View transitions no longer double-animate on mobile devices that provide their own swipe transitions
- React hydration fixes address conditional slot rendering and experimentalReactChildren mismatches
Why do I care: The codec defaults change alone justifies an upgrade for any Astro project with significant image processing. Setting WebP effort levels and AVIF chroma subsampling consistently across hundreds of images is the kind of thing that meaningfully affects build output quality and file sizes, and doing it in one place instead of scattered across components is just good configuration design.
Cookie Consent Management in 2026: Overview and Technical Tips
TLDR: A two-part series from Cloud Four covers why cookie consent has become urgent for US companies in 2026, how to pick a performant consent solution, and practical technical techniques for tracking down exactly which scripts are setting which cookies, including building a Puppeteer-based automated audit tool.
Summary: The timing of this series is not coincidental. California's CCPA enforcement has gotten serious, lawyers are dusting off the 1967 California Invasion of Privacy Act and applying it to web tracking, and the $2.75 million Disney GDPR settlement has gotten people's attention. Companies that were treating cookie banners as a European problem are now getting threatening letters. The author makes a useful framing point early: bring in legal counsel before you start categorizing cookies, not because you cannot figure it out yourself, but because having a lawyer tell your organization what is required takes the argument out of your hands when colleagues want their favorite marketing tool classified as "essential."
The performance advice in part one is worth noting. Cookie consent scripts load early and block other scripts, so performance problems in the consent layer are amplified. The author shares real-world data from RUMVision across their customer base comparing different consent managers, and the spread is significant. CookieYes comes out nearly twice as fast as the nearest competitor in that data. Given that most consent scripts will affect your Largest Contentful Paint or at minimum your Time to Interactive, this is not a trivial consideration.
Part two gets into the actual detective work of attributing cookies to their source scripts. The author walks through how Chrome Incognito blocks third-party cookies by default unless you click the eye icon in the toolbar, which means a lot of developers have been testing cookie behavior wrong for years. Some cookies only appear when a user scrolls to the bottom of the page, because the scripts that set them are loaded lazily. Ad networks can insert different scripts based on geography, making some cookies essentially non-deterministic in testing.
The systematic investigation process involves filtering the network panel to assets from a suspect cookie's domain, checking Set-Cookie response headers first since HTTP-set cookies are easier to isolate, then using request blocking as a binary search to narrow down which JavaScript is responsible when HTTP headers come up empty. The author eventually built a Puppeteer command-line tool to automate this, intercepting cookie creation to reconstruct the initiator chain that Chrome's DevTools does not provide natively.
Key takeaways:
- CCPA enforcement has intensified in 2026, with fines starting at $2,663 per incident and lawyers applying CIPA to web tracking
- Get legal counsel to define cookie categories so you are not the one arguing with marketing about what is essential
- Cookie consent script performance varies dramatically between vendors, and it affects core web vitals
- Chrome Incognito blocks third-party cookies by default, which can hide cookies during testing
- Some cookies only appear after scrolling, making manual testing unreliable without automation
- Puppeteer or Playwright can automate cookie auditing with a clean slate on every run
Why do I care: Cookie compliance is one of those tasks that falls to frontend developers even though it is fundamentally a legal and organizational problem. Having a systematic technical approach for attributing cookies to their source, and the tooling to automate it, is the difference between a compliance project that takes days and one that takes weeks. The Puppeteer approach is also more reproducible than manual testing in a browser with plugins and settings that change.
Cookie Consent Management in 2026, Part 1: Overview | Part 2: Technical Tips
Your Options for Preloading Images with JavaScript
TLDR: There are five distinct ways to preload images in JavaScript and they behave differently in critical edge cases, particularly when the server sends a no-store Cache-Control header. The link rel preload approach wins for reliability because it uses the preload cache rather than the HTTP cache.
Summary: I thought I knew this topic. I would have told you: use new Image() and set the src. That triggers a fetch and the browser caches it. Done. This post is a good reminder that confidence about browser caching behavior is often overconfidence.
The new Image() approach works when the server's caching headers cooperate. If the server sends a Cache-Control no-store header, the browser does not cache the image, so when the image element later requests the same URL, you get two network requests and a visible loading delay. This will not happen often, but it will happen on servers where caching is misconfigured or where the asset is deliberately uncacheable.
The link rel preload approach sidesteps this by using a designated preload cache that sits independently of the HTTP cache. The browser stores the prefetched resource there regardless of the server's Cache-Control header, and when the image element requests the same URL, the preload cache is checked first. There is also a subtle but important fetch priority detail: when you inject a preload link via JavaScript rather than putting it in the static HTML head, the browser defaults to low priority unless you explicitly set fetchPriority to high on the element.
The browser is also smart enough to handle the race condition where the image is needed before preloading is complete. Rather than starting a second request, it waits for the in-flight preload request to finish. The author verified this on a throttled 3G connection.
The Cache API and fetch() approaches offer more control at the cost of more code, and both have appropriate use cases. The Cache API is useful when you need resources to persist across page loads or want explicit control over cache lifecycle. The fetch() approach works when you need the response in memory for a short time and do not want to manage cleanup. The hidden div with CSS background-image technique is listed here but the author rightly cannot think of a reason to use it over the other options.
Key takeaways:
- new Image() is unreliable when the server sends a no-store Cache-Control header
- link rel preload uses a separate preload cache that bypasses HTTP cache restrictions
- JavaScript-injected preload links default to low fetch priority unless you set fetchPriority to high
- The browser will wait for an in-flight preload rather than starting a duplicate request
- The Cache API is appropriate when resources need to persist across page loads
- fetch() works for short-lived in-memory preloading but is subject to server Cache-Control headers
Why do I care: Image preloading appears in enough UI patterns, onboarding flows, image galleries, comment systems with uploads, that choosing the wrong approach causes visible loading artifacts that are hard to reproduce because they depend on server caching configuration. Understanding when each approach breaks is the kind of practical knowledge that turns a bug report from "works on my machine" into a five-minute fix.
Your options for preloading images with JavaScript
Magic Link Pitfalls
TLDR: Magic links seem simple but have two non-obvious failure modes: some programs and browsers will follow the link automatically before the user clicks it, claiming the code unintentionally, and logging in the email client's in-app browser instead of the user's default browser defeats the entire purpose.
Summary: The author came to this with the standard security checklist already in hand: short expiration, single use, sufficient entropy in the secret code, hashed storage in the database. All correct. The two pitfalls they found are the ones that are not on that standard checklist.
The first is that GET requests can be sent by the link preview systems in email clients, Slack, Discord, and messaging apps, and by browser prefetch when a user hovers over a link. If your magic link claims the code on the first GET request, any of these systems can silently consume the code before the user ever sees the result. The fix is to have the link land on a page that requires a button click before the code is marked as claimed. That intermediate page also gives you an opportunity to tell the user what is about to happen.
The second pitfall is more insidious. When you click a magic link in an email, many mobile platforms open it in the email app's in-app browser rather than the system's default browser. If the authentication state is set in that in-app browser, the user is logged in to a browser session they will probably never open again while their actual browser remains unauthenticated.
The proper solution is to have the link's server side only mark the code as verified, not complete the login, and have the original tab polling for that verification state. When it detects the code is verified, it completes the authentication in the correct browser session. This also enables cross-device logins, where a user can type a URL on their TV, initiate a login on their phone, and have the TV tab authenticate once the phone's browser has verified the code. The alternative for cases where full magic link implementation is too heavy is a short numeric code the user types in the original tab, which avoids the in-app browser problem entirely.
Key takeaways:
- Never claim a magic link code on the GET request; require an explicit button click on a landing page
- Email preview systems and browser prefetch will follow links before users interact with them
- Mobile email in-app browsers create a different session than the device's default browser
- The correct pattern: the link marks the code as verified, the original tab polls for verification and completes login
- This pattern also supports cross-device login flows naturally
- Short numeric codes in the original tab are a simpler alternative with adequate entropy for low-stakes use cases
Why do I care: Magic links are the kind of authentication flow that looks straightforward but has sharp edges that only show up in real-world user sessions. Both of these pitfalls result in a broken login experience that the developer probably never encounters in testing because they test in a desktop browser and their email client does not preview links. Real users on real devices will hit both of these.
Why Designing in Code Makes You a Better Designer
TLDR: The web has a grain, a set of natural behaviors it does well by default, and designers who do not understand the material they are working with end up building "bicycle bear websites" that fight the medium at every turn.
Summary: I have heard the argument for designers learning to code many times, and it usually sounds defensive. This post is different because it starts from a material understanding of what the web actually is rather than an argument about process or tooling. The core concept comes from Frank Chimero's essay The Web's Grain: an unstyled HTML page is already doing something. It is fluid, it adapts to any viewport, text reflows, content stacks vertically. That is the web's grain. Every design decision that fights those defaults is fighting the material.
The bicycle bear framing is sharp. Apple's Mac Pro scroll-jacking landing page is the example here, and it is a good one. Technically impressive, visually polished, it spun up fans on Apple's own hardware and did not work properly on Apple's own devices. The result was a website that was hostile to the people using it. Not because the developers were incompetent, but because the design did not account for what the material actually does.
The native select element example is one I have lived through personally. Every designer I have worked with who wanted to replace the native select with a custom dropdown ended up underestimating the cost. Keyboard navigation, screen reader support, iOS scroll wheel, automatic form submission, works without JavaScript: all gone, all needing to be rebuilt from scratch, and the rebuild is always incomplete. If you do not understand what the browser gives you for free, you cannot make an informed decision about when it is worth giving it up.
The author's argument is that designers who start from code have a built-in understanding of what comes free and what costs work. That is not a constraint on creativity, it is the opposite: knowing where the grain runs makes it easier to work with the medium rather than against it.
Key takeaways:
- The web has natural default behaviors that responsive design manages rather than eliminates
- Designs that fight the web's grain produce fragile, complex implementations that often fail users
- Native form controls provide accessibility, keyboard support, and integration that custom replacements must painstakingly rebuild
- Understanding the material leads to designs that are more robust and take less work to implement correctly
- Scroll jacking and JS-dependent interactions are common examples of fighting the grain
Why do I care: Frontend developers spend a significant amount of time implementing designs that would have been simpler, more accessible, and more maintainable if the designer had understood what the browser provides by default. The framing of the web as a material with a grain is more useful than "just learn to code" because it points to what specifically needs to be understood: not syntax, but the medium's natural behaviors.
Why designing in code makes you a better designer
Announcing Babylon.js 9.0
TLDR: Babylon.js 9.0 is the biggest release in the project's history, shipping clustered lighting for scenes with hundreds of lights, a visual node particle editor, volumetric light shafts, a fully realized frame graph rendering pipeline, animation retargeting across different skeleton structures, and advanced Gaussian Splat support.
Summary: Babylon.js 9.0 is covering serious ground. The clustered lighting system is probably the most broadly useful addition. The problem it solves is well-known: in a scene with many light sources, every pixel has to compute lighting contributions from every light, even lights that cannot possibly affect that pixel. Clustered lighting divides the screen into tiles and depth slices, then at render time each pixel only consults the lights assigned to its cluster. The result is scenes with hundreds of lights running smoothly. This works on both WebGPU and WebGL 2.
The Node Particle Editor is the kind of visual tooling that lowers the barrier for effects work substantially. If you have used Babylon's Node Material Editor for shader work, the particle editor follows the same paradigm: drag and connect nodes to control emission shapes, sprite sheets, update behaviors, and sub-emitters without writing code. Flow maps and gravity attractors round out the particle system toolkit, giving artists fine-grained directional control over particles based on screen-space textures.
The frame graph is the architectural change I find most interesting from a systems perspective. A directed acyclic graph where each node is a rendering task, with explicit resource declarations that let the system manage texture allocation, reuse, and optimization automatically. The team reports up to 40% GPU memory savings in some scenes. The visual node render graph editor means you can inspect and customize the rendering pipeline without diving into the class framework.
Gaussian Splatting support has matured substantially. The format support now covers multiple file types and includes triangular splatting for mesh-like opaque rendering, shadow casting, and the ability to combine multiple splat assets with independent transforms. Adobe contributed significantly to this work. For photorealistic volumetric captures in the browser, this is the most capable implementation available.
Key takeaways:
- Clustered lighting enables scenes with hundreds of lights at smooth frame rates on both WebGPU and WebGL 2
- The Node Particle Editor brings visual, non-destructive particle authoring to the browser without code
- The frame graph system delivers up to 40% GPU memory savings through automatic texture reuse
- Animation retargeting allows character animations to transfer across skeletons with different proportions and naming
- Gaussian Splat support now handles multiple file formats, shadow casting, and multi-asset composition
- Volumetric lighting with configurable extinction and phase parameters works via WebGPU compute shaders
Why do I care: WebGPU is enabling a class of visual experiences in the browser that simply were not achievable on WebGL. Babylon.js 9.0 is a good indicator of where production-grade browser 3D rendering stands in 2026. The frame graph in particular, with its automatic resource management and visual editing, is bringing GPU programming patterns from native game engines into a JavaScript-accessible form. That matters for anyone building serious 3D interfaces, architectural visualizations, or games for the web.
Web Platform Features Explorer: March 2026 Release Notes
TLDR: The March 2026 web platform release notes document newly available features including WebTransport, Readable Byte Streams, and the Reporting API, along with widely available features like Subgrid, contain-intrinsic-size, and image-set(), plus Chrome and Firefox gains in custom element registries and popover enhancements.
Summary: The web platform features explorer release notes are one of those resources that are easy to overlook because they are not attached to a single browser release, but they give you the clearest cross-browser picture of what you can actually use. This month's set of newly available features is a solid batch.
WebTransport reaching newly available status is significant for anyone building real-time applications. It transmits data between client and server over HTTP/3, supporting both reliable ordered streams and unreliable datagrams in the same connection. For use cases where WebSockets are too heavy or WebRTC too complex, WebTransport is the right tool and it is now available across the baseline browser set.
Readable Byte Streams have been a long time coming. A ReadableStream constructed with the bytes type allows reading from a stream without making extra copies, using a bring-your-own-buffer approach that improves efficiency substantially for large chunk streams. This is useful in any context where you are working with binary data at scale.
On the widely available front, Subgrid joining that tier is meaningful for design systems. The ability for a grid item to inherit its parent grid's track definitions, rather than establishing a completely independent grid, is what makes proper alignment across card components actually tractable without JavaScript measurement. The fact that contain-intrinsic-size is also now widely available pairs nicely with the CSS containment article also in this issue.
In Chrome, scoped custom element registries are landing. Being able to create a registry separate from the global window.customElements registry means multiple components with the same tag name can coexist in the same document, which is a real limitation in micro-frontend architectures. Firefox gets the popover hint value, which creates tooltips that are subordinate to auto popovers without dismissing them.
Key takeaways:
- WebTransport over HTTP/3 is now part of the baseline newly available set
- Readable Byte Streams enable zero-copy binary stream processing in the browser
- Subgrid is now widely available, enabling proper cross-component grid alignment
- Scoped custom element registries in Chrome allow same-tag-name coexistence in micro-frontend scenarios
- popover=hint in Firefox creates subordinate tooltips that do not dismiss auto popovers
- contain-intrinsic-size reaching wide availability makes CSS containment safer to use with content-visibility
Why do I care: Subgrid reaching wide availability is the headline for design system work. The pattern of using a parent grid to align elements across multiple card components has been technically possible but unreliable in production for too long. With wide availability confirmed, it is now reasonable to use without progressive enhancement workarounds for most project contexts.
Web platform features explorer - March 2026 release notes
Transformers.js v4: WebGPU-Accelerated AI Models in the Browser and Node
TLDR: Transformers.js v4 ships a completely rewritten WebGPU backend in C++, a new ModelRegistry API for production workflows, a standalone Tokenizers.js library, a ten-times faster build system using esbuild, and support for models exceeding 8 billion parameters, with the same code now running across browsers, Node, Bun, and Deno.
Summary: A year of development is reflected in how substantial this release is. The biggest change is the new WebGPU runtime, rewritten in C++ and developed in close collaboration with the ONNX Runtime team. The performance improvement this unlocks is real: the team reports around a four-times speedup for BERT-based embedding models by adopting specialized ONNX Runtime operators for attention mechanisms. Perhaps more importantly, the new runtime lets the same Transformers.js code run under hardware acceleration across browsers, Node, Bun, and Deno. Running a 20-billion parameter quantized model at 60 tokens per second in a server-side JavaScript environment is a tangible capability change.
The ModelRegistry API is the addition that matters most for teams using Transformers.js in production rather than prototypes. You can now inspect required files before loading anything, check cache status, calculate total download size from per-file metadata, and query available quantization types. The progress_callback now includes a progress_total event so you can render a real loading progress bar without manually aggregating per-file updates. These are all things that anyone shipping a production AI feature in the browser needs and previously had to build themselves.
The build system migration from Webpack to esbuild is almost comically impactful for a change that is completely invisible to users. Build time dropped from two seconds to 200 milliseconds, a ten-times improvement. Bundle sizes dropped by an average of 10%, with the main web bundle shrinking by 53%. The standalone Tokenizers.js library extracted from the core is 8.8 kilobytes gzipped with zero dependencies, which is remarkable for something that handles locale-aware tokenization with full bidirectional text support.
The repository restructuring to a pnpm monorepo and the extraction of examples to a dedicated repository are the kind of maintenance work that does not make headlines but matters enormously for the long-term health of a project at this scale.
Key takeaways:
- New WebGPU backend in C++ delivers around 4x speedup for embedding models and enables Node, Bun, and Deno support
- ModelRegistry provides production-grade visibility into model assets, cache state, and download size before loading
- 8B parameter model support is new, with a 20B quantized model running at 60 tokens per second on M4 Pro Max
- esbuild migration cuts build time by 10x and the main bundle size by 53%
- Standalone Tokenizers.js library is 8.8kB gzipped with zero dependencies
- The same code now runs across all major JavaScript environments with hardware acceleration where available
Why do I care: Client-side AI inference is moving from demos to production features faster than most people anticipated, and Transformers.js is the library making that practical for web developers. The combination of a production-grade ModelRegistry, genuinely fast inference via the new WebGPU backend, and unified code for browser and server-side environments means the library is now serious infrastructure, not just an experiment.
Transformers.js v4 Release Notes
Form Automation Tips for Happier Users and Clients
TLDR: A working form is just the starting line. The real job is making sure the data that leaves your form can be reliably acted on by the CRM, automation tools, and people downstream, which requires thinking about data normalization, duplicate prevention, and workflow integration from the beginning.
Summary: The framing here is the most valuable thing in this article: a form that works technically but produces data that breaks your client's workflow has failed. The author learned this after a client lost a referral because a lead sat in an email inbox over a weekend without triggering any follow-up automation. The form worked. The business did not.
The practical observations are grounded in real failures. Sending a currency string with a dollar sign to a Zapier integration that expects a number silently broke automated quote generation. The fix was removing the symbol and sending a bare numeric value. A client manually fixed over 200 CRM records because inconsistent name casing created duplicate entries in a system that treated JOHN SMITH and john smith as different contacts. Five minutes of normalization code on the frontend would have prevented it. CRMs rejecting submissions because a field was treated as optional in the form design but required by the import schema is a category of failure that only surfaces when you actually talk to the people receiving the data before you build the form.
The technical recommendations around duplicate submission prevention are solid. Disabling the submit button on the first click with a submitting guard flag, showing a clear loading state, and only re-enabling the button on error rather than success, this is the correct pattern and it is not consistently implemented in the forms I encounter. The argument for structured data objects over flat FormData is also practically grounded: automation tools like Zapier and Make expect structured input and someone always ends up writing parsing logic when you send flat objects.
The broader point is worth repeating: frontend decisions at the form level directly determine whether backend automation is possible, reliable, or broken by default. Asking what happens after this data leaves my hands before writing a line of form code is a small habit change that prevents a large category of client support problems.
Key takeaways:
- Ask about the downstream workflow before designing a form, not after; required fields and data format depend on it
- Normalize data at the frontend: consistent name casing and phone number formatting prevent CRM duplicate records
- Sending currency strings instead of numeric values breaks Zapier and similar automation integrations silently
- Disable the submit button on first click and re-enable only on error to prevent duplicate submissions
- Structure form data as a nested object matching what your automation tools expect, not as a flat FormData blob
- Include timestamp and source on every submission; this information is always useful six months later
Why do I care: The gap between "the form posts successfully" and "the business can act on this submission" is where a lot of client relationships go wrong. Frontend developers who think about the full data lifecycle from input to CRM to follow-up workflow are significantly more valuable to clients building real products than those who treat the form as a UI exercise. This is exactly the kind of cross-discipline thinking that separates a good frontend developer from a great one.