<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Michaël Vanderheyden</title>
    <description>The latest articles on DEV Community by Michaël Vanderheyden (@th3s4mur41).</description>
    <link>https://dev.to/th3s4mur41</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F289330%2F9c66b8a2-69c5-431d-bc77-ede880958302.jpg</url>
      <title>DEV Community: Michaël Vanderheyden</title>
      <link>https://dev.to/th3s4mur41</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/th3s4mur41"/>
    <language>en</language>
    <item>
      <title>href="#" and the Focus Trap</title>
      <dc:creator>Michaël Vanderheyden</dc:creator>
      <pubDate>Wed, 17 Jun 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/th3s4mur41/href-and-the-focus-trap-2gg1</link>
      <guid>https://dev.to/th3s4mur41/href-and-the-focus-trap-2gg1</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9w455do9d6sry003ywfv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9w455do9d6sry003ywfv.jpg" alt="A comic style illustration within a web browser frame shows Michaël Vanderheyden looking confused as a giant diagonal lightning bolt splits the scene. His upper body is shown floating up towards a yellow background box reading 'VIEWPORT: SCROLL TO TOP,' while his lower body and legs are physically chained to a rusty anchor labeled 'keyboard focus trapped in footer' keycap-rubble background" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Have you ever added a link to your footer to help users navigate back to the top? It usually looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Back to the top&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've seen and used that pattern many times, including on this very website. From an HTML perspective it is absolutely valid (&lt;a href="https://html.spec.whatwg.org/multipage/browsing-the-web.html#scrolling-to-a-fragment" rel="noopener noreferrer"&gt;7.4.6.4 Scrolling to a fragment&lt;/a&gt;), and I always assumed it was a harmless shortcut that worked well enough.&lt;/p&gt;

&lt;p&gt;Then I updated Biome to 2.5 and it flagged it. I opened a &lt;a href="https://github.com/biomejs/biome/issues/10656" rel="noopener noreferrer"&gt;bug report&lt;/a&gt; expecting to close it quickly, but the answer changed my mind: Biome is not rejecting it because of HTML validity, but because of accessibility behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  This Is Not Just a Biome Thing
&lt;/h2&gt;

&lt;p&gt;The rule is actually not new and inspired by other tools that already enforced it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/anchor-is-valid.md" rel="noopener noreferrer"&gt;&lt;code&gt;jsx-a11y/anchor-is-valid&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qwik.dev/docs/advanced/eslint/#jsx-a" rel="noopener noreferrer"&gt;&lt;code&gt;qwik/jsx-a&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The difference is scope. So far these checks mostly hit JSX workflows, which is why many of us never stumbled on this in plain HTML. Biome is the first tool (in my daily workflow, at least) that called this out on pure HTML.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Good news&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With Biome extending these checks to plain HTML, this broken pattern may finally get broader visibility.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What Is Actually Broken?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Focus desynchronization
&lt;/h3&gt;

