<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Bram.us</title>
	<atom:link href="https://www.bram.us/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.bram.us</link>
	<description>A rather geeky/technical weblog, est. 2001, by Bramus</description>
	<lastBuildDate>Thu, 28 May 2026 07:22:25 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>
	<item>
		<title>Introducing view-transitions-toolkit, a collection of utility functions to more easily work with View Transitions.</title>
		<link>https://www.bram.us/2026/04/02/view-transitions-toolkit/</link>
					<comments>https://www.bram.us/2026/04/02/view-transitions-toolkit/#respond</comments>
		
		<dc:creator><![CDATA[Bramus!]]></dc:creator>
		<pubDate>Thu, 02 Apr 2026 20:03:48 +0000</pubDate>
				<category><![CDATA[Original Content]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[view transitions]]></category>
		<guid isPermaLink="false">https://www.bram.us/?p=36216</guid>

					<description><![CDATA[<p>In my work with View Transitions over the last several years, I’ve published everything from deep-dive articles, demos, and announcement videos at Google I/O. I’ve also done some more experimental things with it, such as <a href="https://www.bram.us/2025/02/07/view-transitions-applied-more-performant-view-transition-group-animations/">optimizing the keyframes</a> or <a href="https://www.bram.us/2024/04/29/if-view-transitions-and-scroll-driven-animations-had-a-baby-css-cafe/">driving a View Transition by scroll</a>.</p>

<p>To turn the lessons from these scattered experiments into something more reusable for both you and me, I’ve bundled the most frequent code patterns into a dedicated package: <a href="https://chrome.dev/view-transitions-toolkit/"><code>view-transitions-toolkit</code></a>.</p>]]></description>
										<content:encoded><![CDATA[<figure><a href="https://www.bram.us/wordpress/wp-content/uploads/2026/04/view-transitions-toolkit-main.png"><img fetchpriority="high" decoding="async" src="https://www.bram.us/wordpress/wp-content/uploads/2026/04/view-transitions-toolkit-main.png" alt="" width="560" height="370" class="alignnone size-medium wp-image-36217" /></a><figcaption>Screenshot of <a href="https://chrome.dev/view-transitions-toolkit/">the View Transitions Toolkit homepage</a></figcaption></figure>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<div class="intro">
<p>In my work with View Transitions over the last several years, I’ve published everything from deep-dive articles, demos, and announcement videos at Google I/O. I’ve also done some more experimental things with it, such as <a href="https://www.bram.us/2025/02/07/view-transitions-applied-more-performant-view-transition-group-animations/">optimizing the keyframes</a> or <a href="https://www.bram.us/2024/04/29/if-view-transitions-and-scroll-driven-animations-had-a-baby-css-cafe/">driving a View Transition by scroll</a>.</p>
<p>To turn the lessons from these scattered experiments into something more reusable for both you and me, I’ve bundled the most frequent code patterns into a dedicated package: <a href="https://chrome.dev/view-transitions-toolkit/"><code>view-transitions-toolkit</code></a>.</p>
</div>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3>The <code>view-transitions-toolkit</code></h3>
<p><code>view-transitions-toolkit</code> is a collection of small, focused helper functions for use with View Transitions. They fill in some of the gaps to making advanced patterns much easier to implement.</p>
<ul>
<li><strong>Feature Detection</strong>: Get information about whether certain View Transitions sub-features are supported.</li>
<li><strong>Shim Support</strong>: Shim support for <code>document.activeViewTransition</code>.</li>
<li><strong>Animations</strong>: Utilities for extracting, measuring, and optimizing animations.</li>
<li><strong>Transition Playback Control</strong>: Pause, Resume, or Scrub the playback of a View Transition.</li>
<li><strong>Automatic Page Navigation Types</strong>: Automatically inject View Transition Types based on navigation origin/destination.</li>
</ul>
<p>The goal of the project is to provide these utilities so you don&#8217;t have to reinvent the wheel every time you want to do something slightly advanced with View Transitions.</p>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3>Getting Started</h3>
<p>Using the toolkit is incredibly simple. First, install it from npm:</p>
<pre><code>npm i view-transitions-toolkit</code></pre>
<p>Then, you can import the modules you need and use their functions. Optimizing the keyframes of a View Transition group for example is a simple one-liner:</p>
<pre><code class="language-js">import { optimizeGroupAnimations, OPTIMIZATION_STRATEGY } from "view-transitions-toolkit/animations";

const t = document.startViewTransition(() => { … });
await t.ready;

// Optimize all Group Animations using the default SCALE strategy
optimizeGroupAnimations(t, "*");

// Optimize only the `::view-transition-group(box-flip)` animation using the SLIDE strategy
optimizeGroupAnimations(t, "box-flip", OPTIMIZATION_STRATEGY.SLIDE);</code></pre>
<p>I won’t go into details here, as all features are documented in <a href="https://github.com/GoogleChromeLabs/view-transitions-toolkit/tree/main/docs">the docs folder</a> of the repo.</p>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3>Demos</h3>
<p>The project also comes with a public website that not only gives you the basic info, but also comes with a bunch of demos: <a href="https://chrome.dev/view-transitions-toolkit/">https://chrome.dev/view-transitions-toolkit/</a>.</p>
<figure><a href="https://www.bram.us/wordpress/wp-content/uploads/2026/04/view-transitions-toolkit-optimize.png"><img decoding="async" src="https://www.bram.us/wordpress/wp-content/uploads/2026/04/view-transitions-toolkit-optimize.png" alt="" width="560" height="370" class="alignnone size-medium wp-image-36218" /></a><figcaption>Screenshot of <a href="https://chrome.dev/view-transitions-toolkit/optimize/">the <code>optimize()</code> demo</a></figcaption></figure>
<p>The source of the demos is included in the repository as well.</p>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3>Go check it out</h3>
<ul>
<li><a href="https://chrome.dev/view-transitions-toolkit/"><code>view-transitions-toolkit</code> demos  (Chrome.dev) &rarr;</a></li>
<li><a href="https://github.com/googlechromelabs/view-transitions-toolkit/"><code>view-transitions-toolkit</code> source (GitHub) &rarr;</a></li>
<li><a href="https://www.npmjs.com/package/view-transitions-toolkit"><code>view-transitions-toolkit</code> on NPM &rarr;</a></li>
</ul>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3>Spread the word</h3>
<p>Feel free to reshare one of the following posts on social media to help spread the word:</p>
<ul>
<li><a href="https://bsky.app/profile/bram.us/post/3mijlrthmc22v">🦋 Bluesky</a></li>
<li><a href="https://front-end.social/@bramus/116335982870732034">🦣 Mastodon</a></li>
<li><a href="https://www.linkedin.com/feed/update/urn:li:activity:7445503375812567041/">💼 LinkedIn</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://www.bram.us/2026/04/02/view-transitions-toolkit/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>CSS position: sticky now sticks to the nearest scroller on a per axis basis!</title>
		<link>https://www.bram.us/2026/03/30/css-sticky-per-axis/</link>
					<comments>https://www.bram.us/2026/03/30/css-sticky-per-axis/#respond</comments>
		
		<dc:creator><![CDATA[Bramus!]]></dc:creator>
		<pubDate>Mon, 30 Mar 2026 12:21:18 +0000</pubDate>
				<category><![CDATA[Original Content]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[sticky]]></category>
		<guid isPermaLink="false">https://www.bram.us/?p=36196</guid>

					<description><![CDATA[<p>If you’ve ever tried to build a data table with a sticky header and a sticky first column, you know the pain. You’d think a simple <code>position: sticky</code> with <code>top: 0</code> and <code>left: 0</code> would be enough, but the reality was that only one of both would stick.</p>

<p>A recent change to CSS fixes this: <code>position: sticky</code> now plays nice with <em>single-axis scrollers</em>, allowing you to have sticky elements that track different scroll containers on different axes. This change is available in Chrome 148 with the experimental web platform features flag flipped.</p>]]></description>
										<content:encoded><![CDATA[<!-- Added by Post/Page Specific Custom Code plugin, thank you for using! -->
<style>/* DL GRID @src https://codepen.io/bramus/pen/POEaXg*/
@media(min-width: 30em) {
dl {
	display: grid;
	grid-template: auto / 10em 1fr;
}

dt {
  grid-column: 1;
}

dd {
  grid-column: 2;
}

dt, dd {
	margin: 0;
	padding: .3em .5em;
	border-top: 1px solid rgba(0,0,0,.1);
}

:is(dt, dd) > :first-child {
  margin-top: 0;
}
:is(dt, dd) > :last-child {
  margin-bottom: 0;
}
}</style>
<figure><div style="width: 640px;" class="wp-video"><video class="wp-video-shortcode" id="video-36196-1" width="640" height="391" loop autoplay muted preload="metadata" controls="controls"><source type="video/mp4" src="https://www.bram.us/wordpress/wp-content/uploads/2026/03/sticky-per-axis-2.mp4?_=1" /><a href="https://www.bram.us/wordpress/wp-content/uploads/2026/03/sticky-per-axis-2.mp4">https://www.bram.us/wordpress/wp-content/uploads/2026/03/sticky-per-axis-2.mp4</a></video></div><figcaption>Recording of <a href="https://codepen.io/bramus/pen/VYKQwmK">the demo</a>.</figcaption></figure>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<div class="intro">
<p>If you’ve ever tried to build a data table with a sticky header and a sticky first column, you know the pain. You’d think a simple <code>position: sticky</code> with <code>top: 0</code> and <code>left: 0</code> would be enough, but the reality was that only one of both would stick.</p>
<p>A recent change to CSS fixes this: <code>position: sticky</code> now plays nice with <em>single-axis scrollers</em>, allowing you to have sticky elements that track different scroll containers on different axes. This change is available in Chrome 148 with the experimental web platform features flag flipped.</p>
</div>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3><a href="#the-situation" name="the-situation">#</a> The Situation</h3>
<p>To understand the impact, let&#8217;s quickly review the standard setup for a responsive table. You start with a wide HTML <code>&lt;table&gt;</code> full of data. To prevent this wide table from breaking your page layout on small screen devices, you typically wrap it in a container with <code>overflow-x: auto</code>.</p>
<pre><code class="language-html">&lt;div class="table-wrapper" style="overflow-x: auto;"&gt;
  &lt;table&gt;
    …
  &lt;/table&gt;
&lt;/div&gt;
</code></pre>
<p>Because you want the column headers (the <code>&lt;thead&gt;</code>) to stay visible when scrolling down the document, you apply <code>position: sticky; top: 0;</code> to them. Simultaneously, you want your first column to stay visible when scrolling sideways through the data, so you apply <code>position: sticky; left: 0;</code> there.</p>
<pre><code class="language-css">.table-wrapper {
  overflow-x: auto;
}

.table-wrapper thead {
  position: sticky;
  top: 0;
}

.table-wrapper td:first-child {
  position: sticky;
  left: 0;
}
</code></pre>
<p>However, this doesn&#8217;t work as expected. While the first column will stick to the left edge of the table wrapper when scrolling horizontally, the headers will scroll out of view along with the rest of the page.</p>
<p>This is because the <code>.table-wrapper</code> becomes the sticky reference for <em>both</em> axes. Because the wrapper only scrolls horizontally, the vertical sticking becomes completely ineffective. Your <code>top: 0</code> headers would just happily scroll out of view along with the rest of the page 😭</p>
<p>To get around this, you had to rely on lots of JavaScript to synchronize the scroll position, or rely on duplicated headers.</p>
<div class="note">
<p>Note: While there are <a href="https://css-tricks.com/a-table-with-both-a-sticky-header-and-a-sticky-first-column/">pure CSS solutions</a> to make sure the headers also stick within the <code>.table-wrapper</code>, the problem described here is different: instead of getting the headers to stick inside the <strong>same</strong> scroller, you want the headers to be stuck against a <strong>different</strong> scroller <em>(here: the <code>.table-wrapper</code> for horizontally sticky stuff, and the document’s scroller for vertically sticky stuff)</em>.</p>
</div>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3><a href="#the-change" name="the-change">#</a> The Change</h3>
<p>Thanks to <a href="https://github.com/w3c/csswg-drafts/issues/12289#issuecomment-3206223182">a recent change in the overflow specification</a> that allows containers to be a scroller for only a single axis, <code>position: sticky</code> can now track two different scrollers — one per axis — for the stickiness.</p>
<p>This means that for our wide data table:</p>
<ul>
<li>The first column can be sticky on the horizontal axis against the table wrapper.</li>
<li>The headers can be sticky on the vertical axis against a completely different scroller — such as the document itself!</li>
</ul>
<p>No more duplicating headers. No more scroll-syncing JavaScript. Just plain CSS doing exactly what you&#8217;d expect it to do.</p>
<div class="note">
<p>The request for this change was filed back in 2017, in CSS WG Issue <a href="https://github.com/w3c/csswg-drafts/issues/865">#865</a>. It’s not that often you see three-digit issues get resolved and implemented 😅</p>
</div>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3><a href="#see-it-in-action" name="see-it-in-action">#</a> See it in action</h3>
<p>You can see this new behavior in action in this CodePen demo:</p>
<p class="codepen" data-height="760" data-pen-title="CSS `position: sticky` for Single Axis Scroll Containers" data-default-tab="result" data-slug-hash="VYKQwmK" data-user="bramus" style="height: 760px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/bramus/pen/VYKQwmK"><br />
  CSS `position: sticky` for Single Axis Scroll Containers</a> by Bramus (<a href="https://codepen.io/bramus">@bramus</a>)<br />
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<p><script async src="https://public.codepenassets.com/embed/index.js"></script></p>
<p>If you view the demo in a supported browser, the first column will stick to the left edge of the table wrapper when scrolling horizontally, and the top headers will stick to the top of the viewport when scrolling vertically.</p>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3><a href="#an-important-detail-watch-your-overflow" name="an-important-detail-watch-your-overflow">#</a> An Important Detail: Watch your overflow</h3>
<p>If you look closely at the CSS in the demo, you might notice a very specific detail in how the <code>.table-wrapper</code> is styled.</p>
<pre><code class="language-css">.table-wrapper {
  overflow: auto clip;
}</code></pre>
<p>To make the wrapper scroll horizontally, you might instinctively reach for <code>overflow-x: scroll</code> (or <code>auto</code>). However, doing so will actually break the vertical stickiness we just achieved!</p>
<p>Why? Because in CSS, if you set <code>overflow-x</code> to <code>scroll</code> or <code>auto</code>, the browser automatically computes <code>overflow-y</code> to <code>auto</code> as well (assuming its value was <code>visible</code>). This turns the wrapper into a scroll container on the vertical axis too, which means it once again traps our headers and becomes their sticky reference.</p>
<p>To fix this, we need to explicitly tell the browser <em>not</em> to create a scroll container on the block axis. We do this by using the <code>clip</code> keyword:</p>
<pre><code class="language-css">.table-wrapper {
  /* ❌ This makes overflow-y compute to auto, breaking vertical stickiness */
  /* overflow-x: auto; */

  /* ✅ This creates a scroller on the inline axis, but clips the block axis */
  overflow: auto clip; 
}</code></pre>
<p>By setting <code>overflow-y</code> to <code>clip</code> (via the <code>overflow</code> shorthand), the wrapper does not become a scrollport on the vertical axis. The headers are therefore free to track the document viewport instead.</p>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3><a href="#browser-support" name="browser-support">#</a> Browser Support</h3>
<div class="note note--supergreen">
<p>💡 Although this post was originally published in March 2026, the section below is constantly being updated. <em>Last update: March 30, 2026</em>.</p>
</div>
<p>At the time of writing, this feature is only supported in Chrome 148 as an experimental feature.</p>
<dl>
<dt>Chromium <em>(Blink)</em></dt>
<dd>
<p>👨‍🔬 Available in Chromium 148.0.7742.0 with the experimental web platform features flag flipped on.</p>
</dd>
<dt>Firefox <em>(Gecko)</em></dt>
<dd>
<p>❌ No support</p>
<p>Subscribe to <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=2023702">Bug #2023702</a> for updates.</p>
</dd>
<dt>Safari <em>(WebKit)</em></dt>
<dd>
<p>❌ No support</p>
</dd>
</dl>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3><a href="#feature-detection" name="feature-detection">#</a> Feature Detection</h3>
<p>Because older browsers still use the inner container as the sticky reference for both axes, you might want to feature-detect this new behavior to provide JS-based fallbacks or conditionally load specific styles.</p>
<p>While we can&#8217;t easily detect this layout behavior directly with an <code>@supports</code> query in CSS yet — I have an <a href="https://github.com/w3c/csswg-drafts/issues/13677">open issue at the CSSWG for this</a> — you can feature detect this using JavaScript. The <a href="https://codepen.io/bramus/full/VYKQwmK">CodePen demo linked above</a> includes a script that does exactly this.</p>
<p>It handles the detection by evaluating how the browser resolves the sticky positioning offsets within a scrolling wrapper. Feel free to peek at the JS tab in the Pen to grab the snippet for your own projects.</p>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3><a href="#spread-the-word" name="spread-the-word">#</a> Spread the word</h3>
<p>Feel free to reshare one of the following posts on social media to help spread the word:</p>
<ul>
<li><a href="https://bsky.app/profile/bram.us/post/3mibo3clru22q">🦋 Bluesky</a></li>
<li><a href="https://front-end.social/@bramus/116318130490039355">🦣 Mastodon</a></li>
<p>  <!-- 

<li>💼 LinkedIn</li>

 -->
</ul>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<div class="note">
	<p><b>🔥 Like what you see? Want to stay in the loop? Here's how:</b></p>
	<ul>
            <li><a href="https://bsky.app/profile/bram.us">🦋 Follow @bram.us on Bluesky</a></li>
            <li><a href="https://bram.us/feed">🔸 Follow bram.us using RSS</a></li>
	</ul>
	<p>I can also be found on <a href="https://x.com/bramus">𝕏 Twitter</a> and <a href="https://front-end.social/@bramus">🐘 Mastodon</a> but only post there sporadically.</p>
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://www.bram.us/2026/03/30/css-sticky-per-axis/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		<enclosure url="https://www.bram.us/wordpress/wp-content/uploads/2026/03/sticky-per-axis-2.mp4" length="1589051" type="video/mp4" />

			</item>
		<item>
		<title>Cranking View Transtions up to 11 (2026.03.25 @ devs.gent)</title>
		<link>https://www.bram.us/2026/03/25/cranking-view-transtions-up-to-11-2026-03-25-devs-gent/</link>
					<comments>https://www.bram.us/2026/03/25/cranking-view-transtions-up-to-11-2026-03-25-devs-gent/#comments</comments>
		
		<dc:creator><![CDATA[Bramus!]]></dc:creator>
		<pubDate>Wed, 25 Mar 2026 16:30:48 +0000</pubDate>
				<category><![CDATA[Elsewhere]]></category>
		<category><![CDATA[public speaking]]></category>
		<category><![CDATA[view transitions]]></category>
		<guid isPermaLink="false">https://www.bram.us/?p=36235</guid>

					<description><![CDATA[Talk on View Transitions, given at the devs.gent March meetup]]></description>
										<content:encoded><![CDATA[<p>On Mar 25, 2026 I gave a talk at a <a href="https://www.meetup.com/devs-gent/events/312985269/">devs.gent Meetup</a>. The talk was called “Cranking View Transtions up to 11” and explored the more adventurous side of View Transitions.</p>
<blockquote><p>Ever wondered what happens when you push the View Transition API beyond its documented limits? This talk throws caution to the wind and explores the wild side of View Transitions, cranking them up to 11. We&#8217;ll combine them with Scroll-Driven Animations, trigger them automatically with MutationObserver, and even resurrect classic Internet Explorer’s Page Transitions using this modern API. Prepare for unconventional use cases, unexpected results, and a healthy dose of experimentation as we venture beyond the spec. If you&#8217;re a web developer who loves to tinker and push boundaries, this is the talk for you.</p></blockquote>
<p>I also gave this talk at Beyond Tellerrand 2026 about a month later. You can find the slides and a recording of the talk in the Beyond Tellerrand 2026 post.</p>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<div class="note"><p>💁‍♂️ If you are a conference or meetup organiser, don't hesitate to <a href="https://www.bram.us/speaking-training/">contact me to come speak at your event</a>.</p></div>
]]></content:encoded>
					
					<wfw:commentRss>https://www.bram.us/2026/03/25/cranking-view-transtions-up-to-11-2026-03-25-devs-gent/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>More Easy Light-Dark Mode Switching: light-dark() is about to support images!</title>
		<link>https://www.bram.us/2026/03/19/more-easy-light-dark-mode-switching-light-dark-is-about-to-support-images/</link>
					<comments>https://www.bram.us/2026/03/19/more-easy-light-dark-mode-switching-light-dark-is-about-to-support-images/#comments</comments>
		
		<dc:creator><![CDATA[Bramus!]]></dc:creator>
		<pubDate>Thu, 19 Mar 2026 11:24:41 +0000</pubDate>
				<category><![CDATA[Original Content]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[dark mode]]></category>
		<guid isPermaLink="false">https://www.bram.us/?p=36160</guid>

					<description><![CDATA[<p>CSS <strong><code>light-dark()</code> is being extended to support images.</strong></p>]]></description>
										<content:encoded><![CDATA[<!-- Added by Post/Page Specific Custom Code plugin, thank you for using! -->
<style>/* DL GRID @src https://codepen.io/bramus/pen/POEaXg*/
@media(min-width: 30em) {
dl {
	display: grid;
	grid-template: auto / 10em 1fr;
}

dt {
  grid-column: 1;
}

dd {
  grid-column: 2;
}

dt, dd {
	margin: 0;
	padding: .3em .5em;
	border-top: 1px solid rgba(0,0,0,.1);
}

:is(dt, dd) > :first-child {
  margin-top: 0;
}
:is(dt, dd) > :last-child {
  margin-bottom: 0;
}
}</style>
<p><img decoding="async" src="https://www.bram.us/wordpress/wp-content/uploads/2026/03/light-dark-image.png" alt="" width="560" height="264" class="alignnone size-medium wp-image-36172" /></p>
<div class=intro>
<p>Back in 2023, I wrote about <a href="https://www.bram.us/2023/10/09/the-future-of-css-easy-light-dark-mode-color-switching-with-light-dark/">the future of CSS color switching</a> using the then-novel <code>light-dark()</code> function. It was a game-changer for colors, allowing us to ditch the repetitive <code>@media (prefers-color-scheme: ...)</code> blocks for simple property declarations.</p>
<p>But there was one glaring limitation: it only works for colors. If you wanted to swap out a background image, a mask, or a logo based on the user&#8217;s color scheme, you were stuck doing things the &#8220;old&#8221; way.</p>
<p>Well, I have good news. The spec has been updated, and <strong><code>light-dark()</code> is being extended to support images.</strong></p>
</div>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3><a href="#missing-piece" name="missing-piece">#</a> The Missing Piece</h3>
<p>As a recap, in CSS, you have to write something like this if you want to set background-images for light and dark mode:</p>
<pre><code class="lang-css" style="tab-size: 2">:root {
  --bg-image: url(light-pattern.png);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg-image: url(dark-pattern.png);
  }
}

.element {
  background-image: var(--bg-image);
}</code></pre>
<p>This code has downsides: The necessary parts can be scattered all over the place and it only checks the global color-scheme preference without respecting local color-scheme overrides done with <a href="https://developer.mozilla.org/docs/Web/CSS/Reference/Properties/color-scheme">the <code>color-scheme</code> CSS property</a>.</p>
<p>Thanks CSS <a href="https://drafts.csswg.org/css-color-5/#light-dark"><code>light-dark()</code></a> we can keep our code closely together, and can respond to the local used <code>color-scheme</code> value. In it’s updated form – in which <code>light-dark()</code> now also supports <code>&lt;image&gt;</code> values – that whole block can be collapsed into a single rule, and we can make it respect local <code>color-scheme</code> overrides:</p>
<pre><code class="lang-css" style="tab-size: 2">.element {
  color-scheme: dark;
  background-image: light-dark(url(light-pattern.png), url(dark-pattern.png));
}</code></pre>
<p>Sweet!</p>
<div class=note>
<p>Note that you must pass in either two <code>&lt;color&gt;</code> values or two <code>&lt;image&gt;</code> values as arguments to <code>light-dark()</code> … you can’t mix the types — see <a href="#other-values">further down</a> for an explanation why that is.</p>
</div>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3><a href="#browser-support" name="browser-support">#</a> Browser Support</h3>
<div class="note note--supergreen">
<p>💡 Although this post was originally published in March 2026, the section below is constantly being updated. <em>Last update: May 26, 2026</em>.</p>
</div>
<p>It’s early days, but the engines are already moving:</p>
<dl>
<dt>Chromium <em>(Blink)</em></dt>
<dd>
<p>✅ Supported in Chrome 150. Chrome 150 is expected to go stable on Jun 17, 2026</p>
</dd>
<dt>Firefox <em>(Gecko)</em></dt>
<dd>
<p>✅ Supported in Firefox 150</p>
</dd>
<dt>Safari <em>(WebKit)</em></dt>
<dd>
<p>❌ No support</p>
<p>Subscribe to <a href="https://bugs.webkit.org/show_bug.cgi?id=309689">WebKit Bug #309689</a> for updates.</p>
</dd>
</dl>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3><a href="#feature-detection" name="feature-detection">#</a> Feature Detection</h3>
<p>If you want to start experimenting today while providing a fallback, you can use <code>@supports</code>. To detect support for images specifically, you can test it using <code>linear-gradient()</code> (which is treated as an <code>&lt;image&gt;</code> in CSS) or — another new addition — the keyword <code>none</code>:</p>
<pre><code class="lang-css" style="tab-size: 2">@supports (background-image: light-dark(none, none)) {
  /* Modern image-switching logic here */
}</code></pre>
<p>You can see the code in action this CodePen:</p>
<p class="codepen" data-height="590" data-pen-title="CSS light-dark(&amp;lt;image&amp;gt;, &amp;lt;image&amp;gt;) Support test" data-default-tab="result" data-slug-hash="bNwRmgm" data-user="bramus" style="height: 590px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>See the Pen <a href="https://codepen.io/bramus/pen/bNwRmgm">CSS light-dark(&lt;image&gt;, &lt;image&gt;) Support test</a> by Bramus (<a href="https://codepen.io/bramus">@bramus</a>) on <a href="https://codepen.io">CodePen</a>.</span></p>
<p><script async src="https://public.codepenassets.com/embed/index.js"></script></p>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3><a href="#other-values" name="other-values">#</a> What about non-<code>&lt;color&gt;</code> and non-<code>&lt;image&gt;</code> values?</h3>
<p>Supporting non-<code>&lt;color&gt;</code> and non-<code>&lt;image&gt;</code> values still is an unanswered question (that probably won’t get solved). As explained in my original coverage, CSS can’t just simply accept <code>light-dark()</code> anywhere because <a href="https://www.w3.org/TR/css-syntax-3/#parse-grammar">the parser needs to know the value type of what it is parsing</a> ahead of time.</p>
<p>Internally, <code>light-dark()</code> is <a href="https://github.com/w3c/csswg-drafts/issues/12513#issuecomment-4085662455">about to be defined</a> as having two internal variants – one that accepts <code>&lt;color&gt;</code>s and one that accepts <code>&lt;image&gt;</code>s:</p>
<pre><code>light-dark() =  &#x3C;light-dark-color&#x3E; | &#x3C;light-dark-image&#x3E;
&#x3C;light-dark-color&#x3E; = light-dark(&#x3C;color&#x3E;, &#x3C;color&#x3E;)
&#x3C;light-dark-image&#x3E; = light-dark(&#x3C;image&#x3E;, &#x3C;image&#x3E;)</code></pre>
<p>Each variant has limitations on where it can be used: the version that does colors is only accepted where <code>&lt;color&gt;</code>s are allowed, and the version that does images is only accepted where <code>&lt;image&gt;</code>s are allowed.</p>
<p><pre><code>&#x3C;color&#x3E; = &#x3C;color-base&#x3E; | currentColor | &#x3C;system-color&#x3E; | 
          &#x3C;contrast-color()&#x3E; | &#x3C;device-cmyk()&#x3E; | <b>&#x3C;light-dark-color&#x3E;</b></code></pre>
<pre><code>&#x3C;image&#x3E; = &#x3C;url&#x3E; | &#x3C;image()&#x3E; | &#x3C;image-set()&#x3E; | &#x3C;cross-fade()&#x3E; | 
          &#x3C;element()&#x3E; | &#x3C;gradient&#x3E; | <b>&#x3C;light-dark-image&#x3E;</b></code></pre>
<p>If more types of values needed to be supported, that would required many more internal variants.</p>
<p>This split into two variants allows the CSS parser to discard invalid declarations at <em>parse time</em>. That, in turn, explains why you can’t mix the types in the arguments. Say CSS were to accept a mix of <code>&lt;color&gt;</code> and <code>&lt;image&gt;</code>, then you would be able declare something like <code>background: red light-dark(blue, url(dark.png));</code>. With a dark <code>color-scheme</code> that declaration would end up being OK, but in a light <code>color-scheme</code> you’d end up with <code>background: red blue</code> which is invalid.</p>
<h4>Looking ahead: <code>@function</code> + <code>color-scheme()</code> to the rescue!</h4>
<p>In the future there will be an way to have non-<code>&lt;color&gt;</code> and non-<code>&lt;image&gt;</code> <code>color-scheme</code>-dependent values in the future: using a CSS Custom Function and <code>color-scheme()</code>. Go check out my previous post on <a href="https://www.bram.us/2025/09/30/css-custom-light-dark/">implementing a custom <code>--light-dark()</code> function that works with <em>any</em> type of value</a> for the details. It’s just 3 lines of code, which I’ve included here as well:</p>
<pre><code class="language-css" style="tab-size: 2">@function --light-dark(--l, --d) {
  result: if(color-scheme(dark): var(--d); else: var(--l));
}</code></pre>
<p>To feature detect support for <code>@function</code>, <a href="https://www.bram.us/2026/03/15/at-rule/">check out <code>@supports at-rule()</code></a>. There currently is no easy way to detect support for <code>color-scheme()</code>, unless <a href="https://codepen.io/bramus/pen/bNEWZLV/2f99187f31ee53f87a1b48bb083cb41e">you want to jump through some hoops</a>.</p>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3><a href="#spread-the-word" name="spread-the-word">#</a> Spread the word</h3>
<p>Feel free to reshare one of the following posts on social media to help spread the word:</p>
<ul>
<li><a href="https://bsky.app/profile/bram.us/post/3mhfy336xs22w">🦋 BlueSky</a></li>
<li><a href="https://front-end.social/@bramus/116255783077151930">🦣 Mastodon</a></li>
<li><a href="https://www.linkedin.com/feed/update/urn:li:activity:7440377308567322625/">💼 LinkedIn</a></li>
</ul>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<div class="note">
	<p><b>🔥 Like what you see? Want to stay in the loop? Here's how:</b></p>
	<ul>
            <li><a href="https://bsky.app/profile/bram.us">🦋 Follow @bram.us on Bluesky</a></li>
            <li><a href="https://bram.us/feed">🔸 Follow bram.us using RSS</a></li>
	</ul>
	<p>I can also be found on <a href="https://x.com/bramus">𝕏 Twitter</a> and <a href="https://front-end.social/@bramus">🐘 Mastodon</a> but only post there sporadically.</p>
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://www.bram.us/2026/03/19/more-easy-light-dark-mode-switching-light-dark-is-about-to-support-images/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>Detect at-rule support in CSS with @supports at-rule(@keyword)</title>
		<link>https://www.bram.us/2026/03/15/at-rule/</link>
					<comments>https://www.bram.us/2026/03/15/at-rule/#respond</comments>
		
		<dc:creator><![CDATA[Bramus!]]></dc:creator>
		<pubDate>Sun, 15 Mar 2026 19:51:22 +0000</pubDate>
				<category><![CDATA[Original Content]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[feature detection]]></category>
		<guid isPermaLink="false">https://www.bram.us/?p=36134</guid>

					<description><![CDATA[<p>Back in January 2022, I <a href="https://brm.us/at-rule-2022">wrote</a> about an exciting new CSS Working Group decision: a function to detect at-rule support using <code>@supports at-rule(@keyword)</code>. Fast forward to today, and the CSS Conditional Rules Module Level 5 specification has solidified how this feature works and Chromium (Chrome, Edge, etc.) is about to ship it in Chromium 148!</p>]]></description>
										<content:encoded><![CDATA[<!-- Added by Post/Page Specific Custom Code plugin, thank you for using! -->
<style>/* DL GRID @src https://codepen.io/bramus/pen/POEaXg*/
@media(min-width: 30em) {
dl {
	display: grid;
	grid-template: auto / 10em 1fr;
}

dt {
  grid-column: 1;
}

dd {
  grid-column: 2;
}

dt, dd {
	margin: 0;
	padding: .3em .5em;
	border-top: 1px solid rgba(0,0,0,.1);
}

:is(dt, dd) > :first-child {
  margin-top: 0;
}
:is(dt, dd) > :last-child {
  margin-bottom: 0;
}
}</style>
<p><img loading="lazy" decoding="async" src="https://www.bram.us/wordpress/wp-content/uploads/2026/03/at-rule.png" alt="" width="560" height="336" class="alignnone size-medium wp-image-36142" srcset="https://www.bram.us/wordpress/wp-content/uploads/2026/03/at-rule.png 2333w, https://www.bram.us/wordpress/wp-content/uploads/2026/03/at-rule-560x336.png 560w, https://www.bram.us/wordpress/wp-content/uploads/2026/03/at-rule-1120x672.png 1120w, https://www.bram.us/wordpress/wp-content/uploads/2026/03/at-rule-768x461.png 768w, https://www.bram.us/wordpress/wp-content/uploads/2026/03/at-rule-1536x922.png 1536w, https://www.bram.us/wordpress/wp-content/uploads/2026/03/at-rule-2048x1229.png 2048w, https://www.bram.us/wordpress/wp-content/uploads/2026/03/at-rule-1568x941.png 1568w" sizes="auto, (max-width: 560px) 100vw, 560px" /></p>
<div class="intro">
<p>Back in January 2022, I <a href="https://brm.us/at-rule-2022">wrote</a> about an exciting new CSS Working Group decision: a function to detect at-rule support using <code>@supports at-rule(@keyword)</code>. Fast forward to today, and the CSS Conditional Rules Module Level 5 specification has solidified how this feature works and Chromium (Chrome, Edge, etc.) is about to ship it in Chromium 148!</p>
</div>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3>The solution (Updated)</h3>
<p>The core idea remains the same as back in 2022: use the <code>at-rule()</code> function within an <code>@supports</code> query to check if a user agent recognizes a specific at-rule.</p>
<pre><code class="language-css">@supports at-rule(@starting-style) {
  /* CSS for browsers that support @starting-style here … */
}</code></pre>
<p>The function simply checks if the browser would accept an at-rule in any context. This is super useful for detecting entire new features like <code>@starting-style</code>.</p>
<p>You can also use <code>at-rule()</code> in the <em>supports-condition</em> when doing an <code>@import</code>:</p>
<pre><code class="language-css">/* This CSS only gets imported when @view-transition is supported (and the browser supports at-rule() as well) */
@import "view-transitions.css" supports(at-rule(@view-transition));</code></pre>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3>No descriptors, preludes, or full blocks!</h3>
<p>In my <a href="https://brm.us/at-rule-2022">original coverage of <code>at-rule()</code></a>, I mentioned an extension to the feature that would theoretically allow you to detect support for specific descriptors (e.g. <code>at-rule(@counter-style; system: fixed)</code>) or even pass in a full at-rule block.</p>
<p>This extension — along with checking for at-rule <em>preludes</em> (= the part between the at-rule and the opening <code>{</code>) — <a href="https://github.com/w3c/csswg-drafts/issues/6966#issuecomment-3205037703">has been dropped</a>. The following examples will all fail, as those are not supported:</p>
<pre><code class="language-css">/* ❌ Passing in descriptors is not supported */
@supports at-rule(@counter-style; system: fixed) { … }

/* ❌ Passing in full at-rules is not supported */
@supports at-rule(@counter-style { system: fixed }) { … }

/* ❌ Passing in preludes of at-rules is not supported */
@supports at-rule(@container style(…)) { … }</code></pre>
<p>Because preludes are not allowed in <code>at-rule()</code>, it cannot be used to detect non-size <code>@container</code> queries support <em>(e.g. style queries, anchored queries, etc.)</em>. To detect those, fall back to sniffing out support for the various values of <code>container-type</code> — a technique you can already use today in all browsers:</p>
<pre><code class="language-css">@supports (container-type: anchored) {
  /* CSS for browsers that support anchored queries here … */
}</code></pre>
<p>To feature detect style queries — which has no tell-tale property/value pair — you can use <a href="https://www.bram.us/2024/10/06/feature-detect-style-queries-support-in-css/">this workaround I previously documented</a>:</p>
<pre><code class="language-css" style="tab-size: 2">html {
  --sentinel: 1;
}

@container style(--sentinel: 1) {
  /* Style Queries Supported! */
}</code></pre>
<div class="note">
<p>Note that you also can’t pass <code>@charset</code> into <code>at-rule()</code>, as it technically is <a href="https://drafts.csswg.org/css-syntax/#charset-rule">not an at-rule</a>.</p>
</div>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3>Browser Support</h3>
<p><code>@supports at-rule(@keyword)</code> is available in Chromium 148. The initial implementation was done <a href="https://chromium-review.googlesource.com/5972643">by Google</a> and Kevin from Microsoft <a href="https://chromium-review.googlesource.com/7037634">pushed it over the finish line</a> while also navigation the paperwork with the CSS Working Group.</p>
<dl>
<dt>Chromium <em>(Blink)</em></dt>
<dd>
<p>✅ Supported in Chromium 148 and up.</p>
</dd>
<dt>Firefox <em>(Gecko)</em></dt>
<dd>
<p>❌ No support</p>
<p>Follow <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1751188">Issue #1751188</a> to stay up-to-date and signal interest.</p>
</dd>
<dt>Safari <em>(WebKit)</em></dt>
<dd>
<p>❌ No support</p>
<p>Subscribe to <a href="https://bugs.webkit.org/show_bug.cgi?id=235400">Issue #235400</a> to stay up-to-date and signal interest.</p>
</dd>
</dl>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3>Feature Detection</h3>
<p>You can feature detect support for <code>at-rule()</code> by checking for support for <code>@supports</code> itself.</p>
<pre><code class="language-css">@supports at-rule(@supports) {
  /* ✅ The browser supports `@supports at-rule(…)` */
}</code></pre>
<p>You can see it in action in the following demo:</p>
<p class="codepen" data-height="600" data-pen-title="CSS @supports at-rule() test" data-default-tab="result" data-slug-hash="LERWgbe" data-user="bramus" style="height: 600px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/bramus/pen/LERWgbe"><br />
  CSS @supports at-rule() test</a> by Bramus (<a href="https://codepen.io/bramus">@bramus</a>)<br />
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<p><script async src="https://public.codepenassets.com/embed/index.js"></script></p>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<div class="note">
	<p><b>🔥 Like what you see? Want to stay in the loop? Here's how:</b></p>
	<ul>
            <li><a href="https://bsky.app/profile/bram.us">🦋 Follow @bram.us on Bluesky</a></li>
            <li><a href="https://bram.us/feed">🔸 Follow bram.us using RSS</a></li>
	</ul>
	<p>I can also be found on <a href="https://x.com/bramus">𝕏 Twitter</a> and <a href="https://front-end.social/@bramus">🐘 Mastodon</a> but only post there sporadically.</p>
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://www.bram.us/2026/03/15/at-rule/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Introducing view-transitions-mock: A non-visual Polyfill for Same-Document View Transitions</title>
		<link>https://www.bram.us/2026/03/11/view-transitions-mock-is-a-non-visual-polyfill-for-same-document-view-transitions/</link>
					<comments>https://www.bram.us/2026/03/11/view-transitions-mock-is-a-non-visual-polyfill-for-same-document-view-transitions/#respond</comments>
		
		<dc:creator><![CDATA[Bramus!]]></dc:creator>
		<pubDate>Tue, 10 Mar 2026 23:33:07 +0000</pubDate>
				<category><![CDATA[Original Content]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[polyfill]]></category>
		<category><![CDATA[view transitions]]></category>
		<guid isPermaLink="false">https://www.bram.us/?p=36116</guid>

					<description><![CDATA[<p>View Transitions are a powerful Modern Web feature allow for smooth seamless animated transitions two between different states of a web page. They can make for a much more pleasant user experience, but as with any new web platform feature, browser support is not yet fully universal so you need to add some fallback logic to your code.</p><p><a href="https://github.com/GoogleChromeLabs/view-transitions-mock"><code>view-transitions-mock</code></a> bridges that gap. It is a spec-compliant JavaScript implementation of Same-Document View Transitions that polyfills the entire JavaScript API surface of the spec, but that doesn’t do the animation bits.</p>]]></description>
										<content:encoded><![CDATA[<figure><a href="https://www.bram.us/wordpress/wp-content/uploads/2026/03/view-transitions-mock.png"><img loading="lazy" decoding="async" src="https://www.bram.us/wordpress/wp-content/uploads/2026/03/view-transitions-mock.png" alt="" width="560" height="315" class="alignnone size-medium wp-image-36117" srcset="https://www.bram.us/wordpress/wp-content/uploads/2026/03/view-transitions-mock.png 1920w, https://www.bram.us/wordpress/wp-content/uploads/2026/03/view-transitions-mock-560x315.png 560w, https://www.bram.us/wordpress/wp-content/uploads/2026/03/view-transitions-mock-1120x630.png 1120w, https://www.bram.us/wordpress/wp-content/uploads/2026/03/view-transitions-mock-768x432.png 768w, https://www.bram.us/wordpress/wp-content/uploads/2026/03/view-transitions-mock-1536x864.png 1536w, https://www.bram.us/wordpress/wp-content/uploads/2026/03/view-transitions-mock-1568x882.png 1568w" sizes="auto, (max-width: 560px) 100vw, 560px" /></a><figcaption>Schematic of transitioning from State A to State B. The top row shows the steps of a native Same-Document View Transitions implementation. The bottom row shows how <code>view-transitions-mock</code> handles it.</figcaption></figure>
<div class=intro>
<p>View Transitions are a powerful Modern Web feature allow for smooth seamless animated transitions two between different states of a web page. They can make for a much more pleasant user experience, but as with any new web platform feature, browser support is not yet fully universal so you need to add some fallback logic to your code.</p>
<p><a href="https://github.com/GoogleChromeLabs/view-transitions-mock"><code>view-transitions-mock</code></a> bridges that gap. It is a spec-compliant JavaScript implementation of Same-Document View Transitions that polyfills the entire JavaScript API surface of the spec, but that doesn’t do the animation bits.</p>
</div>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3>The Challenge: Writing Future-Proof Code</h3>
<p>When using new APIs like the View Transition API, you need to write defensive code that check for browser support:</p>
<pre><code class="language-js">if (!document.startViewTransition) {
  updateTheDOM();
} else {
  document.startViewTransition(updateTheDOM);
}</code></pre>
<p>This clutters your codebase with <code>if (document.startViewTransition)</code> guards which makes it harder to maintain. And that list of safeguards quickly grows, for example when you throw View Transition Types in the mix.</p>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3>The Solution: <code>view-transitions-mock</code></h3>
<p>Ideally, you want to write code that will work seamlessly in all browsers, regardless of whether they support Same-Document View Transitions natively or not. The bridge that gap I created <a href="https://github.com/GoogleChromeLabs/view-transitions-mock"><code>view-transitions-mock</code>, a non-visual polyfill that mocks the entire JavaScript API surface of Same-Document View Transitions</a>, including:</p>
<ul>
<li><code>document.startViewTransition()</code></li>
<li>The <code>ViewTransition</code> class, including:
<ul>
<li>The <code>updateCallbackDone</code>, <code>ready</code>, and <code>finished</code> promises.</li>
<li>The <code>ViewTransition.transitionRoot</code> property.</li>
</ul>
</li>
<li><code>document.activeViewTransition</code></li>
<li>View Transition Types</li>
</ul>
<p>What it <strong>doesn&#8217;t</strong> polyfill are the visual aspects: the pseudo-tree, the CSS properties, and the animations themselves. This means that while you won&#8217;t see any animations in browsers without native support, yet your code will still execute without errors, and the DOM will be updated as expected.</p>
<div class="note note--info">
<p>ℹ️ <code>view-transitions-mock</code> is different from <a href="https://developer.chrome.com/docs/web-platform/view-transitions/same-document#not-a-polyfill">earlier attempts to solve this problem</a>.</p>
<p>Where earlier attempts just give you back an object with pre-resolved promises, <b><code>view-transitions-mock</code> is very different because it is an actual spec-compliant JavaScript implementation of <a href="https://drafts.csswg.org/css-view-transitions-1/">the css-view-transitions-1 specification</a> (with some additions from <a href="https://drafts.csswg.org/css-view-transitions-2/">css-view-transitions-2</a>, such as View Transition Types).</b></p>
<p>This means that the handling and manipulation of the <code>ViewTransition</code> promises or the View Transition Types behave exactly the same as a native implementation in browsers.</p>
</div>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3>Getting Started</h3>
<p>Using <code>view-transitions-mock</code> is incredibly simple. First, install it from npm:</p>
<pre><code>npm i view-transitions-mock</code></pre>
<p>Then, import and register it in your code (before calling <code>document.startViewTransition</code>):</p>
<pre><code class="language-js">import { register } from "view-transitions-mock";
register();</code></pre>
<p>That&#8217;s it! You can now use the View Transition API without any worries.</p>
<pre><code class="language-js">import { register } from "view-transitions-mock";
register();

document.startViewTransition(() => {
  // …
});</code></pre>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3>Tweaking the registration</h3>
<p>By default, the registration of <code>view-transitions-mock</code> checks whether <code>document.startViewTransition</code> and View Transition Types are supported or not. When both are natively supported, it won’t register the mock.</p>
<p>You can tweak the registration by passing an object with the following properties into the <code>register</code> function:</p>
<ul>
<li><code>requireTypes</code> <em>(Boolean, default value: <code>true</code>)</em>: Require support for View Transition Types</li>
<li><code>forced</code> <em>(Boolean, default value: false)</em>: Force register the mock, regardless of support.</li>
</ul>
<p>For example, if you are not relying on View Transition Types, call <code>register</code> as follows so that it does not register the mock in Firefox 144–146 (which does not have support for View Transition Types):</p>
<pre><code class="language-js">import { register } from "view-transitions-mock";
register({ requireTypes: false });</code></pre>
<p>Or if you want to disable native support for Same-Document View Transitions entirely – handy if you want to test how your site looks without View Transitions – call <code>register</code> as follows:</p>
<pre><code class="language-js">import { register } from "view-transitions-mock";
register({ forced: true });</code></pre>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3>See it in Action</h3>
<p>Over at <a href="https://chrome.dev/view-transitions-mock">https://chrome.dev/view-transitions-mock</a> you can find a live demo of <code>view-transitions-mock</code>. Every time you click the area around the box a View Transition gets started and output is added to the log.</p>
<figure><a href="https://www.bram.us/wordpress/wp-content/uploads/2026/03/view-transitions-mock-demo.png"><img loading="lazy" decoding="async" src="https://www.bram.us/wordpress/wp-content/uploads/2026/03/view-transitions-mock-demo.png" alt="" width="560" height="364" class="alignnone size-medium wp-image-36124" /></a><figcaption>Screenshot of <a href="https://chrome.dev/view-transitions-mock">https://chrome.dev/view-transitions-mock</a> in Chrome</figcaption></figure>
<p>By default won’t see much happening in the demo, because the thing that you should be seeing is that it doesn’t crash in browsers with no support for Same-Document View Transitions. You can use one of the buttons on the page to do a forced registration of <code>view-transitions-mock</code>. After having done so, you’ll see stuff get added to the log, but no visual transition happening. This is the <code>view-transitions-mock</code> in action!</p>
<p>The source code for the demo is also available in <a href="https://github.com/GoogleChromeLabs/view-transitions-mock">the repository</a>.</p>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<p>To learn more about <code>view-transitions-mock</code>, check out the following links if you haven’t done so already:</p>
<ul>
<li><a href="https://github.com/GoogleChromeLabs/view-transitions-mock"><code>view-transitions-mock</code> Source (GitHub) &rarr;</a></li>
<li><a href="https://chrome.dev/view-transitions-mock"><code>view-transitions-mock</code> Demo &rarr;</a></li>
<li><a href="https://www.npmjs.com/package/view-transitions-mock"><code>view-transitions-mock</code> on NPMJS &rarr;</a></li>
<li><a href="https://npmx.dev/package/view-transitions-mock"><code>view-transitions-mock</code> on npmx &rarr;</a></li>
</ul>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<div class="note">
	<p><b>🔥 Like what you see? Want to stay in the loop? Here's how:</b></p>
	<ul>
            <li><a href="https://bsky.app/profile/bram.us">🦋 Follow @bram.us on Bluesky</a></li>
            <li><a href="https://bram.us/feed">🔸 Follow bram.us using RSS</a></li>
	</ul>
	<p>I can also be found on <a href="https://x.com/bramus">𝕏 Twitter</a> and <a href="https://front-end.social/@bramus">🐘 Mastodon</a> but only post there sporadically.</p>
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://www.bram.us/2026/03/11/view-transitions-mock-is-a-non-visual-polyfill-for-same-document-view-transitions/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Experiment: Animating CSS position-area with View Transitions</title>
		<link>https://www.bram.us/2026/03/02/animating-css-position-area-with-view-transitions/</link>
					<comments>https://www.bram.us/2026/03/02/animating-css-position-area-with-view-transitions/#respond</comments>
		
		<dc:creator><![CDATA[Bramus!]]></dc:creator>
		<pubDate>Mon, 02 Mar 2026 22:19:20 +0000</pubDate>
				<category><![CDATA[Original Content]]></category>
		<category><![CDATA[anchoring]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[view transitions]]></category>
		<guid isPermaLink="false">https://www.bram.us/?p=36083</guid>

					<description><![CDATA[<p>CSS Anchor Positioning is a powerful tool, but one of the things that you cannot do natively <em>(yet)</em> is animating the <code>position-area</code> property. This blog post introduces a technique to animate <code>position-area</code> changes using View Transitions.</p>]]></description>
										<content:encoded><![CDATA[<figure><div style="width: 640px;" class="wp-video"><video class="wp-video-shortcode" id="video-36083-2" width="640" height="432" loop autoplay muted preload="metadata" controls="controls"><source type="video/mp4" src="https://www.bram.us/wordpress/wp-content/uploads/2026/03/animate-position-area.mp4?_=2" /><a href="https://www.bram.us/wordpress/wp-content/uploads/2026/03/animate-position-area.mp4">https://www.bram.us/wordpress/wp-content/uploads/2026/03/animate-position-area.mp4</a></video></div><figcaption>Recording of <a href="https://codepen.io/bramus/full/myrJYQz/b6aace5cc714e3147f81a2a1b4ed10f4">the demo</a>.</figcaption></figure>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<div class=intro>
<p>CSS Anchor Positioning is a powerful tool, but one of the things that you cannot do natively <em>(yet)</em> is animating the <code>position-area</code> property. This blog post introduces a technique to animate <code>position-area</code> changes using View Transitions.</p>
</div>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<div class="note">
<p>🌟 This post is about CSS Anchor Positioning. If you are not familiar with the basics of it, check out <a href="https://www.bram.us/2026/02/28/anchors-aweigh-sotb2026/">this 30-min talk of mine</a> to get up to speed.</p>
</div>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3>The problem</h3>
<p>When the browser chooses one of the <code>position-try-fallbacks</code> to apply to an anchored element, the <code>position-area</code>’s <a href="https://brm.us/value-processing">Computed Value</a> changes to that chosen fallback. This change is abrupt and in response the anchored element simply jumps from the old <code>position-area</code> to the new <code>position-area</code>. This change can’t be animated using CSS because <code>position-area</code> is discretely animatable <em>(or rather: it’s currently defined as “TBD”, see <a href="https://github.com/w3c/csswg-drafts/issues/13577" target="_top">w3c/csswg-drafts#13577</a>)</em>, so the value just flips midway the transition.</p>
<pre><code class="language-css" style="tab-size: 2;">.tooltip {
	position: fixed;
	position-area: block-start;
	position-try-fallbacks: flip-block;
	transition: position-area 0.2s ease; /* This doesn’t do anything visually … */
}</code></pre>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3>The technique</h3>
<p>Back in 2024 I explored <a href="https://www.bram.us/2024/11/25/experiment-automatically-triggered-view-transitions-with-mutationobserver/#shortcomings">a technique to automatically trigger a View Transition whenever a CSS property changes</a>. For this I relied on <a href="https://brm.us/style-observer"><code>@bramus/style-observer</code></a> which is a <code>StyleObserver</code> that allows you to respond to Computed Value changes in JavaScript. In the <code>StyleObserver</code>’s callback, I reset the targeted element to the previously recorded value, and then start a View Transition to the new value of that property.</p>
<p>For Anchor Positioning specifically, it’s sufficient to monitor the <code>position-area</code> property, as its Computed Value changes whenever a new <code>position-try-fallback</code> gets applied. To make sure that the View Transition is started from the previously recorded <code>position-area</code>, the <code>position-try</code> must be unset temporarily at the start of the View Transition. Therefore, that property also needs to be monitored.</p>
<pre><code class="language-js" style="tab-size: 2;">import StyleObserver, { ReturnFormat, NotificationMode } from "@bramus/style-observer";

const $element = document.querySelector('.tooltip');

let isBusy = false;
const styleObserver = new StyleObserver((mutations) => {
	// Prevent double runs
	if (isBusy) return;
	isBusy = true;

	const positionArea = mutations['position-area'];
		
	// No change, do nothing
	if (!positionArea.previousValue || !positionArea.changed) {
		isBusy = false;
		return;
	}
		
	// Move the element back its old location
	// This is done by forcing the old recorded positionArea
	// but most importantly by also unsetting the `position-try`
	$element.style.positionTry = 'none';
	$element.style.positionArea = positionArea.previousValue;

	// Restore the new positions
	const t = document.startViewTransition(() => {
		$element.style.positionTry = '';
		$element.style.positionArea = '';
	});

	isBusy = false;
},
{
	properties: ['position-area', 'position-anchor', 'position-try'],
	returnFormat: ReturnFormat.OBJECT,
	notificationMode: NotificationMode.ALL,
});

styleObserver.observe($element);</code></pre>
<p>Here is <a href="https://codepen.io/bramus/full/myrJYQz/b6aace5cc714e3147f81a2a1b4ed10f4">a live demo</a> that is using this technique:</p>
<p class="codepen" data-height="600" data-pen-title="CSS Anchor Positioning: Animating `position-area` with View Transitions, powered by @bramus/style-observer" data-default-tab="result" data-slug-hash="myrJYQz" data-user="bramus" data-token="b6aace5cc714e3147f81a2a1b4ed10f4" style="height: 600px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>See the Pen <a href="https://codepen.io/bramus/pen/myrJYQz/b6aace5cc714e3147f81a2a1b4ed10f4">CSS Anchor Positioning: Animating `position-area` with View Transitions, powered by @bramus/style-observer </a> by Bramus (<a href="https://codepen.io/bramus">@bramus</a>) on <a href="https://codepen.io">CodePen</a>.</span></p>
<p><script async src="https://public.codepenassets.com/embed/index.js"></script></p>
<p>Scroll the page up and down to trigger a different <code>position-area</code> on the positioned element.</p>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3>Known Issues</h3>
<p>Unfortunately there are some issues with the technique:</p>
<ol>
<li>
<p>The code does not work in Firefox.</p>
<p>I initially thought this was because of <code>position-area</code> being underspecified in the spec – it’s animation type is currently specced as “TBD” – and that Firefox therefore did not mark the property as being <a href="https://www.w3.org/TR/web-animations/#discrete">Discretely Animatable</a>. A look at Stylo’s source code tells me <a href="https://github.com/servo/stylo/blob/2f131eb1909c6193a7cfae22e49f288fe9df4fbb/style/properties/longhands.toml#L1736-L1746">it <em>is</em> defined as a discretely animatable property</a> so so that’s not the problem.</p>
<p>Digging into the code of my <code>StyleObserver</code>, I see it only picks up the initially applied value of <code>position-area</code> but no subsequent changes, even though a <code>getComputedStyle()</code> indicates that the value did change. I think there is a bug on Firefox’s end in which it does not trigger a transition when the value changes as the result of a <code>position-try-fallback</code> being chosen.</p>
<p>I have filed <a href="https://github.com/w3c/csswg-drafts/issues/13577">w3c/csswg-drafts#13577</a> at the CSSWG to fix the spec, and <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=2020592">https://bugzilla.mozilla.org/show_bug.cgi?id=2020592</a> with Firefox to get the bug sorted on their end.</p>
</li>
<li>
<p>Chrome is affected by <a href="https://www.bram.us/2024/11/25/experiment-automatically-triggered-view-transitions-with-mutationobserver/#one-frame-glitch" target="_top">a 1-frame glitch</a> due to the ambiguously defined timing of <code>transitionrun</code>. <a href="https://github.com/w3c/csswg-drafts/issues/11665" target="_top">w3c/csswg-drafts#11665</a> is concerned with this.</p>
</li>
<li>
<p>In Safari, the <code>transitionrun</code> that tracks <code>position-area</code> keeps firing over and over once it has detected a change. This is fixed in Safari Technology Preview.</p>
</li>
<li>
<p>While the View Transition is running, the positioned and anchored elements can feel a bit out-of-sync. This is because of how View Transitions deal with scroll: during a scroll, VTs retarget the end <code>transform</code> of the <code>::view-transition-group()</code> pseudos, which makes them be subjected to the <code>animation-duration</code> instead of changing instantly. In <a href="https://github.com/w3c/csswg-drafts/issues/10197" target="_top">w3c/csswg-drafts#10197</a> I am throwing around ideas to get this fixed.</p>
</li>
</ol>
<p>All these issues are fixable over time, but I would say that the 1-frame glitch in Chrome is preventing this from being something that is really usable right now.</p>
<p>Also note that the anchor in the demo does not change aspect ratio when a new <code>position-area</code> gets set. If yours does, you’ll need <a href="https://jakearchibald.com/2024/view-transitions-handling-aspect-ratio-changes/">this code by Jake</a>.</p>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<div class="note">
	<p><b>🔥 Like what you see? Want to stay in the loop? Here's how:</b></p>
	<ul>
            <li><a href="https://bsky.app/profile/bram.us">🦋 Follow @bram.us on Bluesky</a></li>
            <li><a href="https://bram.us/feed">🔸 Follow bram.us using RSS</a></li>
	</ul>
	<p>I can also be found on <a href="https://x.com/bramus">𝕏 Twitter</a> and <a href="https://front-end.social/@bramus">🐘 Mastodon</a> but only post there sporadically.</p>
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://www.bram.us/2026/03/02/animating-css-position-area-with-view-transitions/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		<enclosure url="https://www.bram.us/wordpress/wp-content/uploads/2026/03/animate-position-area.mp4" length="204323" type="video/mp4" />

			</item>
		<item>
		<title>Anchors Aweigh! (2026.02.28 @ State of the Browser)</title>
		<link>https://www.bram.us/2026/02/28/anchors-aweigh-sotb2026/</link>
					<comments>https://www.bram.us/2026/02/28/anchors-aweigh-sotb2026/#respond</comments>
		
		<dc:creator><![CDATA[Bramus!]]></dc:creator>
		<pubDate>Sat, 28 Feb 2026 14:57:36 +0000</pubDate>
				<category><![CDATA[Original Content]]></category>
		<category><![CDATA[anchoring]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[public speaking]]></category>
		<guid isPermaLink="false">https://www.bram.us/?p=36067</guid>

					<description><![CDATA[Talk on CSS Anchor Positioning]]></description>
										<content:encoded><![CDATA[<p><figure><img loading="lazy" decoding="async" src="https://www.bram.us/wordpress/wp-content/uploads/2026/02/bafkreigab3ac3nblbqfqxtwbh5stbpbsszke2aqbugjtr4ckvyzqdbwruy.jpg" alt="" width="560" height="420" class="alignnone size-medium wp-image-36068" srcset="https://www.bram.us/wordpress/wp-content/uploads/2026/02/bafkreigab3ac3nblbqfqxtwbh5stbpbsszke2aqbugjtr4ckvyzqdbwruy.jpg 2000w, https://www.bram.us/wordpress/wp-content/uploads/2026/02/bafkreigab3ac3nblbqfqxtwbh5stbpbsszke2aqbugjtr4ckvyzqdbwruy-560x420.jpg 560w, https://www.bram.us/wordpress/wp-content/uploads/2026/02/bafkreigab3ac3nblbqfqxtwbh5stbpbsszke2aqbugjtr4ckvyzqdbwruy-1120x841.jpg 1120w, https://www.bram.us/wordpress/wp-content/uploads/2026/02/bafkreigab3ac3nblbqfqxtwbh5stbpbsszke2aqbugjtr4ckvyzqdbwruy-768x576.jpg 768w, https://www.bram.us/wordpress/wp-content/uploads/2026/02/bafkreigab3ac3nblbqfqxtwbh5stbpbsszke2aqbugjtr4ckvyzqdbwruy-1536x1153.jpg 1536w, https://www.bram.us/wordpress/wp-content/uploads/2026/02/bafkreigab3ac3nblbqfqxtwbh5stbpbsszke2aqbugjtr4ckvyzqdbwruy-1568x1177.jpg 1568w" sizes="auto, (max-width: 560px) 100vw, 560px" /><figcaption>Me, on stage. Photo by <a href="https://bsky.app/profile/joshtumath.uk/post/3mfvz23jgzc2f">Josh</a></figcaption></figure>
</p>
<div class="intro">
<p>I’m currently in London, attending the wonderful <a href="https://2026.stateofthebrowser.com/">State of the Browser</a> conference. Earlier today I opened the conference with a talk about CSS Anchor Positioning.</p>
</div>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3>Table of Contents</h3>
<ul>
<li><a href="#the-talk">The Talk</a></li>
<li><a href="#slides">Slides</a></li>
<li><a href="#recording">Recording/Video</a></li>
<li><a href="#thanks">Thanks!</a></li>
</ul>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3><a href="#the-talk" name="the-talk">#</a> The Talk</h3>
<p>This talk was a brand new talk <em>(which I had rehearsed at PHPAntwerp just 2 days before)</em> about CSS Anchor Positioning. It’s a high-energy, fast-paced talk that packs a lot of information in ±30 minutes.</p>
<blockquote><p>We&#8217;ve all been there. You need a popover to attach to a button, but they aren&#8217;t parent/child. You end up reaching for JavaScript, calculating coordinates, and wrestling with viewport edges. CSS Anchor Positioning is a recent API designed to solve exactly this. This talk is a practical, down-to-earth look at how it works. We&#8217;ll explore the <code>anchor()</code> function, the <code>position-anchor</code> and <code>position-area</code> properties, and <code>@position-try</code> to build UIs that are truly context-aware and robust, all without the JS hacks.</p></blockquote>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3><a href="#slides" name="slides">#</a> Slides</h3>
<p>The slides of my talk are <a href="https://slidr.io/bramus/anchors-aweigh-2026-02-28-state-of-the-browser">up on slidr.io</a> are embedded below:</p>
<p><iframe loading="lazy" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" allowtransparency="true" frameborder="0" id="slidrio-deck-3665" src="https://slidr.io/bramus/anchors-aweigh-2026-02-28-state-of-the-browser?embed=true" style="border:0; padding:0; margin:0; background:transparent;" width="560" height="490"><a href="https://slidr.io/bramus/anchors-aweigh-2026-02-28-state-of-the-browser" title="Anchors aweigh! (2026.02.28 @ State of the Browser)">Check out the slides</a></iframe></p>
<p>Unfortunately these exported slides don’t contain any of the slide transitions that supported the story I was bringing. Also missing are any recordings of the included demos (they’re just screenshots in the export), but you can click the links to check them out yourself.</p>
<p>All the Anchoring demos that I built are gathered in <a href="https://codepen.io/collection/gPRabN?grid_type=LIST">this CodePen Collection</a>.</p>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3><a href="#slides" name="recording">#</a> Recording/Video</h3>
<p>The talk was recorded and is embedded below:</p>
<p><iframe loading="lazy" width="560" height="315" src="https://www.youtube.com/embed/VD_q3x9GnZs" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe></p>
<p>The recording comes wiht subtitles and you can <a href="https://2026.stateofthebrowser.com/speaker/bramus-van-damme/">find a transcript on the SotB site itself</a>.</p>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<h3><a href="#thanks" name="thanks">#</a> Thanks!</h3>
<p>Thanks to Web Standards London for organizing State of the Browser, with a special shout-out to <a href="https://letorey.co.uk/">Dave</a> <em>(MORE ACID!)</em>. Similar to previous years, they’ve managed to curate a nice line-up of talks, with the right balance between practical/inspirational, known/new speakers, etc – It was an honor to get to open the conference with this technical deep dive.</p>
<p><figure><img loading="lazy" decoding="async" src="https://www.bram.us/wordpress/wp-content/uploads/2026/02/bafkreidbk6v5abchg74wlx64vdjb2cf4gnzeeuenjfuwebqe6luguck3te-560x420.jpg" alt="" width="560" height="420" class="alignnone size-medium wp-image-36072" srcset="https://www.bram.us/wordpress/wp-content/uploads/2026/02/bafkreidbk6v5abchg74wlx64vdjb2cf4gnzeeuenjfuwebqe6luguck3te-560x420.jpg 560w, https://www.bram.us/wordpress/wp-content/uploads/2026/02/bafkreidbk6v5abchg74wlx64vdjb2cf4gnzeeuenjfuwebqe6luguck3te-1120x840.jpg 1120w, https://www.bram.us/wordpress/wp-content/uploads/2026/02/bafkreidbk6v5abchg74wlx64vdjb2cf4gnzeeuenjfuwebqe6luguck3te-768x576.jpg 768w, https://www.bram.us/wordpress/wp-content/uploads/2026/02/bafkreidbk6v5abchg74wlx64vdjb2cf4gnzeeuenjfuwebqe6luguck3te-1536x1152.jpg 1536w, https://www.bram.us/wordpress/wp-content/uploads/2026/02/bafkreidbk6v5abchg74wlx64vdjb2cf4gnzeeuenjfuwebqe6luguck3te-1568x1176.jpg 1568w, https://www.bram.us/wordpress/wp-content/uploads/2026/02/bafkreidbk6v5abchg74wlx64vdjb2cf4gnzeeuenjfuwebqe6luguck3te.jpg 2000w" sizes="auto, (max-width: 560px) 100vw, 560px" /><figcaption>Me, on stage with a slide about the <a href="https://brm.us/imcb">IMCB</a>. Photo by <a href="https://bsky.app/profile/r8r.at/post/3mfw2eie7fs25">Mario</a></figcaption></figure>
</p>
<p>It was wonderful catching up with familiar faces <em>(Jake! Cassie! Jeremy! Zach! Keith! Luke! Ryan! Josh! PPK! Niels! Alex! Estelle! Sophie! Sara! Manuel! …)</em> and finally meeting those of you I’ve only known online until now <em>(Oliver! Pavel! James! Josh! …)</em>!</p>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<div class="note"><p>💁‍♂️ If you are a conference or meetup organiser, don't hesitate to <a href="https://www.bram.us/speaking-training/">contact me to come speak at your event</a>.</p></div>
]]></content:encoded>
					
					<wfw:commentRss>https://www.bram.us/2026/02/28/anchors-aweigh-sotb2026/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Anchors Aweigh! (2026.02.26 @ PHP Antwerp)</title>
		<link>https://www.bram.us/2026/02/26/anchors-aweigh/</link>
					<comments>https://www.bram.us/2026/02/26/anchors-aweigh/#respond</comments>
		
		<dc:creator><![CDATA[Bramus!]]></dc:creator>
		<pubDate>Thu, 26 Feb 2026 19:12:54 +0000</pubDate>
				<category><![CDATA[Original Content]]></category>
		<category><![CDATA[anchoring]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[public speaking]]></category>
		<guid isPermaLink="false">https://www.bram.us/?p=36230</guid>

					<description><![CDATA[Talk on CSS Anchor Positioning.]]></description>
										<content:encoded><![CDATA[<p>On Feb 26, 2026 I gave a talk at a <a href="https://www.meetup.com/php-antwerp/events/312755491/">PHP Antwerp Meetup</a>. The talk was called “Anchors Aweigh!” and covered CSS Anchor Positioning.</p>
<blockquote><p>We&#8217;ve all been there. You need a popover to attach to a button, but they aren&#8217;t parent/child. You end up reaching for JavaScript, calculating coordinates, and wrestling with viewport edges. CSS Anchor Positioning is a recent API designed to solve exactly this. This talk is a practical, down-to-earth look at how it works. We&#8217;ll explore the <code>anchor()</code> function, the <code>position-anchor</code> and <code>position-area</code> properties, and <code>@position-fallback</code> to build UIs that are truly context-aware and robust, all without the JS hacks.</p></blockquote>
<p>I also <a href="https://www.bram.us/2026/02/28/anchors-aweigh-sotb2026/">gave this talk at State of the Browser 2026</a>, just a few days later. You can <a href="https://www.bram.us/2026/02/28/anchors-aweigh-sotb2026/">find the slides and a recording of the talk in the State of the Browser post</a>.</p>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<div class="note"><p>💁‍♂️ If you are a conference or meetup organiser, don't hesitate to <a href="https://www.bram.us/speaking-training/">contact me to come speak at your event</a>.</p></div>
]]></content:encoded>
					
					<wfw:commentRss>https://www.bram.us/2026/02/26/anchors-aweigh/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Chrome 145 146 adds Experimental Support for Vertical Tabs</title>
		<link>https://www.bram.us/2026/01/16/chrome-145-adds-experimental-support-for-vertical-tabs/</link>
					<comments>https://www.bram.us/2026/01/16/chrome-145-adds-experimental-support-for-vertical-tabs/#respond</comments>
		
		<dc:creator><![CDATA[Bramus!]]></dc:creator>
		<pubDate>Fri, 16 Jan 2026 09:56:23 +0000</pubDate>
				<category><![CDATA[Original Content]]></category>
		<category><![CDATA[google chrome]]></category>
		<guid isPermaLink="false">https://www.bram.us/?p=36042</guid>

					<description><![CDATA[Vertical Tabs are available behind a flag in Chrome 145 (current beta)]]></description>
										<content:encoded><![CDATA[<div>
<figure><a href="https://www.bram.us/wordpress/wp-content/uploads/2026/01/chrome-vertical-tabs-3.png"><img loading="lazy" decoding="async" src="https://www.bram.us/wordpress/wp-content/uploads/2026/01/chrome-vertical-tabs-3.png" alt="" width="560" height="338" class="alignnone size-medium wp-image-36045" /></a><figcaption>Chrome 145 with the tabs shown to the side (vertical tabs) and a collapsed tab bar</figcaption></figure>
</div>
<div class=intro>
<p>Vertical Tabs are available behind a flag in Chrome 145 (current Chrome Beta)</p>
</div>
<div class=update>UPDATE: The flag for this experimental feature moved up to Chrome 146 (current Chrome Canary).</div>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<p>The feature is available in preview behind a feature flag. First you need to flip the flag <em>(to enable the feature)</em> and then choose the vertical tabs layout <em>(to apply the feature)</em>:</p>
<ol>
<li>Navigate to <code>chrome://flags/#vertical-tabs</code></li>
<li>Set the dropdown to enabled</li>
<li>Relaunch Chrome</li>
<li>Right click the tabbar and choose “Move Tabs To The Side”</li>
</ul>
<p>Here’s screenshots:</p>
<div class="table">
<div>
<figure><a href="https://www.bram.us/wordpress/wp-content/uploads/2026/01/chrome-vertical-tabs-1.jpg"><img loading="lazy" decoding="async" src="https://www.bram.us/wordpress/wp-content/uploads/2026/01/chrome-vertical-tabs-1.jpg" alt="" width="560" height="338" class="alignnone size-medium wp-image-36043" srcset="https://www.bram.us/wordpress/wp-content/uploads/2026/01/chrome-vertical-tabs-1.jpg 2380w, https://www.bram.us/wordpress/wp-content/uploads/2026/01/chrome-vertical-tabs-1-560x338.jpg 560w, https://www.bram.us/wordpress/wp-content/uploads/2026/01/chrome-vertical-tabs-1-1120x676.jpg 1120w, https://www.bram.us/wordpress/wp-content/uploads/2026/01/chrome-vertical-tabs-1-768x463.jpg 768w, https://www.bram.us/wordpress/wp-content/uploads/2026/01/chrome-vertical-tabs-1-1536x927.jpg 1536w, https://www.bram.us/wordpress/wp-content/uploads/2026/01/chrome-vertical-tabs-1-2048x1236.jpg 2048w, https://www.bram.us/wordpress/wp-content/uploads/2026/01/chrome-vertical-tabs-1-1568x946.jpg 1568w" sizes="auto, (max-width: 560px) 100vw, 560px" /></a><figcaption>Chrome 145 with the flag enabled and showing the context menu to move the tabs to the side</figcaption></figure>
</div>
<div>
<figure><a href="https://www.bram.us/wordpress/wp-content/uploads/2026/01/chrome-vertical-tabs-2.jpg"><img loading="lazy" decoding="async" src="https://www.bram.us/wordpress/wp-content/uploads/2026/01/chrome-vertical-tabs-2.jpg" alt="" width="560" height="338" class="alignnone size-medium wp-image-36044" srcset="https://www.bram.us/wordpress/wp-content/uploads/2026/01/chrome-vertical-tabs-2.jpg 2380w, https://www.bram.us/wordpress/wp-content/uploads/2026/01/chrome-vertical-tabs-2-560x338.jpg 560w, https://www.bram.us/wordpress/wp-content/uploads/2026/01/chrome-vertical-tabs-2-1120x676.jpg 1120w, https://www.bram.us/wordpress/wp-content/uploads/2026/01/chrome-vertical-tabs-2-768x463.jpg 768w, https://www.bram.us/wordpress/wp-content/uploads/2026/01/chrome-vertical-tabs-2-1536x927.jpg 1536w, https://www.bram.us/wordpress/wp-content/uploads/2026/01/chrome-vertical-tabs-2-2048x1236.jpg 2048w, https://www.bram.us/wordpress/wp-content/uploads/2026/01/chrome-vertical-tabs-2-1568x946.jpg 1568w" sizes="auto, (max-width: 560px) 100vw, 560px" /></a><figcaption>Chrome 145 with the tabs shown to the side (vertical tabs)</figcaption></figure>
</div>
</div>
<p>If you want, you can collapse the tabs bar to a minimal form, as shown at the top of this post.</p>
<p style="text-align: center; font-size: 28px; font-family: 'times new roman', times; margin: 3em 0;">~</p>
<div class="note">
	<p><b>🔥 Like what you see? Want to stay in the loop? Here's how:</b></p>
	<ul>
            <li><a href="https://bsky.app/profile/bram.us">🦋 Follow @bram.us on Bluesky</a></li>
            <li><a href="https://bram.us/feed">🔸 Follow bram.us using RSS</a></li>
	</ul>
	<p>I can also be found on <a href="https://x.com/bramus">𝕏 Twitter</a> and <a href="https://front-end.social/@bramus">🐘 Mastodon</a> but only post there sporadically.</p>
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://www.bram.us/2026/01/16/chrome-145-adds-experimental-support-for-vertical-tabs/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