&lt;p&gt;An empty hash (&lt;code&gt;#&lt;/code&gt;) does not point to a specific element ID. When activated, the browser scrolls the page visually, but keyboard focus stays on the trigger element (for example, the footer link you just clicked).&lt;/p&gt;

&lt;p&gt;The result is confusing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The page looks like it moved to the top.&lt;/li&gt;
&lt;li&gt;Focus did not move with it.&lt;/li&gt;
&lt;li&gt;The next &lt;code&gt;Tab&lt;/code&gt; keystroke jumps the user back to the bottom context.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  WCAG impact
&lt;/h3&gt;

&lt;p&gt;That behavior creates a mismatch between visual reading sequence and interactive focus sequence, which is exactly the kind of failure &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html" rel="noopener noreferrer"&gt;WCAG 2.4.3 (Focus Order)&lt;/a&gt; is trying to prevent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Workaround (And Why It Works)
&lt;/h2&gt;

&lt;p&gt;Instead of &lt;code&gt;href="#"&lt;/code&gt;, give the top container an explicit ID and target it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;body&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"_top"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- page content --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#_top"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Back to top&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get the same practical scroll effect, but without focus desync.&lt;/p&gt;

&lt;p&gt;What you will notice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The classic empty &lt;code&gt;#&lt;/code&gt; keeps focus on the original footer link after scroll. This is visible through the pink outline and background that stays in the footer.&lt;/li&gt;
&lt;li&gt;The explicit fragment target behaves like normal fragment navigation: focus moves with the scroll, the outline disappears from the link and background color is removed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Although both solutions scroll to the body element, only the second one behaves consistently with any other fragment link pointing to a heading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Impact on Screen Reader Users
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/nvaccess/nvda/issues/19190" rel="noopener noreferrer"&gt;NVDA Issue #19190&lt;/a&gt; — "Focus shifts to top of page when activating a link using &lt;code&gt;href="#"&lt;/code&gt; on Chrome" describes exactly what happens to screen reader users in Chromium-based browsers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The viewport jumps to the top.&lt;/li&gt;
&lt;li&gt;The virtual reading context follows that jump.&lt;/li&gt;
&lt;li&gt;Native keyboard focus remains on the original footer element.&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;Tab&lt;/code&gt;, and you are yanked straight back down.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Browser Vendors Haven't "Fixed" It
&lt;/h2&gt;

&lt;p&gt;This is a classic boundary problem between assistive technology and browser engines, and the issue trail reflects that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Screen reader side
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://github.com/nvaccess/nvda/issues/19190" rel="noopener noreferrer"&gt;NVDA issue&lt;/a&gt; was initially closed as not planned — the reasoning being that the screen reader is simply reacting to where the browser moved the viewport. It was later reopened and triaged, and the question of who owns the fix is still open.&lt;/p&gt;

&lt;h3&gt;
  
  
  Browser engine side
&lt;/h3&gt;

&lt;p&gt;Chromium has addressed a related focus behavior in &lt;a href="https://issues.chromium.org/issues/40403681" rel="noopener noreferrer"&gt;Issue #40403681&lt;/a&gt;: when a fragment identifier points to a non-focusable element, focus handling was improved in that scope.&lt;/p&gt;

&lt;p&gt;The empty fragment case (&lt;code&gt;href="#"&lt;/code&gt;) was not part of that change and can still be reproduced in Firefox and Safari as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  My Take
&lt;/h3&gt;

&lt;p&gt;What makes this especially frustrating is the asymmetry:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;href="#"&lt;/code&gt; scrolls the page without moving focus.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;href="#_top"&lt;/code&gt; often targets the same unfocusable element at the top and yet focus behaves correctly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same destination. Completely different focus outcomes. That's hard to defend as intentional design.&lt;/p&gt;

&lt;p&gt;I still see this primarily as a browser behavior problem, not a screen reader problem. But because the spec does not define what should happen to focus for an empty fragment navigation, it may need to be updated first.&lt;/p&gt;

&lt;p&gt;My expectation is that when navigation to an empty fragment identifier happens, the &lt;code&gt;body&lt;/code&gt; element should become the &lt;strong&gt;sequential focus navigation starting point&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So the fix on our side is straightforward: use the explicit ID approach. It eliminates an accessibility issue with just one line of code. And it's a good reminder that "technically valid" and "actually usable" are not the same thing — sometimes you have to work around browser limitations to deliver a coherent user experience.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://th3s4mur41.me/blog/href-hash-focus-desync-accessibility/" rel="noopener noreferrer"&gt;Th3S4mur41.me&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>html</category>
    </item>
    <item>
      <title>headingoffset Is Coming: How to Prepare Today</title>
      <dc:creator>Michaël Vanderheyden</dc:creator>
      <pubDate>Thu, 04 Jun 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/th3s4mur41/headingoffset-is-coming-how-to-prepare-today-1hc1</link>
      <guid>https://dev.to/th3s4mur41/headingoffset-is-coming-how-to-prepare-today-1hc1</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwajzd8gxsaog5wastdz0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwajzd8gxsaog5wastdz0.jpg" alt="Comic-style illustration of Michaël Vanderheyden giving a thumbs-up while holding the Firefox logo. In the background, a flat screen displays HTML code demonstrating how the new headingoffset attribute dynamically shifts nested heading levels." width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Jake Archibald recently announced that &lt;a href="https://bsky.app/profile/webdevs.firefox.com/post/3mncp42h7ik2b" rel="noopener noreferrer"&gt;Firefox implemented &lt;code&gt;headingoffset&lt;/code&gt; in Firefox Nightly&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is big news for anyone building reusable UI components with semantic headings. For a long time, developers had to choose between awkward workarounds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ask consumers to provide heading elements via slots.&lt;/li&gt;
&lt;li&gt;Build custom heading-level logic into each component.&lt;/li&gt;
&lt;li&gt;Duplicate components just to avoid broken heading structure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that browser implementation has started, we can finally prepare for a cleaner pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;Reusable components often need to appear in different contexts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A card title might be an &lt;code&gt;h2&lt;/code&gt; on one page.&lt;/li&gt;
&lt;li&gt;The same card title might need to be an &lt;code&gt;h3&lt;/code&gt; inside a nested section.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without contextual heading adjustment, one reusable component can easily create skipped or flattened heading levels. That hurts document structure for everyone, especially screen reader users.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;headingoffset&lt;/code&gt; gives us a semantic way to keep component internals stable while adapting heading levels to surrounding context.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we do today (and why it is painful)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Workaround 1: Slot-based heading ownership
&lt;/h3&gt;

&lt;p&gt;We let consumers provide headings from outside the component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;article-card&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;slot=&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Billing Settings&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/article-card&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can work, but it shifts structure responsibilities to every caller, which leads to inconsistency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Workaround 2: Custom heading-level APIs
&lt;/h3&gt;

&lt;p&gt;We pass a level and render dynamic heading tags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;article-card&lt;/span&gt; &lt;span class="na"&gt;heading-level=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/article-card&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;heading-level&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`h&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;))}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is repetitive, easy to misuse, and often re-implemented differently across codebases.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes with headingoffset
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;headingoffset&lt;/code&gt;, a component can keep meaningful internal heading markup, while parent context controls the effective level.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Heading level-1&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;article-card&lt;/span&gt; &lt;span class="na"&gt;headingoffset=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Heading level-2&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/article-card&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;article-card&lt;/code&gt; contains an &lt;code&gt;h1&lt;/code&gt;, it can be treated as a deeper-level heading in that context.&lt;/p&gt;

&lt;p&gt;That means we can write components with clearer semantics, similar to the second workaround but without the need for custom heading-level logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can we use it today?
&lt;/h2&gt;

&lt;p&gt;Not in stable browsers yet, but we can prepare now.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/smockle/headingoffset-polyfill" rel="noopener noreferrer"&gt;headingoffset polyfill&lt;/a&gt; helps bridge the gap. It checks support and, where needed, applies &lt;code&gt;aria-level&lt;/code&gt; to mimic &lt;code&gt;headingoffset&lt;/code&gt; behavior.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://unpkg.com/headingoffset-polyfill"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes progressive enhancement possible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Native behavior when supported.&lt;/li&gt;
&lt;li&gt;Polyfilled fallback elsewhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Firefox + Narrator currently has an announcement quirk&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For cases like &lt;code&gt;&amp;lt;h1 aria-level="2"&amp;gt;&lt;/code&gt;, Firefox with Narrator may announce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"heading level 1"&lt;/li&gt;
&lt;li&gt;then the title&lt;/li&gt;
&lt;li&gt;then "at level 2"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same content is announced as expected with Firefox + NVDA/JAWS and with Edge/Chrome + Narrator. At this point, this looks like a Firefox-Narrator interpretation issue rather than a general &lt;code&gt;aria-level&lt;/code&gt; model problem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  CSS styling requirements (deeper dive)
&lt;/h2&gt;

&lt;p&gt;As soon as heading levels no longer match the literal element name, targeting styles with &lt;code&gt;h1&lt;/code&gt; to &lt;code&gt;h6&lt;/code&gt; selectors is no longer a viable long-term strategy.&lt;/p&gt;

&lt;p&gt;That is exactly where the new &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:heading_function" rel="noopener noreferrer"&gt;&lt;code&gt;:heading()&lt;/code&gt;&lt;/a&gt; selector comes in: it lets you style by effective heading level instead of raw tag name.&lt;/p&gt;

&lt;p&gt;Unfortunately, support is not there yet either, so we still need a fallback strategy today.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:heading&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* heading level 1 styles */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;:heading&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* heading level 2 styles */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Fallback when headingoffset and :heading() are not supported */&lt;/span&gt;
&lt;span class="k"&gt;@supports&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;(:&lt;/span&gt;&lt;span class="n"&gt;heading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* Specific heading without aria-level set, 
     or any heading with aria-level set to the target value. 
   */&lt;/span&gt;
  &lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="nd"&gt;:where&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;:not&lt;/span&gt;&lt;span class="o"&gt;([&lt;/span&gt;&lt;span class="nt"&gt;aria-level&lt;/span&gt;&lt;span class="o"&gt;])),&lt;/span&gt;
  &lt;span class="nd"&gt;:is&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;h4&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;h5&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;h6&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nd"&gt;:where&lt;/span&gt;&lt;span class="o"&gt;([&lt;/span&gt;&lt;span class="nt"&gt;aria-level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"1"&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;/* heading level 1 styles */&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="nd"&gt;:where&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;:not&lt;/span&gt;&lt;span class="o"&gt;([&lt;/span&gt;&lt;span class="nt"&gt;aria-level&lt;/span&gt;&lt;span class="o"&gt;])),&lt;/span&gt;
  &lt;span class="nd"&gt;:is&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;h4&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;h5&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;h6&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nd"&gt;:where&lt;/span&gt;&lt;span class="o"&gt;([&lt;/span&gt;&lt;span class="nt"&gt;aria-level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"2"&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;/* heading level 2 styles */&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This could also be done as progressive enhancement and that may be a better fit if you still support very old browsers. I personally prefer the structure above because once &lt;code&gt;:heading()&lt;/code&gt; support is good enough, I can delete the &lt;code&gt;@supports&lt;/code&gt; block and keep the rest unchanged.&lt;/p&gt;

&lt;p&gt;Check it out live in the CodePen&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If browsers ship &lt;code&gt;headingoffset&lt;/code&gt; before &lt;code&gt;:heading()&lt;/code&gt; support, this fallback can break. You can already observe that scenario in Chrome Canary and Edge Canary where &lt;code&gt;headingoffset&lt;/code&gt; works, but &lt;code&gt;:heading()&lt;/code&gt; is still unsupported.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Practical preparation checklist
&lt;/h2&gt;

&lt;p&gt;You can start preparing now, but this should stay out of production for the moment: the fallback proposal is mostly working, yet there is still release risk because browsers may ship &lt;code&gt;headingoffset&lt;/code&gt; and &lt;code&gt;:heading()&lt;/code&gt; at different times.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Audit components that currently accept &lt;code&gt;heading-level&lt;/code&gt; props.&lt;/li&gt;
&lt;li&gt;Identify places where slot-owned headings are used only for hierarchy control.&lt;/li&gt;
&lt;li&gt;Add exploratory examples behind feature flags or internal demos.&lt;/li&gt;
&lt;li&gt;Add the polyfill in non-supporting browsers and test with real assistive tech combinations.&lt;/li&gt;
&lt;li&gt;Keep fallback CSS explicit for &lt;code&gt;aria-level&lt;/code&gt; mappings where needed.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;This is an exciting transition period: we are moving from custom component logic and one-off heading APIs toward a shared, standards-based model for contextual heading structure.&lt;/p&gt;

&lt;p&gt;That shift can reduce complexity across many contexts, for example in design systems and CMS implementations, improve long-term maintainability, and keep accessibility behavior more consistent across projects.&lt;/p&gt;

&lt;p&gt;What do you think? When will you start replacing your custom heading-level logic with a standards-first approach?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://th3s4mur41.me/blog/headingoffset-is-coming-how-to-prepare-today/" rel="noopener noreferrer"&gt;Th3S4mur41.me&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>html</category>
      <category>css</category>
      <category>a11y</category>
      <category>howto</category>
    </item>
    <item>
      <title>Beyond compliance: Building accessibility into quality with test automation</title>
      <dc:creator>Michaël Vanderheyden</dc:creator>
      <pubDate>Tue, 12 May 2026 10:00:00 +0000</pubDate>
      <link>https://dev.to/th3s4mur41/beyond-compliance-building-accessibility-into-quality-with-test-automation-1a49</link>
      <guid>https://dev.to/th3s4mur41/beyond-compliance-building-accessibility-into-quality-with-test-automation-1a49</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcfwoupxi4mn7fe90661l.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcfwoupxi4mn7fe90661l.jpg" alt="Comic-style illustration of Michaël Vanderheyden showing his latest post on the Atos Blog on a tablet" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’m hyped to share a new deep-dive article I’ve co-authored with &lt;a href="https://www.linkedin.com/in/j%C3%B6rg-jakoby-93a6a8264/" rel="noopener noreferrer"&gt;Jörg Jakoby&lt;/a&gt;, now live on the Atos blog!&lt;/p&gt;

&lt;p&gt;We’ve been exploring how to make accessibility a "business as usual" part of the development lifecycle. The secret? Don't reinvent the wheel.&lt;/p&gt;

&lt;p&gt;In this post, we dig into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to start checking accessibility directly in your CI pipeline.&lt;/li&gt;
&lt;li&gt;Why automation is a massive lever for preventing regressions (even if it doesn't cover 100%).&lt;/li&gt;
&lt;li&gt;Practical ways to extend the tools you already have in place to save time and effort.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Building inclusive tech doesn't have to be complex and expensive. It’s about working smarter with the automation you’ve already built!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read the full story over at Atos:&lt;/strong&gt; &lt;a href="https://atos.net/en/blog/beyond-compliance-building-accessibility-into-quality-with-test-automation" rel="noopener noreferrer"&gt;Beyond compliance: Building accessibility into quality with test automation&lt;/a&gt;&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>testautomation</category>
      <category>devops</category>
      <category>cicd</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Michaël Vanderheyden</dc:creator>
      <pubDate>Wed, 15 Apr 2026 13:14:59 +0000</pubDate>
      <link>https://dev.to/th3s4mur41/-21fg</link>
      <guid>https://dev.to/th3s4mur41/-21fg</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/mfairchild365/embedding-accessibility-into-ai-based-software-development-282k" class="crayons-story__hidden-navigation-link"&gt;Embedding Accessibility into AI based software development&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/mfairchild365" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F152506%2F86319f69-42bd-469e-8c09-996986a2d551.jpeg" alt="mfairchild365 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/mfairchild365" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Michael Fairchild
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Michael Fairchild
                
              
              &lt;div id="story-author-preview-content-3344524" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/mfairchild365" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F152506%2F86319f69-42bd-469e-8c09-996986a2d551.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Michael Fairchild&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/mfairchild365/embedding-accessibility-into-ai-based-software-development-282k" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 12&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/mfairchild365/embedding-accessibility-into-ai-based-software-development-282k" id="article-link-3344524"&gt;
          Embedding Accessibility into AI based software development
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/a11y"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;a11y&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/llm"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;llm&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/benchmark"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;benchmark&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/mfairchild365/embedding-accessibility-into-ai-based-software-development-282k" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;14&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/mfairchild365/embedding-accessibility-into-ai-based-software-development-282k#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              3&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Labels: Automatic Checks and Manual Validation</title>
      <dc:creator>Michaël Vanderheyden</dc:creator>
      <pubDate>Tue, 14 Apr 2026 09:00:00 +0000</pubDate>
      <link>https://dev.to/th3s4mur41/labels-automatic-checks-and-manual-validation-58a2</link>
      <guid>https://dev.to/th3s4mur41/labels-automatic-checks-and-manual-validation-58a2</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fez8an153r1i5u9f2s3h2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fez8an153r1i5u9f2s3h2.jpg" alt="Comic-style illustration of Michaël Vanderheyden wearing a baseball cap and samurai-themed shirt, working on a laptop with a samurai sticker. To the side, accessibility icons and the hashtag #A11yTips promote practical accessibility tips for developers." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Labels are essential for screen reader users and overall accessibility. While automated tools can verify that labels are present on interactive elements, determining whether a label is meaningful and contextually appropriate still requires manual testing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://webaim.org/articles/screenreader_testing/" rel="noopener noreferrer"&gt;Screen reader testing&lt;/a&gt; can be challenging for developers and testers who aren't regular users. Practical alternatives include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inspecting labels and roles through the browser DevTools accessibility tree.&lt;/li&gt;
&lt;li&gt;Using voice navigation to verify that elements are reachable and operable by spoken commands.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Screen reader setup&lt;/strong&gt;&lt;br&gt;
If you do test with a screen reader, the results will only be reliable if your environment is correctly set up. The right screen reader depends on both your &lt;strong&gt;operating system&lt;/strong&gt; and the &lt;strong&gt;browser&lt;/strong&gt; you're testing with. Sara Soueidan's &lt;a href="https://www.sarasoueidan.com/blog/testing-environment-setup/" rel="noopener noreferrer"&gt;guide to setting up an accessible testing environment&lt;/a&gt; covers screen reader and browser pairings for macOS and Windows, virtual testing, mobile screen readers, and keyboard configuration.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  DevTools Accessibility Tree
&lt;/h2&gt;

&lt;p&gt;For developers who aren't regular screen reader users, browser DevTools offer a practical way to inspect how labels and roles are exposed to assistive technologies. Both Chromium-based browsers (like Edge and Chrome) and Firefox include an &lt;strong&gt;Accessibility Tree&lt;/strong&gt; view that mirrors what screen readers perceive.&lt;/p&gt;

&lt;p&gt;This tree shows the semantic structure of your page - including roles, names, states, and relationships - allowing you to verify whether interactive elements are properly labeled and accessible.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe8z202508li1bydnq2oa.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe8z202508li1bydnq2oa.jpg" alt="Screenshot comparing the accessibility tree views in Edge (Chromium) and Firefox DevTools. The left panel shows the Elements tab with the accessibility tree in Edge, while the right panel displays detailed accessibility properties in Firefox. An arrow highlights the menu location to access these tools." width="800" height="496"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Inspecting accessibility like a pro: Both Edge (Chromium) and Firefox offer built-in tools to explore the accessibility tree. Just open DevTools and follow the arrow to find the menu - it's easier than you think.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How to access the Accessibility Tree
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Edge / Chrome:
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Open DevTools (&lt;code&gt;F12&lt;/code&gt; or &lt;code&gt;Ctrl+Shift+I&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Open DevTools settings through the 3-dot menu&lt;/li&gt;
&lt;li&gt;Go to the experiments tab and activate the &lt;strong&gt;Enable full accessibility tree view in Element panel&lt;/strong&gt; option&lt;/li&gt;
&lt;li&gt;Go to the &lt;strong&gt;Elements&lt;/strong&gt; tab&lt;/li&gt;
&lt;li&gt;Click the floating Accessibility button in the top right of the pane&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Firefox:
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Open DevTools (&lt;code&gt;F12&lt;/code&gt; or &lt;code&gt;Ctrl+Shift+I&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Go to the &lt;strong&gt;Accessibility&lt;/strong&gt; tab directly&lt;/li&gt;
&lt;li&gt;Navigate the tree or select elements to inspect roles, names, and states&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The screenshot above compares both views using a demo from &lt;a href="https://codepen.io/th3s4mur41/pen/MYKGORX" rel="noopener noreferrer"&gt;this CodePen&lt;/a&gt;, showing how a checkbox labeled "Read" is exposed in the accessibility tree. You can see its role, name, and state - all critical for screen reader compatibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Voice Navigation
&lt;/h2&gt;

&lt;p&gt;Screen readers aren't the only way to validate whether labels are meaningful and usable. Voice navigation tools let you interact with a page the way users relying on speech commands would - asking the system to "click," "open," or "select" elements by name. This is a powerful way to check if your labels are both present and practical in real-world use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it matters
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;If a button or link can't be activated by its label, it's a sign the label isn't descriptive enough.&lt;/li&gt;
&lt;li&gt;Voice navigation highlights issues that automated checks often miss, such as ambiguous or duplicated labels.&lt;/li&gt;
&lt;li&gt;It helps ensure that your interface works for users who rely on voice control for accessibility or convenience.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How to test with voice navigation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Desktop&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Windows (Voice Access)
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Enable Voice Access in Accessibility settings.&lt;/li&gt;
&lt;li&gt;Use commands like "Click Read" or "Open Settings" to test if elements respond correctly.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  macOS (Voice Control)
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Turn on Voice Control in System Settings (or System Preferences in older versions) → Accessibility.&lt;/li&gt;
&lt;li&gt;Try commands such as "Click Log In" or "Select checkbox".&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Mobile&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Android (Voice Access)
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings → Accessibility&lt;/strong&gt; and enable &lt;strong&gt;Voice Access&lt;/strong&gt; (you may need to install it from the Play Store first).&lt;/li&gt;
&lt;li&gt;Tap &lt;strong&gt;Open&lt;/strong&gt; in the Voice Access notification to start a session — numbered overlays will appear on interactive elements.&lt;/li&gt;
&lt;li&gt;Try commands like &lt;strong&gt;"Tap Read"&lt;/strong&gt; or &lt;strong&gt;"Scroll down"&lt;/strong&gt; to verify that elements are reachable by their label.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Voice Access Numbers&lt;/strong&gt;&lt;br&gt;
Android also shows interactive element numbers on screen during a Voice Access session. If an element only gets a number but no spoken label, it is a strong signal the accessible name is missing or unclear.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  iOS (Voice Control)
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings → Accessibility → Voice Control&lt;/strong&gt; and toggle it on.&lt;/li&gt;
&lt;li&gt;Try commands like &lt;strong&gt;"Tap Log In"&lt;/strong&gt; or &lt;strong&gt;"Tap checkbox"&lt;/strong&gt; to verify that elements respond to their visible or programmatic label.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Voice Navigation as a Developer Tool
&lt;/h3&gt;

&lt;p&gt;Voice navigation isn't just for end users with accessibility needs. Developers themselves may benefit from it at some point in their careers. For example, those experiencing hand mobility issues such as carpal tunnel syndrome can use voice commands as an alternative input method.&lt;/p&gt;

&lt;p&gt;Building with accessibility in mind ensures that these options are available when you or your users need them most.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Properly associating labels with interactive elements isn't just a screen reader concern. As this article shows, labels are the backbone of voice navigation, the anchor for DevTools inspection, and a key signal for any assistive technology or automated agent, whether it's a switch device, a browser extension, an AI assistant filling out a form on your behalf, or a future input method we haven't seen yet.&lt;/p&gt;

&lt;p&gt;When a label is clear and correctly linked, everyone benefits: screen reader users hear the right name, voice control users can activate elements by speaking them, and sighted keyboard users get a larger click target for free.&lt;/p&gt;

&lt;p&gt;You have more testing tools at your disposal than you might think. Use whichever fits your workflow — open the accessibility tree, try a voice command, or both. The important thing is to make label validation a deliberate step, not an afterthought.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://th3s4mur41.me/blog/a11y-tips/labels-validation/" rel="noopener noreferrer"&gt;Th3S4mur41.me&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>wcag</category>
      <category>devtools</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Michaël Vanderheyden</dc:creator>
      <pubDate>Thu, 26 Mar 2026 16:45:47 +0000</pubDate>
      <link>https://dev.to/th3s4mur41/-4lce</link>
      <guid>https://dev.to/th3s4mur41/-4lce</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/stuffbreaker/how-declarative-custom-elements-dce-could-improve-web-components-53eg" class="crayons-story__hidden-navigation-link"&gt;How Declarative Custom Elements (DCE) Could Improve Web Components&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/stuffbreaker" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F87382%2F524f7820-0def-4bf8-bea4-2beaf247d75d.jpeg" alt="stuffbreaker profile" class="crayons-avatar__image" width="400" height="400"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/stuffbreaker" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Burton Smith
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Burton Smith
                
              
              &lt;div id="story-author-preview-content-3033126" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/stuffbreaker" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F87382%2F524f7820-0def-4bf8-bea4-2beaf247d75d.jpeg" class="crayons-avatar__image" alt="" width="400" height="400"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Burton Smith&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/stuffbreaker/how-declarative-custom-elements-dce-could-improve-web-components-53eg" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 24&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/stuffbreaker/how-declarative-custom-elements-dce-could-improve-web-components-53eg" id="article-link-3033126"&gt;
          How Declarative Custom Elements (DCE) Could Improve Web Components
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/html"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;html&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javavscript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javavscript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webcomponents"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webcomponents&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/stuffbreaker/how-declarative-custom-elements-dce-could-improve-web-components-53eg" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;7&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/stuffbreaker/how-declarative-custom-elements-dce-could-improve-web-components-53eg#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              3&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            8 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>html</category>
      <category>javavscript</category>
      <category>webcomponents</category>
    </item>
    <item>
      <title>Making Scrollable Code Blocks Accessible</title>
      <dc:creator>Michaël Vanderheyden</dc:creator>
      <pubDate>Fri, 20 Feb 2026 11:59:17 +0000</pubDate>
      <link>https://dev.to/th3s4mur41/making-scrollable-code-blocks-accessible-3i0o</link>
      <guid>https://dev.to/th3s4mur41/making-scrollable-code-blocks-accessible-3i0o</guid>
      <description>&lt;p&gt;When you work on the web long enough, you get used to browsers disagreeing on the small things. But sometimes those “small things” quietly break accessibility in ways that aren’t obvious until you test with a keyboard.&lt;/p&gt;

&lt;p&gt;This is exactly what happened with scrollable elements.&lt;/p&gt;

&lt;p&gt;Most browsers like Chrome and Firefox automatically make scrollable regions keyboard‑focusable. That means if the content overflows, a keyboard user can tab into it and scroll through the content using the arrow keys. It’s intuitive, consistent, and aligns with WCAG’s requirement that scrollable regions must be keyboard accessible.&lt;br&gt;
Safari, however, doesn't follow this behavior. There's a long‑standing &lt;a href="https://bugs.webkit.org/show_bug.cgi?id=277290" rel="noopener noreferrer"&gt;bug report in WebKit Backlog&lt;/a&gt; where scrollable elements (like long code blocks or tables) do not receive keyboard focus, as reported by &lt;a href="https://adrianroselli.com/2022/06/keyboard-only-scrolling-areas.html" rel="noopener noreferrer"&gt;Adrian Roselli&lt;/a&gt;, making them inaccessible to keyboard users.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0sqa5bb62j99a9eaq9m0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0sqa5bb62j99a9eaq9m0.png" alt="WebKit Bug 277290" width="614" height="190"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Safari doesn’t make scrollable elements focusable
&lt;/h2&gt;

&lt;p&gt;In Safari, an element that becomes scrollable (e.g., long code snippets) does not receive focus by default. That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can see the scrollbar.&lt;/li&gt;
&lt;li&gt;You can scroll with a mouse or trackpad.&lt;/li&gt;
&lt;li&gt;But you cannot tab into it.&lt;/li&gt;
&lt;li&gt;And therefore you cannot scroll it using the keyboard.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a direct accessibility failure under &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html" rel="noopener noreferrer"&gt;WCAG 2.2’s Keyboard criterion&lt;/a&gt;, which states that all functionality must be operable through a keyboard interface — including scrolling inside overflow regions.&lt;br&gt;
For keyboard‑only users, this effectively traps content. They can see the beginning of the content, but not the rest.&lt;br&gt;
And because Chrome and Firefox “do the right thing,” most developers never notice the issue.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why this matters more than it seems
&lt;/h2&gt;

&lt;p&gt;Scrollable code blocks are everywhere: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Documentation sites&lt;/li&gt;
&lt;li&gt;Developer blogs&lt;/li&gt;
&lt;li&gt;Design system pages&lt;/li&gt;
&lt;li&gt;API references&lt;/li&gt;
&lt;li&gt;Tutorials&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Code blocks are usually implemented using &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; elements. The &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; element is one of the most common non-interactive elements that becomes scrollable by default because it's designed to preserve formatting and not wrap content.&lt;br&gt;
If a code block is long enough to require scrolling, it becomes inaccessible to keyboard users in Safari.&lt;/p&gt;

&lt;p&gt;If a user can’t scroll them, they can’t read them. And if they can’t read them, the content might as well not exist.&lt;br&gt;
This is one of those accessibility issues that isn’t flashy, but has a real impact on real people.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;There is a simple solution to the problem: make scrollable elements keyboard‑focusable by adding &lt;code&gt;tabindex="0"&lt;/code&gt;. This allows keyboard users to tab into the element and use arrow keys to scroll through the content.&lt;br&gt;
However, this requires developers to manually add &lt;code&gt;tabindex="0"&lt;/code&gt; to every scrollable &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; element, which is not ideal. It’s easy to forget, and it adds extra maintenance.&lt;br&gt;
Moreover, it’s not ideal to make every &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; focusable, since not all of them will be scrollable.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The polyfill is only a temporary workaround until Safari implements the expected behavior. It’s not a permanent solution, but it helps bridge the gap in the meantime.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Automatically adding tabindex="0" when needed
&lt;/h3&gt;

&lt;p&gt;To fix this gap, I created a small polyfill: &lt;a href="https://github.com/Th3S4mur41/scroll-focus-polyfill" rel="noopener noreferrer"&gt;scroll-focus-polyfill&lt;/a&gt; that is pretty easy to use by simply including it on your page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://unpkg.com/scroll-focus-polyfill"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The idea is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Detect when a &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; element becomes scrollable.&lt;/li&gt;
&lt;li&gt;Automatically add &lt;code&gt;tabindex="0"&lt;/code&gt; so it becomes keyboard-focusable.&lt;/li&gt;
&lt;li&gt;Only apply this where the behavior is not already provided by the browser.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This keeps the behavior consistent across browsers without forcing developers to manually annotate every code block.&lt;br&gt;
It’s intentionally lightweight, unobtrusive, and standards‑aligned.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why not just add &lt;code&gt;tabindex="0"&lt;/code&gt; everywhere?
&lt;/h3&gt;

&lt;p&gt;You could, but that introduces its own problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Non‑scrollable &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; elements don’t need to be in the tab order.&lt;/li&gt;
&lt;li&gt;Adding unnecessary focus targets creates noise for keyboard users.&lt;/li&gt;
&lt;li&gt;It also breaks expected semantics — not every &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; is interactive.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The polyfill only applies focusability where it's actually needed.&lt;/p&gt;
&lt;h3&gt;
  
  
  What about other scrollable elements?
&lt;/h3&gt;

&lt;p&gt;While this polyfill focuses on &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; elements by default for performance reasons, it can be configured to also apply the same logic to other elements like tables, divs with &lt;code&gt;white-space: nowrap&lt;/code&gt;, or any other scrollable container.&lt;br&gt;
For example, if you have a class &lt;code&gt;scrollable&lt;/code&gt; that prevents an element from wrapping, you can easily extend the polyfill to handle any element with that class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://unpkg.com/scroll-focus-polyfill"&lt;/span&gt;
        &lt;span class="na"&gt;data-selectors=&lt;/span&gt;&lt;span class="s"&gt;"pre, .scrollable"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A subtle bug with real accessibility impact
&lt;/h2&gt;

&lt;p&gt;This is a great example of how accessibility issues often hide in the details. &lt;br&gt;
Safari’s behavior isn’t “wrong” in a spec sense — but it’s inconsistent with user expectations and with other browsers’ accessibility affordances.&lt;br&gt;
And because developers don't always test keyboard navigation explicitly in Safari, the issue slips through unnoticed.&lt;br&gt;
The polyfill bridges that gap until Safari aligns with the behavior of other engines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Making scrollable code blocks accessible is a small but important step towards a more inclusive web.&lt;br&gt;
By understanding the issue, implementing a simple polyfill, and advocating for better cross‑browser consistency, we can ensure that all users have equal access to content — regardless of their browser or input method.&lt;br&gt;
If you maintain a documentation site, developer blog, or any content with scrollable code blocks, consider implementing this polyfill to improve accessibility for your keyboard users. It’s a small change that can make a big difference.&lt;/p&gt;




&lt;p&gt;Join the conversation on &lt;a href="https://bsky.app/profile/th3s4mur41.me/post/3meqi7ecnjc2z" rel="noopener noreferrer"&gt;Bluesky&lt;/a&gt; or &lt;a href="https://www.linkedin.com/posts/michaelvanderheyden_making-scrollable-code-blocks-accessible-activity-7428049258546737153-_xQi" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://th3s4mur41.me/blog/scroll-focus-polyfill/" rel="noopener noreferrer"&gt;https://th3s4mur41.me&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>wcag</category>
      <category>howto</category>
      <category>keyboardnavigation</category>
    </item>
    <item>
      <title>Contrast: Beyond WCAG Compliance</title>
      <dc:creator>Michaël Vanderheyden</dc:creator>
      <pubDate>Wed, 14 Jan 2026 10:00:00 +0000</pubDate>
      <link>https://dev.to/th3s4mur41/contrast-beyond-wcag-compliance-lh8</link>
      <guid>https://dev.to/th3s4mur41/contrast-beyond-wcag-compliance-lh8</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs4sc1qialqdcpaxx2s67.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs4sc1qialqdcpaxx2s67.jpg" alt="Comic-style illustration of a bearded developer wearing a baseball cap and samurai-themed shirt, working on a laptop with a samurai sticker. To the side, accessibility icons and the hashtag #A11yTips promote practical accessibility tips for developers." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Contrast is one of the easiest accessibility aspects to evaluate because it can be measured with algorithms. Whether through automated tools or semi‑automatic checks, developers can quickly verify if text and background combinations meet established standards. This makes contrast a practical starting point for improving accessibility - though, as we'll see, compliance alone doesn't always guarantee the best user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  WCAG vs APCA: Understanding the Difference
&lt;/h2&gt;

&lt;p&gt;While &lt;a href="https://www.w3.org/TR/WCAG22/#contrast-minimum" rel="noopener noreferrer"&gt;WCAG guidelines&lt;/a&gt; provide a solid foundation for color contrast, strict compliance sometimes leads to suboptimal results with certain colors. Consider using the &lt;a href="https://git.apcacontrast.com/documentation/APCAeasyIntro" rel="noopener noreferrer"&gt;APCA (Accessible Perceptual Contrast Algorithm)&lt;/a&gt; as an alternative or in addition to WCAG checks. APCA offers a more nuanced approach to contrast that better reflects human perception.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fth3s4mur41.me%2F_astro%2Fcontrast-wcag-vs-apca.DU4TBag9_Z1uOBIv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fth3s4mur41.me%2F_astro%2Fcontrast-wcag-vs-apca.DU4TBag9_Z1uOBIv.png" alt="Same background, different verdicts: APCA favors readability of white text on orange background, while WCAG prefers&lt;br&gt;
black. A striking example of how contrast standards can diverge." width="737" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Visual clarity vs legacy metrics: Though WCAG favors black text on orange background, users often find white more&lt;br&gt;
readable. APCA captures this perceptual nuance, challenging traditional contrast assumptions.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Try the demo interactively:&lt;/p&gt;

&lt;h3&gt;WCAG Levels in Brief&lt;/h3&gt;

&lt;h3&gt;
  
  
  Real-world example: Stack Overflow
&lt;/h3&gt;

&lt;p&gt;For example, Stack Overflow recently transitioned to APCA for their accessibility design, improving readability and user experience. You can read more about their approach and lessons learned in their blog post: &lt;a href="https://stackoverflow.blog/2024/08/07/accessibility-by-design-building-interfaces-for-everyone-at-stack-overflow/" rel="noopener noreferrer"&gt;Accessibility by Design at Stack Overflow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To keep contrast checks in their automated accessibility testing, they also built custom Axe rules that validate APCA contrast levels (instead of the traditional WCAG contrast ratio checks). That work is available publicly as &lt;a href="https://github.com/StackExchange/apca-check/tree/main" rel="noopener noreferrer"&gt;StackExchange/apca-check&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to check contrast (DevTools + quick tools)
&lt;/h2&gt;

&lt;p&gt;Even if you run automated checks, it’s useful to spot‑check contrast right in the browser while you build.&lt;/p&gt;

&lt;h3&gt;
  
  
  Chrome / Chromium-based browsers
&lt;/h3&gt;

&lt;p&gt;You can see contrast information in a few places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the element picker (“Select an element in the page to inspect”, usually + + ) to inspect the text you care about.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;Styles&lt;/strong&gt; panel, open the color picker for the &lt;code&gt;color&lt;/code&gt; property; DevTools can display contrast results for the foreground/background pairing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fth3s4mur41.me%2F_astro%2Fchrome-inspect-popover.CAogomm4_Z1QFJmV.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fth3s4mur41.me%2F_astro%2Fchrome-inspect-popover.CAogomm4_Z1QFJmV.png" alt="Chrome DevTools element picker highlighting a text element and showing the inspect&lt;br&gt;
popover." width="178" height="254"&gt;&lt;/a&gt; &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fth3s4mur41.me%2F_astro%2Fchrome-color-picker.ChK_ceAM_Z1VOITq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fth3s4mur41.me%2F_astro%2Fchrome-color-picker.ChK_ceAM_Z1VOITq.png" alt="Chrome DevTools color picker showing contrast information for the selected foreground and background&lt;br&gt;
colors." width="277" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Chrome DevTools: use the element picker to target the exact text you want to evaluate, or use the&lt;br&gt;
 picker to view contrast results.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Known DevTools issue:&lt;/strong&gt; Chrome DevTools has a known issue (&lt;a href="https://crbug.com/1414206" rel="noopener noreferrer"&gt;Chromium bug 1414206&lt;/a&gt;) where contrast ratios aren’t displayed if a color is applied to an element containing children (or even a comment node). Until it’s fixed, check contrast directly on the actual text node(s), or use an external checker.&lt;/p&gt;

&lt;p&gt;If you want to switch DevTools to the APCA algorithm, open &lt;strong&gt;DevTools Settings&lt;/strong&gt; → &lt;strong&gt;Experiments&lt;/strong&gt; and enable the APCA contrast option. The main downside is that DevTools typically shows either WCAG &lt;em&gt;or&lt;/em&gt; APCA results, not both at once.&lt;/p&gt;

&lt;h3&gt;
  
  
  Firefox
&lt;/h3&gt;

&lt;p&gt;Firefox DevTools also shows contrast in the color picker for the &lt;code&gt;color&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fth3s4mur41.me%2F_astro%2Ffirefox-color-picker.BeedRq-v_yPOKI.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fth3s4mur41.me%2F_astro%2Ffirefox-color-picker.BeedRq-v_yPOKI.png" alt="Firefox DevTools color picker displaying contrast information for the selected text&lt;br&gt;
color." width="260" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Firefox DevTools: check contrast in the color picker for the property.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  When you want WCAG and APCA side-by-side
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://visbug.web.app/" rel="noopener noreferrer"&gt;VisBug&lt;/a&gt; is a good alternative for quick checks, since its contrast checker shows WCAG and APCA results side by side.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd39x1oqrvy3xr125at2b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd39x1oqrvy3xr125at2b.png" alt="VisBug contrast checker showing WCAG and APCA results side by side." width="347" height="172"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;VisBug: handy for quick spot-checks when you want WCAG and APCA at the same time.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Warning: Contrast tools have limits
&lt;/h2&gt;

&lt;p&gt;All of these tools are helpful, but they have a limitation: they can only reliably validate contrast by following the normal DOM and paint flow to determine the “effective” background behind text.&lt;/p&gt;

&lt;p&gt;If you use visual stacking (for example via CSS Grid overlap, positioned layers, or a background created with a pseudo-element like &lt;code&gt;::before&lt;/code&gt;) where the foreground and background colors live on different elements, tools can pick the wrong background to compare against.&lt;/p&gt;

&lt;p&gt;In the following demo, both boxes look identical, but most tools report a contrast of 1 (no contrast) for the second box because they compare the text against the page background (e.g. &lt;code&gt;body&lt;/code&gt;) rather than the yellow background painted by &lt;code&gt;::before&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Tools and rules are there to help you make better decisions, not to replace judgment. The real goal of contrast work isn’t “passing a checker”, it’s making content that people can comfortably read and use.&lt;/p&gt;

&lt;p&gt;Ideally, aim to satisfy both WCAG and APCA. But when you can’t (or when the two point you in different directions), favor real user experience over compliance: validate with real content, real contexts, and—when possible—real users.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://th3s4mur41.me/blog/a11y-tips/contrast/" rel="noopener noreferrer"&gt;Th3S4mur41.me&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>wcag</category>
      <category>apca</category>
      <category>colorcontrast</category>
    </item>
    <item>
      <title>Frameworks are tools, not gatekeepers: Rethinking web developer hiring</title>
      <dc:creator>Michaël Vanderheyden</dc:creator>
      <pubDate>Mon, 01 Sep 2025 07:00:00 +0000</pubDate>
      <link>https://dev.to/th3s4mur41/frameworks-are-tools-not-gatekeepers-rethinking-web-developer-hiring-5bkk</link>
      <guid>https://dev.to/th3s4mur41/frameworks-are-tools-not-gatekeepers-rethinking-web-developer-hiring-5bkk</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4p7fmg6kfrug0mqdiye8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4p7fmg6kfrug0mqdiye8.jpg" alt="A comic-style illustration of Michaël Vanderheyden frowning at popular framework logos next to the text " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another week, another missed opportunity. A seasoned web developer, 20+ years of experience, expert in Angular, certified in accessibility, and a proven leader, was dismissed from a job application. The reason? Not enough practical experience with React. Not because the role demanded deep React internals or framework-specific wizardry. They were hiring someone to build features. And yet, the checkbox hiring mentality struck again.&lt;/p&gt;

&lt;h2&gt;
  
  
  The checklist problem
&lt;/h2&gt;

&lt;p&gt;Too many hiring processes are driven by rigid requirement lists, often crafted by people who don't truly understand how the web works. They treat frameworks like qualifications, not tools. And in doing so, they overlook developers who bring deep, transferable knowledge - people who understand the web's foundations and can adapt to any framework thrown their way.&lt;/p&gt;

&lt;p&gt;This isn't just frustrating. It's wasteful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hire for fundamentals, not familiarity
&lt;/h2&gt;

&lt;p&gt;If I were hiring for a web development role, I'd choose someone who understands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Semantic HTML and why it matters&lt;/li&gt;
&lt;li&gt;How to build accessible, inclusive interfaces&lt;/li&gt;
&lt;li&gt;The principles of progressive enhancement&lt;/li&gt;
&lt;li&gt;How the browser actually works&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are the skills that transcend frameworks. React, Angular, Vue, Svelte - they're all just tools. A good developer can learn them. A great developer knows when to use them, and when not to.&lt;/p&gt;

&lt;p&gt;So, when we have open opportunities and I'm hiring, this is exactly what I'm looking for. I'm not scanning for the latest buzzwords on a résumé or checking off a list of libraries someone has used. I'm searching for people who think deeply about the web, who understand its foundations, and who care about building resilient, user-first experiences. If you bring curiosity, clarity of thought, and a strong grasp of the fundamentals, you're already speaking my language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frameworks fade, standards endure
&lt;/h2&gt;

&lt;p&gt;Remember jQuery? It was revolutionary. It solved real problems. But most of what made it essential has now been absorbed into native web standards. The same fate awaits today's frameworks. In 10 years, React may be a footnote. Web standards, however, will still be here, evolving, expanding, and forming the bedrock of everything we build.&lt;/p&gt;

&lt;p&gt;So why invest in developers who are deeply tied to a single framework, rather than those who build on enduring principles?&lt;/p&gt;

&lt;h2&gt;
  
  
  Build for the future, not just the stack
&lt;/h2&gt;

&lt;p&gt;Hiring someone who sees frameworks as tools instead of crutches means investing in solutions that can evolve. It means building products that aren't locked into a single ecosystem. It means fostering a team that can pivot, adapt, and innovate.&lt;/p&gt;

&lt;p&gt;Framework fluency is useful. But framework dependency is dangerous.&lt;/p&gt;

&lt;p&gt;Let's stop filtering out great developers because they haven't ticked the right box. Let's start hiring people who understand the web, not just the latest abstraction of it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://th3s4mur41.me/blog/frameworks-are-tools-not-gatekeepers/" rel="noopener noreferrer"&gt;Th3S4mur41.me&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>hiring</category>
      <category>frameworks</category>
      <category>a11y</category>
    </item>
  </channel>
</rss>
