<?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: Priya Nair</title>
    <description>The latest articles on DEV Community by Priya Nair (@priya_nair).</description>
    <link>https://dev.to/priya_nair</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%2F3868481%2Fb1f50609-3fa2-4d3a-a172-285aa7c95374.png</url>
      <title>DEV Community: Priya Nair</title>
      <link>https://dev.to/priya_nair</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/priya_nair"/>
    <language>en</language>
    <item>
      <title>Mastering Focus Management in React 19: Solving the Single-Page Application Routing Gap for WCAG 2.1 and EN 301 549 Compliance</title>
      <dc:creator>Priya Nair</dc:creator>
      <pubDate>Sun, 21 Jun 2026 08:25:57 +0000</pubDate>
      <link>https://dev.to/priya_nair/mastering-focus-management-in-react-19-solving-the-single-page-application-routing-gap-for-wcag-3dfi</link>
      <guid>https://dev.to/priya_nair/mastering-focus-management-in-react-19-solving-the-single-page-application-routing-gap-for-wcag-3dfi</guid>
      <description>&lt;h1&gt;
  
  
  Mastering Focus Management in React 19: Solving the Single-Page Application Routing Gap for WCAG 2.1 and EN 301 549 Compliance
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Meta:&lt;/strong&gt; Stop losing your users on page transitions. Learn how to manage focus in React 19 to meet WCAG 2.1 standards and provide a seamless screen reader experience.&lt;/p&gt;

&lt;p&gt;When you click a link in a traditional multi-page website, the browser does something vital: it refreshes the page, resets the focus to the top of the document, and announces the new page title. For a sighted user, this is a blink-and-you-miss-it transition. For a screen reader user, it is a critical signal that they have arrived at a new destination.&lt;/p&gt;

&lt;p&gt;In a Single-Page Application (SPA) built with React, this behavior vanishes. Because the page doesn't actually refresh—React simply swaps out the component tree—the browser’s focus stays exactly where it was. If the user clicked a "View Profile" link in the footer and that link disappears from the DOM during the transition, the focus often drops back to the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; or, worse, vanishes into a "focus vacuum."&lt;/p&gt;

&lt;p&gt;The result? A screen reader user is left stranded. They don't know if the page changed, where they are, or how to start reading the new content. This isn't just a "UX quirk"; it is a direct violation of &lt;strong&gt;WCAG 2.1 Success Criterion 2.4.3 (Focus Order)&lt;/strong&gt; and is a failure under the &lt;strong&gt;EN 301 549&lt;/strong&gt; European accessibility standards.&lt;/p&gt;

&lt;p&gt;If you want your app to be truly inclusive, you have to take manual control of the focus. Here is how to do it professionally in React 19.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Focus Management is a Business Requirement
&lt;/h2&gt;

&lt;p&gt;I often hear developers say, "Our site passes the automated audit, so we're good." Automated tools (like Axe or Lighthouse) are great for catching missing alt text or low contrast, but they cannot tell you if your focus management is logical. They can't feel the frustration of a user who has to tab through the entire navigation menu again every time they change pages.&lt;/p&gt;

&lt;p&gt;Poor focus management creates a "cognitive tax." When users have to hunt for their position on a page, they fatigue faster and abandon the product. In the context of European regulations (EN 301 549), failing to provide a predictable focus order can leave your organization legally vulnerable. Accessibility is not a feature you "add" at the end of a sprint; it is a core engineering requirement for a production-ready application.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Focus Vacuum" Problem
&lt;/h2&gt;

&lt;p&gt;Imagine a user navigating your app using a screen reader (like NVDA or VoiceOver). They click a link to navigate to &lt;code&gt;/settings&lt;/code&gt;. React updates the DOM. The link they just clicked is gone. The browser now has no active element. &lt;/p&gt;

&lt;p&gt;Depending on the browser and screen reader combination, one of three things happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Focus resets to the very top of the document (forcing the user to tab through the global header again).&lt;/li&gt;
&lt;li&gt;Focus stays on the &lt;code&gt;body&lt;/code&gt; element, meaning the user has to manually search for the main content.&lt;/li&gt;
&lt;li&gt;The focus "gets lost," and the screen reader remains silent, leaving the user wondering if the click even worked.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To solve this, we need to implement a "Focus Wrapper" or a global focus management strategy that explicitly moves the focus to a known, logical location upon every route change.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Implementation: A Custom Focus Manager
&lt;/h2&gt;

&lt;p&gt;In React 19, we can leverage the updated hooks and the refined component lifecycle to create a reusable &lt;code&gt;FocusManager&lt;/code&gt; component. The goal is to move focus to a top-level heading (usually the &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;) of the new page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Creating the Focusable Wrapper
&lt;/h3&gt;

&lt;p&gt;First, we need a way to make an element focusable that isn't naturally focusable (like an &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;). We do this using &lt;code&gt;tabIndex="-1"&lt;/code&gt;. This allows the element to receive focus programmatically via JavaScript, but prevents it from being part of the natural Tab sequence, so we don't clutter the user's navigation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useLocation&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-router-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Assuming react-router-dom&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PageFocusManager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useLocation&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;focusRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// We use a small timeout to ensure the DOM has fully updated &lt;/span&gt;
    &lt;span class="c1"&gt;// and the new page content is rendered before attempting to focus.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;focusRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;focusRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"page-wrapper"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* 
          tabIndex="-1" is the magic here. 
          It allows .focus() to work without adding the element to the Tab order.
      */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;focusRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;tabIndex&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"-1"&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* This will be populated by the page content, 
            or we can use a visually hidden announcement */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"sr-only"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Page Loaded&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;PageFocusManager&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: The "Visually Hidden" Pattern
&lt;/h3&gt;

&lt;p&gt;You might be thinking, "I don't want my &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; to be the focus target because it looks weird when it's highlighted." &lt;/p&gt;

&lt;p&gt;The solution is the &lt;code&gt;.sr-only&lt;/code&gt; (screen-reader only) CSS pattern. This keeps the element available to assistive technology while hiding it from sighted users.&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="c"&gt;/* Standard .sr-only utility for inclusive design */&lt;/span&gt;
&lt;span class="nc"&gt;.sr-only&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;white-space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;nowrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&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;h2&gt;
  
  
  Advanced Pattern: Managing Modals and Dialogs
&lt;/h2&gt;

&lt;p&gt;Routing isn't the only place where focus breaks. Modals are the most common source of accessibility failures. When a modal opens, focus must move &lt;em&gt;into&lt;/em&gt; the modal. When it closes, focus must return &lt;em&gt;exactly&lt;/em&gt; to the button that opened it.&lt;/p&gt;

&lt;p&gt;Failure to do this violates &lt;strong&gt;WCAG 2.1 2.4.3&lt;/strong&gt;. If you open a modal and the focus stays on the trigger button behind the modal's backdrop, the user is effectively trapped in a "ghost" layer.&lt;/p&gt;

&lt;p&gt;Here is a professional implementation of a focus-trapped modal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&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;AccessibleModal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onClose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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;modalRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&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;triggerRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Store the element that triggered the modal to return focus later&lt;/span&gt;
      &lt;span class="nx"&gt;triggerRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="c1"&gt;// Move focus to the modal container or the first focusable element&lt;/span&gt;
      &lt;span class="nx"&gt;modalRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Return focus to the trigger button when the modal closes&lt;/span&gt;
      &lt;span class="nx"&gt;triggerRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; 
      &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"modal-overlay"&lt;/span&gt; 
      &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"dialog"&lt;/span&gt; 
      &lt;span class="na"&gt;aria-modal&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; 
      &lt;span class="na"&gt;aria-labelledby&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"modal-title"&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; 
        &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;modalRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; 
        &lt;span class="na"&gt;tabIndex&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"-1"&lt;/span&gt; 
        &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"modal-content"&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"modal-title"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onClose&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Close&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&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;h2&gt;
  
  
  The "Keyboard Trap" and Focus Trapping
&lt;/h2&gt;

&lt;p&gt;Moving focus &lt;em&gt;into&lt;/em&gt; a modal is only half the battle. You must also prevent the user from Tabbing &lt;em&gt;out&lt;/em&gt; of the modal and back into the background page. This is called a "Keyboard Trap," and it's a critical failure.&lt;/p&gt;

&lt;p&gt;To solve this, you need to listen for the &lt;code&gt;Tab&lt;/code&gt; key and wrap the focus from the last focusable element back to the first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleKeyDown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tab&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&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;focusableElements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;modalRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])&lt;/span&gt;&lt;span class="dl"&gt;'&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;firstElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;focusableElements&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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;lastElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;focusableElements&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;focusableElements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shiftKey&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeElement&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;firstElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;lastElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shiftKey&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeElement&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;lastElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;firstElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&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;h2&gt;
  
  
  Testing Your Implementation
&lt;/h2&gt;

&lt;p&gt;You cannot verify focus management by looking at your code. You must test it. I recommend a three-tiered testing strategy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;The Keyboard-Only Test&lt;/strong&gt;: Put your mouse away. Navigate your entire application using only &lt;code&gt;Tab&lt;/code&gt;, &lt;code&gt;Shift+Tab&lt;/code&gt;, and &lt;code&gt;Enter&lt;/code&gt;. If you ever "lose" your place or have to Tab through the whole header to reach the content, your focus management is broken.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Screen Reader Test&lt;/strong&gt;: Use NVDA (Windows) or VoiceOver (macOS). Navigate through a route change. Does the screen reader immediately announce the page title? If it says "blank" or starts reading the navigation menu, you have a gap.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The &lt;code&gt;document.activeElement&lt;/code&gt; Audit&lt;/strong&gt;: Open your DevTools console and type &lt;code&gt;document.activeElement&lt;/code&gt; after a page transition. If it returns &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;, you are failing WCAG 2.1.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Summary of Best Practices for React Developers
&lt;/h2&gt;

&lt;p&gt;To ensure your application meets WCAG 2.1 and EN 301 549 compliance, follow these non-negotiable rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Route Changes&lt;/strong&gt;: Always move focus to the &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; or a visually hidden "Page Loaded" announcement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modal Entry&lt;/strong&gt;: Move focus to the modal container or the first interactive element immediately upon opening.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modal Exit&lt;/strong&gt;: Return focus to the element that triggered the modal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tab Index&lt;/strong&gt;: Use &lt;code&gt;tabIndex="-1"&lt;/code&gt; for programmatic focus targets that shouldn't be in the natural Tab order.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic HTML&lt;/strong&gt;: Use &lt;code&gt;role="dialog"&lt;/code&gt; and &lt;code&gt;aria-modal="true"&lt;/code&gt; for overlays to signal to screen readers that the rest of the page is inert.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Quick Reference Checklist
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;WCAG Criterion&lt;/th&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Page Transition&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; or Page Title&lt;/td&gt;
&lt;td&gt;2.4.3&lt;/td&gt;
&lt;td&gt;Predictable focus order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open Modal&lt;/td&gt;
&lt;td&gt;Modal Container&lt;/td&gt;
&lt;td&gt;2.4.3&lt;/td&gt;
&lt;td&gt;Focus moved to active content&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Close Modal&lt;/td&gt;
&lt;td&gt;Trigger Button&lt;/td&gt;
&lt;td&gt;2.4.3&lt;/td&gt;
&lt;td&gt;Restore previous context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Navigation&lt;/td&gt;
&lt;td&gt;Menu items&lt;/td&gt;
&lt;td&gt;2.1.1&lt;/td&gt;
&lt;td&gt;Full keyboard accessibility&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Focus management is often overlooked because it's "invisible" to most developers. But for a significant portion of your users, focus is the only way they can perceive the structure of your application. By implementing these patterns in React 19, you aren't just checking a compliance box—you are removing barriers and making your software usable for everyone.&lt;/p&gt;

&lt;p&gt;Stop treating accessibility as a "nice-to-have" cleanup task. Treat it as a technical requirement, just like performance or security.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What’s your biggest struggle with focus management in SPAs? Do you use a specific library for focus trapping, or do you prefer custom hooks? Let's discuss in the comments.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;About the Author:&lt;/strong&gt; Priya Nair is a Senior Frontend Developer and Accessibility Consultant based in Amsterdam. She specializes in WCAG 2.1 compliance and inclusive design patterns for high-growth startups. She is a contributor to several open-source accessibility libraries and a frequent speaker on the intersection of engineering and inclusion.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>javascript</category>
      <category>react</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Beyond the Div: Leveraging Semantic HTML to Solve Complex ARIA Failures in Modern React Applications</title>
      <dc:creator>Priya Nair</dc:creator>
      <pubDate>Thu, 18 Jun 2026 08:05:57 +0000</pubDate>
      <link>https://dev.to/priya_nair/beyond-the-div-leveraging-semantic-html-to-solve-complex-aria-failures-in-modern-react-applications-1d59</link>
      <guid>https://dev.to/priya_nair/beyond-the-div-leveraging-semantic-html-to-solve-complex-aria-failures-in-modern-react-applications-1d59</guid>
      <description>&lt;p&gt;&lt;strong&gt;Meta:&lt;/strong&gt; Stop over-using ARIA. Learn how semantic HTML solves complex accessibility failures in React and how to meet WCAG 2.1 AA without the ARIA overhead.&lt;/p&gt;

&lt;h1&gt;
  
  
  Beyond the Div: Leveraging Semantic HTML to Solve Complex ARIA Failures in Modern React Applications
&lt;/h1&gt;

&lt;p&gt;I remember a project I worked on a few years ago for a fintech client in Amsterdam. The team had built a beautiful, high-performance dashboard in React. Visually, it was stunning. But when I opened VoiceOver (a screen reader for macOS), the experience was a nightmare. &lt;/p&gt;

&lt;p&gt;The entire application was essentially a "div soup." Every button was a &lt;code&gt;div&lt;/code&gt; with an &lt;code&gt;onClick&lt;/code&gt; handler; every navigation link was a &lt;code&gt;span&lt;/code&gt; with a &lt;code&gt;cursor: pointer&lt;/code&gt;. To "fix" the accessibility, the developers had layered on a massive amount of ARIA (Accessible Rich Internet Applications) attributes. They had &lt;code&gt;role="button"&lt;/code&gt;, &lt;code&gt;tabIndex="0"&lt;/code&gt;, and complex &lt;code&gt;aria-labelledby&lt;/code&gt; relationships everywhere. &lt;/p&gt;

&lt;p&gt;On paper, the code looked "compliant." In practice, it was fragile. A single typo in an ID reference broke the relationship between a label and an input, and the keyboard navigation was inconsistent because they had to manually manage focus with &lt;code&gt;useEffect&lt;/code&gt; hooks. They were working ten times harder to simulate behavior that the browser provides for free.&lt;/p&gt;

&lt;p&gt;The most important lesson I learned from that project—and one I share in my "Accessibility Office Hours" every month—is this: &lt;strong&gt;ARIA is a polyfill for missing semantics. The best ARIA is no ARIA.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you can use a native HTML element to achieve your goal, do it. Every time you use a &lt;code&gt;div&lt;/code&gt; where a &lt;code&gt;button&lt;/code&gt; or &lt;code&gt;nav&lt;/code&gt; should be, you are opting out of decades of browser and assistive technology optimization.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "ARIA Paradox" in Modern Frameworks
&lt;/h2&gt;

&lt;p&gt;In the React ecosystem, we often get caught up in the "componentization" of everything. We create &lt;code&gt;&amp;lt;CustomButton /&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;CustomInput /&amp;gt;&lt;/code&gt; components. Because these are often wrapped in multiple layers of abstraction, developers tend to reach for ARIA roles to "tell" the screen reader what the component is.&lt;/p&gt;

&lt;p&gt;The paradox is that the more ARIA you add to a non-semantic element, the more likely you are to introduce a bug. ARIA doesn't change the behavior of an element; it only changes how it is announced. &lt;/p&gt;

&lt;p&gt;If you give a &lt;code&gt;div&lt;/code&gt; a &lt;code&gt;role="button"&lt;/code&gt;, the screen reader says "Button," but the browser still doesn't know that the &lt;code&gt;Enter&lt;/code&gt; key or the &lt;code&gt;Space&lt;/code&gt; bar should trigger a click event. You then have to write a custom &lt;code&gt;onKeyDown&lt;/code&gt; handler to handle those keys. If you forget one, you've just failed &lt;strong&gt;WCAG 2.1 Success Criterion 2.1.1 (Keyboard)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Compare that to a native &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;. It is focusable by default, it triggers on &lt;code&gt;Enter&lt;/code&gt; and &lt;code&gt;Space&lt;/code&gt; by default, and it is announced as a button by default. Zero lines of extra JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cost of "Div Soup"
&lt;/h2&gt;

&lt;p&gt;Let's look at a common failure pattern. Imagine a custom dropdown menu built with &lt;code&gt;div&lt;/code&gt; elements.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Wrong" Way (The ARIA Overload)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Fragile and over-engineered&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CustomDropdown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; 
      &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"combobox"&lt;/span&gt; 
      &lt;span class="na"&gt;aria-expanded&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; 
      &lt;span class="na"&gt;aria-haspopup&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"listbox"&lt;/span&gt; 
      &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"dropdown-container"&lt;/span&gt;
      &lt;span class="na"&gt;tabIndex&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
      &lt;span class="na"&gt;onKeyDown&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleKeyDown&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// Manually managing Enter/Space/Arrows&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      Select an option...
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"listbox"&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"options-list"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opt&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; 
            &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"option"&lt;/span&gt; 
            &lt;span class="na"&gt;tabIndex&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"-1"&lt;/span&gt; 
            &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"option-item"&lt;/span&gt;
            &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;selectOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&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;In the example above, we are fighting the browser. We have to manually manage &lt;code&gt;tabIndex&lt;/code&gt;, manually handle keyboard events, and manually track the &lt;code&gt;aria-expanded&lt;/code&gt; state. If the state logic fails, the screen reader user is left in a vacuum.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Right" Way (Semantic HTML)
&lt;/h3&gt;

&lt;p&gt;Now, let's look at how we achieve the same result using semantic HTML. While some complex patterns truly require ARIA, many "custom" components can be simplified.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ Robust, accessible, and lightweight&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SemanticDropdown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"field-wrapper"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"category-select"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Choose a category&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;select&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"category-select"&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opt&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;select&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&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;By using &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;option&amp;gt;&lt;/code&gt;, we satisfy &lt;strong&gt;WCAG 2.1 SC 4.1.2 (Name, Role, Value)&lt;/strong&gt; automatically. The browser handles the keyboard navigation, the focus management, and the screen reader announcement without a single line of custom ARIA.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solving Common ARIA Failures with Semantics
&lt;/h2&gt;

&lt;p&gt;Let's dive into three common areas where developers over-use ARIA and how to fix them using a semantic-first approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Clickable Elements
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Mistake:&lt;/strong&gt; Using &lt;code&gt;div&lt;/code&gt; or &lt;code&gt;span&lt;/code&gt; for buttons because "it's easier to style."&lt;br&gt;
&lt;strong&gt;The Failure:&lt;/strong&gt; Failure of &lt;strong&gt;WCAG 2.1 SC 2.1.1 (Keyboard)&lt;/strong&gt;. Users who rely on keyboards cannot tab to your "button," and users with screen readers may not realize the element is interactive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt; Use a &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;. If you hate the default browser styling, use CSS to reset it.&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="c"&gt;/* Resetting button styles to make it a blank canvas */&lt;/span&gt;
&lt;span class="nc"&gt;.btn-reset&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;inherit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;inherit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;inherit&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Use the reset class to get the look you want with the functionality you need&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"btn-reset"&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;doSomething&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  Click Me
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Navigation Landmarks
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Mistake:&lt;/strong&gt; Wrapping your site navigation in a &lt;code&gt;div&lt;/code&gt; and adding &lt;code&gt;role="navigation"&lt;/code&gt;.&lt;br&gt;
&lt;strong&gt;The Failure:&lt;/strong&gt; While technically compliant, it's redundant. If you forget the role on one page, you break the landmark structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt; Use the &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; element. It provides the landmark role implicitly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Instead of &amp;lt;div role="navigation"&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;nav&lt;/span&gt; &lt;span class="na"&gt;aria-label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Main Navigation"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/home"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Home&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/about"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;About&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;nav&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: Adding &lt;code&gt;aria-label&lt;/code&gt; to the &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; is important if you have multiple navigation blocks (e.g., Header and Footer) so users can distinguish between them.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Form Labels and Associations
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Mistake:&lt;/strong&gt; Using a &lt;code&gt;div&lt;/code&gt; as a label and trying to connect it using &lt;code&gt;aria-labelledby&lt;/code&gt;.&lt;br&gt;
&lt;strong&gt;The Failure:&lt;/strong&gt; If the ID is duplicated or missing, the association is lost. This violates &lt;strong&gt;WCAG 2.1 SC 1.3.1 (Info and Relationships)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt; Use the &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; element with the &lt;code&gt;htmlFor&lt;/code&gt; attribute.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Fragile&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"label-1"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Email Address&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"label-1"&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Robust&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email-field"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Email Address&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email-field"&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When is ARIA Actually Necessary?
&lt;/h2&gt;

&lt;p&gt;I don't want you to think ARIA is "evil." It is an incredibly powerful tool. However, it should be your &lt;strong&gt;last resort&lt;/strong&gt;, not your first step. &lt;/p&gt;

&lt;p&gt;You should use ARIA when:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;You are building a complex widget&lt;/strong&gt; that HTML doesn't have a native equivalent for (e.g., a Tab panel, a Tree view, or a Modal dialog).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need to communicate dynamic changes&lt;/strong&gt; that aren't visually obvious (e.g., using &lt;code&gt;aria-live&lt;/code&gt; to announce a successful form submission without moving the user's focus).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You are enhancing a native element&lt;/strong&gt; (e.g., adding &lt;code&gt;aria-invalid="true"&lt;/code&gt; to an input when a validation error occurs).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Even then, follow the &lt;strong&gt;First Rule of ARIA&lt;/strong&gt;: If you can use a native HTML element or attribute with the semantics and behavior you require already built-in, do so.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Workflow for React Developers
&lt;/h2&gt;

&lt;p&gt;If you're working in a fast-paced startup environment, you don't have time to read the entire WAI-ARIA specification every morning. Here is the workflow I use when building new components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Semantic Search:&lt;/strong&gt; Ask, "Is there an HTML element that does this?" (e.g., Instead of a &lt;code&gt;div&lt;/code&gt; with a click handler $\rightarrow$ &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;. Instead of a &lt;code&gt;div&lt;/code&gt; for a list $\rightarrow$ &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Keyboard Test:&lt;/strong&gt; Unplug your mouse. Can you reach the element? Can you trigger it? Does the focus indicator (the outline) clearly show where you are?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Screen Reader Audit:&lt;/strong&gt; Use VoiceOver (Mac) or NVDA (Windows). Does the element announce its purpose and state?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The ARIA Layer:&lt;/strong&gt; Only now, if the behavior is missing or the announcement is vague, add the minimum amount of ARIA required to bridge the gap.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Accessibility Essentials Checklist
&lt;/h2&gt;

&lt;p&gt;To make this actionable, here is a quick reference for your next PR review:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Instead of...&lt;/th&gt;
&lt;th&gt;Use...&lt;/th&gt;
&lt;th&gt;Why?&lt;/th&gt;
&lt;th&gt;WCAG Criteria&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;div onClick={...}&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Native focus &amp;amp; keyboard support&lt;/td&gt;
&lt;td&gt;2.1.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;div className="header"&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Structural landmark for screen readers&lt;/td&gt;
&lt;td&gt;1.3.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;span className="link"&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Proper navigation and SEO&lt;/td&gt;
&lt;td&gt;2.1.1 / 4.1.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;div role="main"&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Implicit landmark&lt;/td&gt;
&lt;td&gt;1.3.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; (as a list)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; / &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Announces number of items in the list&lt;/td&gt;
&lt;td&gt;1.3.1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Final Thoughts: Accessibility is a Business Requirement
&lt;/h2&gt;

&lt;p&gt;Some developers argue that using native elements limits their design flexibility. This is a myth. CSS is powerful enough to make a &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; look like anything—a pill, a square, a ghost button, or a complex icon. What you cannot "style" is the underlying accessibility tree that a screen reader relies on.&lt;/p&gt;

&lt;p&gt;When we prioritize semantic HTML, we aren't just "checking a box" for compliance. We are ensuring that a user with a motor impairment can navigate our site, a blind user can understand our layout, and a user with a cognitive disability can predict how our interface behaves.&lt;/p&gt;

&lt;p&gt;Building inclusive software isn't about adding features; it's about removing barriers. Start with the basics. Stop the "div soup." Let the browser do the heavy lifting.&lt;/p&gt;




&lt;h3&gt;
  
  
  Let's Discuss
&lt;/h3&gt;

&lt;p&gt;What is the most frustrating "custom" component you've encountered that should have just been a native HTML element? Or, have you found a specific case where ARIA was the only way to solve a problem? Let's talk in the comments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;About the Author:&lt;/strong&gt;&lt;br&gt;
Priya Nair is a Senior Frontend Developer and Accessibility Consultant based in Amsterdam. She specializes in WCAG 2.1 compliance and inclusive design patterns for React applications. She is a frequent speaker at a11y conferences and maintains several open-source accessibility checklists on GitHub.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>frontend</category>
      <category>html</category>
      <category>react</category>
    </item>
    <item>
      <title>Keyboard Navigation Testing: The 20-Minute Audit Any Developer Can Run</title>
      <dc:creator>Priya Nair</dc:creator>
      <pubDate>Wed, 15 Apr 2026 07:17:03 +0000</pubDate>
      <link>https://dev.to/priya_nair/keyboard-navigation-testing-the-20-minute-audit-any-developer-can-run-b6g</link>
      <guid>https://dev.to/priya_nair/keyboard-navigation-testing-the-20-minute-audit-any-developer-can-run-b6g</guid>
      <description>&lt;h1&gt;
  
  
  Keyboard Navigation Testing: The 20-Minute Audit Any Developer Can Run
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Meta: You don't need tools to test keyboard navigation. You just need your keyboard and 20 minutes. Here's the exact walkthrough I use.&lt;/p&gt;

&lt;p&gt;Keyword: keyboard navigation accessibility testing web&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;Tags: #accessibility #testing #wcag #webdev #a11y&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Keyboard Navigation Matters (More Than You Think)
&lt;/h2&gt;

&lt;p&gt;Before we get into the how-to, let me explain the why.&lt;/p&gt;

&lt;p&gt;Keyboard-only users aren't a tiny edge case. They include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Blind users&lt;/strong&gt; — Screen reader software doesn't control the mouse; it navigates via keyboard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Motor disabilities&lt;/strong&gt; — People with cerebral palsy, Parkinson's, or arthritis can't manipulate a mouse.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Power users&lt;/strong&gt; — Developers, writers, accessibility professionals who just prefer keyboard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Broken hands, surgery recovery&lt;/strong&gt; — You'd be surprised how often this happens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile users tabbing through a website&lt;/strong&gt; — Yes, mobile has Tab support in some browsers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So when a website breaks keyboard navigation, you're not just inconveniencing someone. You're locking them out.&lt;/p&gt;

&lt;p&gt;The good news? Testing keyboard navigation takes no special tools, no software, no budget. Just your keyboard and 20 minutes.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Only Tools You Need
&lt;/h2&gt;

&lt;p&gt;Your keyboard. Seriously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tab&lt;/strong&gt; — Move focus forward (one interactive element at a time)&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Shift+Tab&lt;/strong&gt; — Move focus backward&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Enter&lt;/strong&gt; — Activate a button or link&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Space&lt;/strong&gt; — Activate a button or toggle a checkbox&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Arrow Keys&lt;/strong&gt; — Navigate within components (menu items, radio buttons, slider)&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Escape&lt;/strong&gt; — Close modals, collapse menus  &lt;/p&gt;

&lt;p&gt;That's it. You don't need browser extensions, accessibility tools, or a screen reader. Just these keys.&lt;/p&gt;


&lt;h2&gt;
  
  
  The 20-Minute Keyboard Audit: Step-by-Step
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Step 1: The Tab Test (3 minutes)
&lt;/h3&gt;

&lt;p&gt;Open your site in a fresh browser tab. Press &lt;strong&gt;Tab&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Did a focus outline appear? If not, you already found a problem.&lt;/p&gt;

&lt;p&gt;Now keep pressing Tab. Watch the focus move through the page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Questions to answer:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does focus appear as a visible outline, border, or shadow around each element?&lt;/li&gt;
&lt;li&gt;Does focus move &lt;em&gt;logically&lt;/em&gt; through the page? (Left to right, top to bottom, roughly in reading order?)&lt;/li&gt;
&lt;li&gt;Does focus move through &lt;em&gt;every&lt;/em&gt; interactive element? (Buttons, links, inputs, dropdowns?)&lt;/li&gt;
&lt;li&gt;Are there any elements where focus gets stuck or skips unexpectedly?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What to look for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you can't see focus, that's a problem. The page fails WCAG 2.4.13 immediately.&lt;/li&gt;
&lt;li&gt;If focus jumps around randomly (skips to the footer, then back to the nav), there's a &lt;code&gt;tabindex&lt;/code&gt; problem.&lt;/li&gt;
&lt;li&gt;If you reach an element and focus disappears, there's a z-index or visibility bug.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Quick fix:&lt;/strong&gt; If focus is invisible, the site probably has &lt;code&gt;outline: none;&lt;/code&gt; in the CSS. That needs to be removed or replaced with a visible style.&lt;/p&gt;


&lt;h3&gt;
  
  
  Step 2: The "Where Am I?" Test (2 minutes)
&lt;/h3&gt;

&lt;p&gt;While you're tabbing through, ask yourself: &lt;strong&gt;Can I tell where focus is?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is different from step 1. It's not "does focus exist"—it's "is it obvious enough?"&lt;/p&gt;

&lt;p&gt;If the focus indicator is a 1px gray line on a gray background, technically it exists. But in practice, you can't see it. That's a failure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pass criteria:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can see the focus indicator without squinting.&lt;/li&gt;
&lt;li&gt;The focus indicator has decent contrast (at least 3:1 ratio against the background).&lt;/li&gt;
&lt;li&gt;It's at least 2px thick or surrounded by spacing (like a 3px box-shadow).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Quick fix:&lt;/strong&gt; If focus is hard to see, add this to your CSS:&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="o"&gt;*&lt;/span&gt;&lt;span class="nd"&gt;:focus-visible&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#0066ff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;outline-offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&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;Not perfect, but it works as a baseline.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 3: The Button Activation Test (2 minutes)
&lt;/h3&gt;

&lt;p&gt;Keep tabbing. When you reach a button, press &lt;strong&gt;Enter&lt;/strong&gt;. When you reach a button-like link, press &lt;strong&gt;Enter&lt;/strong&gt; too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Questions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the button activate? (Page submits, modal opens, action completes?)&lt;/li&gt;
&lt;li&gt;Do you need to press Space instead of Enter? (Shouldn't happen—buttons should respond to Enter.)&lt;/li&gt;
&lt;li&gt;What about toggle buttons or checkboxes? Do they respond to Space?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What's broken:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A button that requires clicking, doesn't respond to Enter → failure&lt;/li&gt;
&lt;li&gt;A custom &lt;code&gt;&amp;lt;div role="button"&amp;gt;&lt;/code&gt; that doesn't respond to keyboard → failure&lt;/li&gt;
&lt;li&gt;A form input that requires mouse interaction → failure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Quick reference:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; → Enter&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;input type="checkbox"&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;input type="radio"&amp;gt;&lt;/code&gt; → Space (and Arrow keys for radio groups)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;input type="submit"&amp;gt;&lt;/code&gt; → Enter&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Step 4: The Modal Dialog Test (3 minutes)
&lt;/h3&gt;

&lt;p&gt;If your site has a modal (popup, dialog, overlay), test it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tab into the modal:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does focus enter the modal?&lt;/li&gt;
&lt;li&gt;Can you tab through all the buttons/inputs &lt;em&gt;inside&lt;/em&gt; the modal?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Escape to close:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Press Escape. Does the modal close?&lt;/li&gt;
&lt;li&gt;After it closes, does focus return to the element that opened it? (Not always required, but it's nice.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Focus trap:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you Tab to the last button in the modal, does Tab again wrap back to the first button? (This is called a focus trap—it prevents you from tabbing out to the page behind.)&lt;/li&gt;
&lt;li&gt;Focus traps are okay if they're intentional (some accessibility guidelines recommend them for modals). But you need to be able to close with Escape.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Test scenario:&lt;/strong&gt;&lt;br&gt;
Open a modal. Tab through every element. Press Escape. Does the modal close? Good. Now open it again and try to Tab out of it using the last button. If you can't escape without pressing Escape, that's a trap. It might be intentional, but it should be documented or clearly closable.&lt;/p&gt;


&lt;h3&gt;
  
  
  Step 5: The Dropdown &amp;amp; Menu Test (3 minutes)
&lt;/h3&gt;

&lt;p&gt;If you have navigation menus or dropdown selects, test them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Navigation menu with submenus:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tab to the menu trigger.&lt;/li&gt;
&lt;li&gt;Does the menu open automatically? (Should, or should require Enter.)&lt;/li&gt;
&lt;li&gt;Can you Arrow-key down to submenu items?&lt;/li&gt;
&lt;li&gt;Arrow-left and Arrow-right to navigate between menus?&lt;/li&gt;
&lt;li&gt;Escape to close?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Native HTML &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; dropdown:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tab to it. Arrow-key down to see options. Very smooth.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Custom dropdowns built with &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; and JavaScript:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is where bugs hide. Test Tab to open, Arrow keys to navigate, Enter to select, Escape to close.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What's broken:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Custom dropdown that requires clicking to open (arrow keys don't work) → failure&lt;/li&gt;
&lt;li&gt;Dropdown menu that disappears when you blur it, trapping you if you Tab into it → bad pattern&lt;/li&gt;
&lt;/ul&gt;


&lt;h3&gt;
  
  
  Step 6: The Form Completion Test (3 minutes)
&lt;/h3&gt;

&lt;p&gt;Fill out a form entirely using only your keyboard.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tab through each field.&lt;/li&gt;
&lt;li&gt;Type in text inputs.&lt;/li&gt;
&lt;li&gt;Tab to checkboxes and press Space to check them.&lt;/li&gt;
&lt;li&gt;Tab to radio buttons and use Arrow keys to select.&lt;/li&gt;
&lt;li&gt;Tab to a submit button and press Enter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Questions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can you reach every input?&lt;/li&gt;
&lt;li&gt;Can you check/uncheck everything?&lt;/li&gt;
&lt;li&gt;Can you submit the form?&lt;/li&gt;
&lt;li&gt;Do error messages appear? If so, can you navigate to them?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What's broken:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A required field that's keyboard-inaccessible (hidden &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; with no label) → failure&lt;/li&gt;
&lt;li&gt;A form that auto-submits on blur → annoying and potentially broken&lt;/li&gt;
&lt;li&gt;A date picker that requires a mouse click to open → failure (should allow keyboard or Tab to an input)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Quick tip:&lt;/strong&gt; Check that every &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; has an associated &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt;:&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="c"&gt;&amp;lt;!-- Good --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Email&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Bad --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Email"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Step 7: The Skip Link Test (1 minute)
&lt;/h3&gt;

&lt;p&gt;This is the fastest test. Reload the page and press Tab immediately.&lt;/p&gt;

&lt;p&gt;Does a skip link appear? Something like "Skip to main content"?&lt;/p&gt;

&lt;p&gt;If yes, click it. Does it take you to the main content, skipping the navigation?&lt;/p&gt;

&lt;p&gt;If no, that's a miss. You should have one. It takes 30 seconds to add.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add a skip link:&lt;/strong&gt;&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;"#main-content"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"skip-link"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Skip to main content&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;nav&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- navigation --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;main&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"main-content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- content --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.skip-link&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-40px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text-decoration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.skip-link&lt;/span&gt;&lt;span class="nd"&gt;:focus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&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;h2&gt;
  
  
  The Issues You'll Find (And How to Fix Them)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Issue 1: No Focus Indicator (Most Common)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; You Tab and can't see where focus is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; CSS has &lt;code&gt;outline: none;&lt;/code&gt; or &lt;code&gt;box-shadow: none;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&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="c"&gt;/* Remove this: */&lt;/span&gt;
&lt;span class="nt"&gt;outline&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;none&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c"&gt;/* Replace with visible focus (or don't remove it at all) */&lt;/span&gt;
&lt;span class="nd"&gt;:focus-visible&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#0066ff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;outline-offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&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;h3&gt;
  
  
  Issue 2: Focus Getting Lost
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; You Tab through a page, focus disappears somewhere in the middle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; JavaScript is setting focus to a hidden element, or z-index is hiding focus.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Use dev tools to inspect. Check if the focused element is hidden or has &lt;code&gt;display: none&lt;/code&gt;. If it does, don't let focus go there. Add JavaScript to skip hidden elements:&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="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;focus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;nextElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&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;h3&gt;
  
  
  Issue 3: Tabindex Chaos
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; You Tab and jump around the page in weird order (jumps to footer, back to header, etc.).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; HTML has &lt;code&gt;tabindex="2"&lt;/code&gt;, &lt;code&gt;tabindex="5"&lt;/code&gt;, &lt;code&gt;tabindex="1"&lt;/code&gt; in random order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Delete all positive &lt;code&gt;tabindex&lt;/code&gt; values. Use only &lt;code&gt;tabindex="-1"&lt;/code&gt; (remove from tab order) or no tabindex at all. Let the browser handle order based on HTML structure.&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="c"&gt;&amp;lt;!-- ❌ Don't do this --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;tabindex=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Third&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;tabindex=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;First&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;tabindex=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Second&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- ✅ Do this (leave out tabindex) --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;First&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;Second&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;Third&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Issue 4: Modal Without Escape
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Modal opens, you Tab through buttons, but can't close with Escape.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Modal doesn't listen for Escape key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Escape&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;closeModal&lt;/span&gt;&lt;span class="p"&gt;();&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;h3&gt;
  
  
  Issue 5: Custom Interactive Element Without Keyboard Handler
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; You Tab to a custom &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; that &lt;em&gt;looks&lt;/em&gt; like a button, but Enter doesn't work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; It's missing keyboard event listeners.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&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;customButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[role="button"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;customButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Enter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;customButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// Trigger the click handler&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;Or better: just use a real &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; element.&lt;/p&gt;

&lt;h3&gt;
  
  
  Issue 6: Hidden Focus Indicator
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Focus exists, but it's invisible (1px, same color as background, etc.).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; CSS is too minimal or background color is the same.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&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="c"&gt;/* ❌ Invisible */&lt;/span&gt;
&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="nd"&gt;:focus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#ccc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c"&gt;/* Gray on gray = invisible */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* ✅ Visible */&lt;/span&gt;
&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="nd"&gt;:focus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#0066ff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c"&gt;/* Blue on any background = visible */&lt;/span&gt;
  &lt;span class="nl"&gt;outline-offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&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;h2&gt;
  
  
  The Audit Checklist (Your Quick Reference)
&lt;/h2&gt;

&lt;p&gt;Print this. Use it. Update it as you find issues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keyboard Navigation Audit&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Tab moves focus through the page in logical order&lt;/li&gt;
&lt;li&gt;[ ] Focus indicator is visible on every element&lt;/li&gt;
&lt;li&gt;[ ] Focus indicator has adequate contrast (3:1 minimum)&lt;/li&gt;
&lt;li&gt;[ ] All buttons respond to Enter key&lt;/li&gt;
&lt;li&gt;[ ] All toggles/checkboxes respond to Space key&lt;/li&gt;
&lt;li&gt;[ ] Form can be filled entirely with keyboard&lt;/li&gt;
&lt;li&gt;[ ] Modal closes with Escape key&lt;/li&gt;
&lt;li&gt;[ ] Dropdown/menu navigable with Arrow keys&lt;/li&gt;
&lt;li&gt;[ ] Skip link appears on Tab and works&lt;/li&gt;
&lt;li&gt;[ ] Focus doesn't get lost in hidden elements&lt;/li&gt;
&lt;li&gt;[ ] No positive &lt;code&gt;tabindex&lt;/code&gt; values (chaos)&lt;/li&gt;
&lt;li&gt;[ ] Custom interactive elements have full keyboard support&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Things I Always Find Broken
&lt;/h2&gt;

&lt;p&gt;After testing hundreds of sites, here's what I see constantly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;outline: none;&lt;/code&gt; without replacement&lt;/strong&gt; — Most common. Developers remove outlines for aesthetics and forget to add focus styling.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Modal that doesn't close with Escape&lt;/strong&gt; — Second most common. Looks polished, completely breaks keyboard users.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tabindex all over the place&lt;/strong&gt; — Someone tried to fix tab order with &lt;code&gt;tabindex="2"&lt;/code&gt; and now it's a maze.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Icon-only buttons with no aria-label&lt;/strong&gt; — Keyboard users can Tab to them, but don't know what they do.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Custom select dropdowns that don't use Arrow keys&lt;/strong&gt; — Built with JavaScript, requires mouse or clicking.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Focus trap without escape in a modal&lt;/strong&gt; — You get stuck. Only way out is Escape or clicking outside.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  How to Document Your Findings
&lt;/h2&gt;

&lt;p&gt;When you find issues, note them somewhere. Here's a simple template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Keyboard Navigation Test — [Date]&lt;/span&gt;

&lt;span class="gu"&gt;### Pass&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Focus indicators visible and clear
&lt;span class="p"&gt;-&lt;/span&gt; Form submission works with keyboard
&lt;span class="p"&gt;-&lt;/span&gt; Skip link present

&lt;span class="gu"&gt;### Fail&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Modal lacks Escape key handler (2026-04-06)
&lt;span class="p"&gt;-&lt;/span&gt; Dropdown menu requires click to open (2026-04-06)

&lt;span class="gu"&gt;### Warnings&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Focus indicator very thin (meets minimum, could be stronger)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Share this with your team. It's quick, actionable, and non-judgmental.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Value
&lt;/h2&gt;

&lt;p&gt;Here's the thing about keyboard testing: it takes 20 minutes and catches issues that automated tools miss. A linter won't tell you that your focus trap is a pain to use. A scanner won't know that your custom dropdown is unusable without a mouse.&lt;/p&gt;

&lt;p&gt;Your fingers will.&lt;/p&gt;

&lt;p&gt;So grab your keyboard, put your mouse aside, and actually try to use your site the way millions of people do every day. You'll be shocked at what you find. And once you fix those issues, your site will work better for &lt;em&gt;everyone&lt;/em&gt;—not just keyboard users.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Priya Nair&lt;/strong&gt; is a frontend developer and accessibility advocate based in Amsterdam. She spends way too much time testing sites with just her keyboard and thinks you should too. Her favorite debugging tool is her Tab key.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>testing</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>ARIA Labels Done Wrong: The Most Common Mistakes I See in Production Code</title>
      <dc:creator>Priya Nair</dc:creator>
      <pubDate>Thu, 09 Apr 2026 10:32:01 +0000</pubDate>
      <link>https://dev.to/priya_nair/aria-labels-done-wrong-the-most-common-mistakes-i-see-in-production-code-39ga</link>
      <guid>https://dev.to/priya_nair/aria-labels-done-wrong-the-most-common-mistakes-i-see-in-production-code-39ga</guid>
      <description>&lt;h1&gt;
  
  
  ARIA Labels Done Wrong: The Most Common Mistakes I See in Production Code
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Meta: ARIA is powerful but dangerous when misused. Here are the 7 mistakes I see constantly—and how to fix them.&lt;/p&gt;

&lt;p&gt;Keyword: ARIA labels accessibility mistakes common&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;Tags: #accessibility #aria #wcag #webdev #a11y&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Golden Rule of ARIA (And Why Most People Break It)
&lt;/h2&gt;

&lt;p&gt;Before we talk about mistakes, let me say this clearly:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No ARIA is better than bad ARIA.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ARIA (Accessible Rich Internet Applications) is a powerful toolset for filling accessibility gaps. But when you use it wrong—and I mean &lt;em&gt;really wrong&lt;/em&gt;—it makes things worse for screen reader users, not better.&lt;/p&gt;

&lt;p&gt;I've audited hundreds of codebases. I'd estimate 70% of the ARIA I see is either unnecessary or broken. And when a screen reader user encounters broken ARIA, they don't get a degraded experience. They get a &lt;em&gt;confusing&lt;/em&gt; one.&lt;/p&gt;

&lt;p&gt;So here's the deal: I'm going to walk you through the most common mistakes I see, why they're problems, and how to fix them. By the end, you'll know when to use ARIA and when to just use HTML.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake 1: aria-label on a Div Instead of Using Semantic HTML
&lt;/h2&gt;

&lt;p&gt;This is the #1 mistake. By far.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The pattern:&lt;/strong&gt; Someone needs a button. Instead of using &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;, they use a styled &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; and add &lt;code&gt;aria-label&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it's wrong:&lt;/strong&gt; A &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; is invisible to screen readers and keyboard navigation. No amount of ARIA can change that. You're building an accessibility shim on top of an inaccessible foundation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ❌ This is broken, even with aria-label --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; 
  &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Submit form"&lt;/span&gt; 
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
  &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"submitForm()"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Submit
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- What a screen reader hears: "Submit" (as text, not a button) --&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- What keyboard users can do: Nothing (not focusable) --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Good example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ✅ Use semantic HTML --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"submitForm()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- OR if you need a link styled as a button: --&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;"/submit"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Screen reader: "Submit, button" --&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- Keyboard: Focusable with Tab, activatable with Enter --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; Screen reader users need to know it's a button. Keyboard users need to be able to reach it with Tab and activate it with Enter or Space. A bare &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; does neither. ARIA's &lt;code&gt;role="button"&lt;/code&gt; &lt;em&gt;partially&lt;/em&gt; helps, but you still need to add keyboard listeners manually:&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="c1"&gt;// If you insist on using a div (you shouldn't):&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fakeButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[role="button"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;fakeButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Enter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;fakeButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&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;&lt;strong&gt;One-liner:&lt;/strong&gt; Use &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; before you even think about &lt;code&gt;aria-label&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake 2: aria-label Duplicating Visible Text
&lt;/h2&gt;

&lt;p&gt;This one wastes screen reader users' time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The pattern:&lt;/strong&gt; You have a button with visible text. You add &lt;code&gt;aria-label&lt;/code&gt; with the same text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ❌ Redundant --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Click to download file"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Download
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Screen reader: "Click to download file, button" (verbose, redundant) --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it's wrong:&lt;/strong&gt; If the button text is clear, you don't need &lt;code&gt;aria-label&lt;/code&gt;. Screen readers already announce the text. You're just making them say it twice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ✅ Let the text speak for itself --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;Download&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- OR, if you need to clarify: --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;Download Invoice PDF&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Screen reader: "Download Invoice PDF, button" (clear, not redundant) --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When aria-label IS necessary:&lt;/strong&gt; When you have an icon-only button (see Mistake 3).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-liner:&lt;/strong&gt; Don't use &lt;code&gt;aria-label&lt;/code&gt; to repeat visible text. Use it to &lt;em&gt;clarify&lt;/em&gt; invisible intent.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake 3: Icon-Only Buttons Without aria-label (Or ARIA)
&lt;/h2&gt;

&lt;p&gt;This is the flip side of Mistake 2. You have a button with &lt;em&gt;only&lt;/em&gt; an icon. No text. No ARIA. Screen readers have no idea what it does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ❌ Icon-only, no label --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"close-button"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"icon-x"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Screen reader: "Button" (what button? who knows?) --&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- ❌ Or worse: --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hamburger"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 24 24"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"24"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"24"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;line&lt;/span&gt; &lt;span class="na"&gt;x1=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="na"&gt;y1=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt; &lt;span class="na"&gt;x2=&lt;/span&gt;&lt;span class="s"&gt;"21"&lt;/span&gt; &lt;span class="na"&gt;y2=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;line&lt;/span&gt; &lt;span class="na"&gt;x1=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="na"&gt;y1=&lt;/span&gt;&lt;span class="s"&gt;"12"&lt;/span&gt; &lt;span class="na"&gt;x2=&lt;/span&gt;&lt;span class="s"&gt;"21"&lt;/span&gt; &lt;span class="na"&gt;y2=&lt;/span&gt;&lt;span class="s"&gt;"12"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;line&lt;/span&gt; &lt;span class="na"&gt;x1=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="na"&gt;y1=&lt;/span&gt;&lt;span class="s"&gt;"18"&lt;/span&gt; &lt;span class="na"&gt;x2=&lt;/span&gt;&lt;span class="s"&gt;"21"&lt;/span&gt; &lt;span class="na"&gt;y2=&lt;/span&gt;&lt;span class="s"&gt;"18"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Screen reader: "Button" (again, meaningless) --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Good example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ✅ Icon + aria-label --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Close dialog"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"close-button"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"icon-x"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Screen reader: "Close dialog, button" (clear) --&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- ✅ Icon + aria-label + title (tooltip bonus) --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Open navigation menu"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hamburger"&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Menu"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 24 24"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"24"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"24"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;line&lt;/span&gt; &lt;span class="na"&gt;x1=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="na"&gt;y1=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt; &lt;span class="na"&gt;x2=&lt;/span&gt;&lt;span class="s"&gt;"21"&lt;/span&gt; &lt;span class="na"&gt;y2=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;line&lt;/span&gt; &lt;span class="na"&gt;x1=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="na"&gt;y1=&lt;/span&gt;&lt;span class="s"&gt;"12"&lt;/span&gt; &lt;span class="na"&gt;x2=&lt;/span&gt;&lt;span class="s"&gt;"21"&lt;/span&gt; &lt;span class="na"&gt;y2=&lt;/span&gt;&lt;span class="s"&gt;"12"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;line&lt;/span&gt; &lt;span class="na"&gt;x1=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="na"&gt;y1=&lt;/span&gt;&lt;span class="s"&gt;"18"&lt;/span&gt; &lt;span class="na"&gt;x2=&lt;/span&gt;&lt;span class="s"&gt;"21"&lt;/span&gt; &lt;span class="na"&gt;y2=&lt;/span&gt;&lt;span class="s"&gt;"18"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Screen reader: "Open navigation menu, button" --&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- Hover tooltip: "Menu" --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Real-world checklist:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Is the button icon-only (no text)? If yes, add &lt;code&gt;aria-label&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;[ ] Does the &lt;code&gt;aria-label&lt;/code&gt; describe what happens? If yes, you're good.&lt;/li&gt;
&lt;li&gt;[ ] Do sighted users also understand? (Can't rely on icon alone.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;One-liner:&lt;/strong&gt; Every icon-only button needs &lt;code&gt;aria-label&lt;/code&gt;, &lt;code&gt;aria-labelledby&lt;/code&gt;, or text inside the button.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake 4: aria-labelledby Pointing to an ID That Doesn't Exist
&lt;/h2&gt;

&lt;p&gt;This is a silent killer. No error. No warning. Just broken accessibility.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ❌ ID doesn't exist --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"form-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Account Settings&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Later... --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"nonexistent-id"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;  &lt;span class="c"&gt;&amp;lt;!-- Wrong ID! --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Email&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Screen reader: "Form" (no context) --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Good example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ✅ ID exists and is used correctly --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"form-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Account Settings&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"form-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;  &lt;span class="c"&gt;&amp;lt;!-- Correct ID --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Email&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Screen reader: "Account Settings, form" (context clear) --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How to debug:&lt;/strong&gt; Open Chrome DevTools → Accessibility panel. It'll warn you if an ID is broken.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-liner:&lt;/strong&gt; Always check that the ID in &lt;code&gt;aria-labelledby&lt;/code&gt; actually exists in your HTML.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake 5: role="button" on a Div Without Keyboard Handling
&lt;/h2&gt;

&lt;p&gt;This is the classic "I tried to fix it but didn't finish" pattern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ❌ Has role, but no keyboard support --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"doSomething()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Click me
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Screen reader: "Click me, button" (sounds accessible) --&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- Keyboard user: Tabs right past it (not focusable) --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Good example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ✅ Full keyboard support --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; 
  &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; 
  &lt;span class="na"&gt;tabindex=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;  &lt;span class="err"&gt;&amp;lt;!&lt;/span&gt;&lt;span class="na"&gt;--&lt;/span&gt; &lt;span class="na"&gt;Make&lt;/span&gt; &lt;span class="na"&gt;it&lt;/span&gt; &lt;span class="na"&gt;focusable&lt;/span&gt; &lt;span class="na"&gt;--&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  onclick="doSomething()"
  onkeydown="handleKeydown(event)"  &lt;span class="c"&gt;&amp;lt;!-- Handle keyboard --&amp;gt;&lt;/span&gt;
&amp;gt;
  Click me
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleKeydown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Enter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- OR, even better: --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"doSomething()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Click me&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The real talk:&lt;/strong&gt; If you're adding &lt;code&gt;role="button"&lt;/code&gt;, you're probably doing something wrong. You should be using a &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;. &lt;code&gt;role="button"&lt;/code&gt; is for when you have a &lt;em&gt;very good reason&lt;/em&gt; to not use HTML buttons (like custom styling that breaks them, which is rare).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-liner:&lt;/strong&gt; If you use &lt;code&gt;role="button"&lt;/code&gt;, you must also add &lt;code&gt;tabindex="0"&lt;/code&gt; and keyboard event listeners. (But really, just use &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;.)&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake 6: Hiding Content With aria-hidden="true" That Users Can Still Reach
&lt;/h2&gt;

&lt;p&gt;This one creates a horrible experience: keyboard users can reach an element, but screen readers can't see it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ❌ aria-hidden but still focusable --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"doSomething()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Hidden Action
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Screen reader: Ignores the button entirely --&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- Keyboard user: Tabs to it, tries to interact, gets confused --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When this happens:&lt;/strong&gt; Usually with decorative elements that accidentally got tabindex, or off-canvas menus that aren't fully hidden.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ✅ Truly hidden: not visible, not focusable, not announced --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; 
  &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
  &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"display: none;"&lt;/span&gt;  &lt;span class="err"&gt;&amp;lt;!&lt;/span&gt;&lt;span class="na"&gt;--&lt;/span&gt; &lt;span class="na"&gt;Remove&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt; &lt;span class="na"&gt;document&lt;/span&gt; &lt;span class="na"&gt;flow&lt;/span&gt; &lt;span class="na"&gt;--&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&amp;gt;
  Decorative icon
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- ✅ OR: Off-canvas menu that's actually invisible --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;nav&lt;/span&gt; 
  &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"position: fixed; left: -300px;"&lt;/span&gt;  &lt;span class="err"&gt;&amp;lt;!&lt;/span&gt;&lt;span class="na"&gt;--&lt;/span&gt; &lt;span class="na"&gt;Off-screen&lt;/span&gt; &lt;span class="na"&gt;--&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  aria-hidden="true"
&amp;gt;
  Menu items
&lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- ✅ OR: Use visibility + aria-hidden for keyboard access but screen reader hiding --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; 
  &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"visibility: hidden;"&lt;/span&gt;
  &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Invisible to all users
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Test:&lt;/strong&gt; Inspect the element in DevTools. Is it visible? Is it focusable? If both are true but &lt;code&gt;aria-hidden="true"&lt;/code&gt; is set, that's a problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-liner:&lt;/strong&gt; Only use &lt;code&gt;aria-hidden="true"&lt;/code&gt; on elements that are actually invisible/non-interactive.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake 7: Dynamic Content Updates Not Announced (Missing aria-live)
&lt;/h2&gt;

&lt;p&gt;A screen reader user is on your page. New content loads via JavaScript. The screen reader has no idea. User keeps reading old content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ❌ Content updates, but no announcement --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"notifications"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Initially empty --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;// New notification arrives&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;notification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Order #12345 shipped!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;notifications&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Screen reader: *crickets* (no announcement)&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Good example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ✅ Content updates AND is announced --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"notifications"&lt;/span&gt; &lt;span class="na"&gt;aria-live=&lt;/span&gt;&lt;span class="s"&gt;"polite"&lt;/span&gt; &lt;span class="na"&gt;aria-atomic=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Initially empty --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;notification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Order #12345 shipped!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;notifications&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Screen reader: "Order #12345 shipped!" (announced immediately)&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- ✅ Or, for urgent updates: --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"alerts"&lt;/span&gt; &lt;span class="na"&gt;aria-live=&lt;/span&gt;&lt;span class="s"&gt;"assertive"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"alert"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Errors, warnings, urgent messages --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;// This gets announced immediately, interrupting other content&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alerts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your session will expire in 2 minutes.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;aria-live options:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;aria-live="polite"&lt;/code&gt; — Announce when convenient (after current speech)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aria-live="assertive"&lt;/code&gt; — Announce immediately (interrupts current speech)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;role="alert"&lt;/code&gt; — Shorthand for &lt;code&gt;aria-live="assertive" aria-atomic="true"&lt;/code&gt; (errors, warnings)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;role="status"&lt;/code&gt; — Shorthand for &lt;code&gt;aria-live="polite" aria-atomic="true"&lt;/code&gt; (status updates)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real-world examples:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Form validation errors → use &lt;code&gt;role="alert"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;"Saved successfully" message → use &lt;code&gt;role="status"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Real-time notifications → use &lt;code&gt;aria-live="polite"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;One-liner:&lt;/strong&gt; If content loads dynamically, wrap it in &lt;code&gt;aria-live&lt;/code&gt; so screen readers know it's there.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Test: Chrome DevTools
&lt;/h2&gt;

&lt;p&gt;Here's the fastest way to catch most ARIA mistakes.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open your site in Chrome.&lt;/li&gt;
&lt;li&gt;Open DevTools (F12).&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Elements&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;Right-click any element → &lt;strong&gt;Inspect accessibility properties&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Look at the &lt;strong&gt;Computed Name&lt;/strong&gt; section.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;What you're checking:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the element have a name? (If it's interactive, it should.)&lt;/li&gt;
&lt;li&gt;Is the name what you intended? (Not "button" or blank.)&lt;/li&gt;
&lt;li&gt;Are there warnings? (Red warning icon = problem.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Element: &amp;lt;button&amp;gt;
Computed Name: "Submit"
Role: button
Warnings: None ✓

---

Element: &amp;lt;div role="button" aria-label="close"&amp;gt;
Computed Name: "close"
Role: button
Warnings: None ✓

---

Element: &amp;lt;button aria-label="nonexistent-id"&amp;gt;
Computed Name: (empty)
Role: button
Warnings: ⚠️ aria-labelledby points to non-existent element
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Testing With a Real Screen Reader
&lt;/h2&gt;

&lt;p&gt;If Chrome DevTools isn't enough, test with an actual screen reader.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On Mac:&lt;/strong&gt; VoiceOver is built-in. Turn it on: System Preferences → Accessibility → VoiceOver. Press Ctrl+Option+U to start.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On Windows:&lt;/strong&gt; NVDA is free. Download from &lt;a href="https://www.nvaccess.org/" rel="noopener noreferrer"&gt;nvaccess.org&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listen for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the button announcement sound right? (Not just "button," but "submit button" or "close dialog"?)&lt;/li&gt;
&lt;li&gt;Can you navigate to all interactive elements?&lt;/li&gt;
&lt;li&gt;Do new notifications get announced?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It takes 10 minutes to learn the basics. Highly worth it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Quick Fix Checklist
&lt;/h2&gt;

&lt;p&gt;Running through a codebase? Use this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] No &lt;code&gt;&amp;lt;div role="button"&amp;gt;&lt;/code&gt; without &lt;code&gt;tabindex="0"&lt;/code&gt; and keyboard handlers&lt;/li&gt;
&lt;li&gt;[ ] Every &lt;code&gt;aria-label&lt;/code&gt; / &lt;code&gt;aria-labelledby&lt;/code&gt; has a real target&lt;/li&gt;
&lt;li&gt;[ ] Icon-only buttons have &lt;code&gt;aria-label&lt;/code&gt; or text&lt;/li&gt;
&lt;li&gt;[ ] No &lt;code&gt;aria-hidden="true"&lt;/code&gt; on focusable elements&lt;/li&gt;
&lt;li&gt;[ ] Dynamic content has &lt;code&gt;aria-live&lt;/code&gt; or &lt;code&gt;role="alert"&lt;/code&gt; / &lt;code&gt;role="status"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] ARIA attributes only add information, not replace semantic HTML&lt;/li&gt;
&lt;li&gt;[ ] Form fields have associated &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; elements&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ARIA Violations I See Most
&lt;/h2&gt;

&lt;p&gt;After auditing 200+ projects:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;aria-label duplicating text&lt;/strong&gt; (70% of aria-label misuse)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;role="button" without keyboard&lt;/strong&gt; (30% of role misuse)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;aria-hidden hiding interactive content&lt;/strong&gt; (25% of aria-hidden issues)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing aria-live on dynamic updates&lt;/strong&gt; (50% of single-page apps)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;aria-labelledby pointing to nothing&lt;/strong&gt; (15% of aria-labelledby)&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Real Rule
&lt;/h2&gt;

&lt;p&gt;Here's how to think about ARIA:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ARIA is for semantics and live regions. Not for fixing broken HTML.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're tempted to use ARIA, ask yourself first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is there a semantic HTML element that does this? (Usually yes.)&lt;/li&gt;
&lt;li&gt;Is the content actually hidden/dynamic? (If not, probably doesn't need ARIA.)&lt;/li&gt;
&lt;li&gt;Are screen reader users getting accurate information? (Test it.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of the time, better HTML solves the problem faster than ARIA ever could.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources (All Free, Official)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MDN ARIA documentation&lt;/strong&gt; — &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA" rel="noopener noreferrer"&gt;developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WAI-ARIA Authoring Practices Guide&lt;/strong&gt; — &lt;a href="https://www.w3.org/WAI/ARIA/apg/" rel="noopener noreferrer"&gt;w3.org/WAI/ARIA/apg&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ARIA in HTML Spec&lt;/strong&gt; — &lt;a href="https://www.w3.org/TR/html-aria/" rel="noopener noreferrer"&gt;w3.org/TR/html-aria&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't light reading, but they're authoritative. Bookmark them.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Closing Truth
&lt;/h2&gt;

&lt;p&gt;ARIA is a powerful tool for bridging the gap between modern web apps and assistive technology. But it's a tool for &lt;em&gt;filling gaps&lt;/em&gt;, not building on nothing.&lt;/p&gt;

&lt;p&gt;Master semantic HTML first. Learn ARIA second. Test with actual screen readers third.&lt;/p&gt;

&lt;p&gt;When you do that, you'll stop making these mistakes. And your sites will work better for &lt;em&gt;everyone&lt;/em&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Priya Nair&lt;/strong&gt; is a frontend developer based in Amsterdam who is genuinely tired of seeing broken ARIA in production. She tests with screen readers regularly and believes that if a feature needs ARIA to work, you probably should have built it differently. When not debugging ARIA, she advocates for better HTML.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>frontend</category>
      <category>html</category>
      <category>webdev</category>
    </item>
    <item>
      <title>ARIA Labels Done Wrong: The Most Common Mistakes I See in Production Code</title>
      <dc:creator>Priya Nair</dc:creator>
      <pubDate>Wed, 08 Apr 2026 20:19:59 +0000</pubDate>
      <link>https://dev.to/priya_nair/aria-labels-done-wrong-the-most-common-mistakes-i-see-in-production-code-1943</link>
      <guid>https://dev.to/priya_nair/aria-labels-done-wrong-the-most-common-mistakes-i-see-in-production-code-1943</guid>
      <description>&lt;h1&gt;
  
  
  ARIA Labels Done Wrong: The Most Common Mistakes I See in Production Code
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Meta: ARIA is powerful but dangerous when misused. Here are the 7 mistakes I see constantly—and how to fix them.&lt;/p&gt;

&lt;p&gt;Keyword: ARIA labels accessibility mistakes common&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;Tags: #accessibility #aria #wcag #webdev #a11y&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Golden Rule of ARIA (And Why Most People Break It)
&lt;/h2&gt;

&lt;p&gt;Before we talk about mistakes, let me say this clearly:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No ARIA is better than bad ARIA.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ARIA (Accessible Rich Internet Applications) is a powerful toolset for filling accessibility gaps. But when you use it wrong—and I mean &lt;em&gt;really wrong&lt;/em&gt;—it makes things worse for screen reader users, not better.&lt;/p&gt;

&lt;p&gt;I've audited hundreds of codebases. I'd estimate 70% of the ARIA I see is either unnecessary or broken. And when a screen reader user encounters broken ARIA, they don't get a degraded experience. They get a &lt;em&gt;confusing&lt;/em&gt; one.&lt;/p&gt;

&lt;p&gt;So here's the deal: I'm going to walk you through the most common mistakes I see, why they're problems, and how to fix them. By the end, you'll know when to use ARIA and when to just use HTML.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake 1: aria-label on a Div Instead of Using Semantic HTML
&lt;/h2&gt;

&lt;p&gt;This is the #1 mistake. By far.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The pattern:&lt;/strong&gt; Someone needs a button. Instead of using &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;, they use a styled &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; and add &lt;code&gt;aria-label&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it's wrong:&lt;/strong&gt; A &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; is invisible to screen readers and keyboard navigation. No amount of ARIA can change that. You're building an accessibility shim on top of an inaccessible foundation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ❌ This is broken, even with aria-label --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; 
  &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Submit form"&lt;/span&gt; 
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
  &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"submitForm()"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Submit
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- What a screen reader hears: "Submit" (as text, not a button) --&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- What keyboard users can do: Nothing (not focusable) --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Good example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ✅ Use semantic HTML --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"submitForm()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- OR if you need a link styled as a button: --&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;"/submit"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Screen reader: "Submit, button" --&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- Keyboard: Focusable with Tab, activatable with Enter --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; Screen reader users need to know it's a button. Keyboard users need to be able to reach it with Tab and activate it with Enter or Space. A bare &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; does neither. ARIA's &lt;code&gt;role="button"&lt;/code&gt; &lt;em&gt;partially&lt;/em&gt; helps, but you still need to add keyboard listeners manually:&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="c1"&gt;// If you insist on using a div (you shouldn't):&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fakeButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[role="button"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;fakeButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Enter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;fakeButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&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;&lt;strong&gt;One-liner:&lt;/strong&gt; Use &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; before you even think about &lt;code&gt;aria-label&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake 2: aria-label Duplicating Visible Text
&lt;/h2&gt;

&lt;p&gt;This one wastes screen reader users' time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The pattern:&lt;/strong&gt; You have a button with visible text. You add &lt;code&gt;aria-label&lt;/code&gt; with the same text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ❌ Redundant --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Click to download file"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Download
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Screen reader: "Click to download file, button" (verbose, redundant) --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it's wrong:&lt;/strong&gt; If the button text is clear, you don't need &lt;code&gt;aria-label&lt;/code&gt;. Screen readers already announce the text. You're just making them say it twice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ✅ Let the text speak for itself --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;Download&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- OR, if you need to clarify: --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;Download Invoice PDF&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Screen reader: "Download Invoice PDF, button" (clear, not redundant) --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When aria-label IS necessary:&lt;/strong&gt; When you have an icon-only button (see Mistake 3).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-liner:&lt;/strong&gt; Don't use &lt;code&gt;aria-label&lt;/code&gt; to repeat visible text. Use it to &lt;em&gt;clarify&lt;/em&gt; invisible intent.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake 3: Icon-Only Buttons Without aria-label (Or ARIA)
&lt;/h2&gt;

&lt;p&gt;This is the flip side of Mistake 2. You have a button with &lt;em&gt;only&lt;/em&gt; an icon. No text. No ARIA. Screen readers have no idea what it does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ❌ Icon-only, no label --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"close-button"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"icon-x"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Screen reader: "Button" (what button? who knows?) --&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- ❌ Or worse: --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hamburger"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 24 24"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"24"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"24"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;line&lt;/span&gt; &lt;span class="na"&gt;x1=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="na"&gt;y1=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt; &lt;span class="na"&gt;x2=&lt;/span&gt;&lt;span class="s"&gt;"21"&lt;/span&gt; &lt;span class="na"&gt;y2=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;line&lt;/span&gt; &lt;span class="na"&gt;x1=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="na"&gt;y1=&lt;/span&gt;&lt;span class="s"&gt;"12"&lt;/span&gt; &lt;span class="na"&gt;x2=&lt;/span&gt;&lt;span class="s"&gt;"21"&lt;/span&gt; &lt;span class="na"&gt;y2=&lt;/span&gt;&lt;span class="s"&gt;"12"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;line&lt;/span&gt; &lt;span class="na"&gt;x1=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="na"&gt;y1=&lt;/span&gt;&lt;span class="s"&gt;"18"&lt;/span&gt; &lt;span class="na"&gt;x2=&lt;/span&gt;&lt;span class="s"&gt;"21"&lt;/span&gt; &lt;span class="na"&gt;y2=&lt;/span&gt;&lt;span class="s"&gt;"18"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Screen reader: "Button" (again, meaningless) --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Good example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ✅ Icon + aria-label --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Close dialog"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"close-button"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"icon-x"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Screen reader: "Close dialog, button" (clear) --&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- ✅ Icon + aria-label + title (tooltip bonus) --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Open navigation menu"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hamburger"&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Menu"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 24 24"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"24"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"24"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;line&lt;/span&gt; &lt;span class="na"&gt;x1=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="na"&gt;y1=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt; &lt;span class="na"&gt;x2=&lt;/span&gt;&lt;span class="s"&gt;"21"&lt;/span&gt; &lt;span class="na"&gt;y2=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;line&lt;/span&gt; &lt;span class="na"&gt;x1=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="na"&gt;y1=&lt;/span&gt;&lt;span class="s"&gt;"12"&lt;/span&gt; &lt;span class="na"&gt;x2=&lt;/span&gt;&lt;span class="s"&gt;"21"&lt;/span&gt; &lt;span class="na"&gt;y2=&lt;/span&gt;&lt;span class="s"&gt;"12"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;line&lt;/span&gt; &lt;span class="na"&gt;x1=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="na"&gt;y1=&lt;/span&gt;&lt;span class="s"&gt;"18"&lt;/span&gt; &lt;span class="na"&gt;x2=&lt;/span&gt;&lt;span class="s"&gt;"21"&lt;/span&gt; &lt;span class="na"&gt;y2=&lt;/span&gt;&lt;span class="s"&gt;"18"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Screen reader: "Open navigation menu, button" --&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- Hover tooltip: "Menu" --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Real-world checklist:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Is the button icon-only (no text)? If yes, add &lt;code&gt;aria-label&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;[ ] Does the &lt;code&gt;aria-label&lt;/code&gt; describe what happens? If yes, you're good.&lt;/li&gt;
&lt;li&gt;[ ] Do sighted users also understand? (Can't rely on icon alone.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;One-liner:&lt;/strong&gt; Every icon-only button needs &lt;code&gt;aria-label&lt;/code&gt;, &lt;code&gt;aria-labelledby&lt;/code&gt;, or text inside the button.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake 4: aria-labelledby Pointing to an ID That Doesn't Exist
&lt;/h2&gt;

&lt;p&gt;This is a silent killer. No error. No warning. Just broken accessibility.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ❌ ID doesn't exist --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"form-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Account Settings&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Later... --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"nonexistent-id"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;  &lt;span class="c"&gt;&amp;lt;!-- Wrong ID! --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Email&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Screen reader: "Form" (no context) --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Good example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ✅ ID exists and is used correctly --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"form-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Account Settings&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"form-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;  &lt;span class="c"&gt;&amp;lt;!-- Correct ID --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Email&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Screen reader: "Account Settings, form" (context clear) --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How to debug:&lt;/strong&gt; Open Chrome DevTools → Accessibility panel. It'll warn you if an ID is broken.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-liner:&lt;/strong&gt; Always check that the ID in &lt;code&gt;aria-labelledby&lt;/code&gt; actually exists in your HTML.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake 5: role="button" on a Div Without Keyboard Handling
&lt;/h2&gt;

&lt;p&gt;This is the classic "I tried to fix it but didn't finish" pattern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ❌ Has role, but no keyboard support --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"doSomething()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Click me
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Screen reader: "Click me, button" (sounds accessible) --&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- Keyboard user: Tabs right past it (not focusable) --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Good example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ✅ Full keyboard support --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; 
  &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; 
  &lt;span class="na"&gt;tabindex=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;  &lt;span class="err"&gt;&amp;lt;!&lt;/span&gt;&lt;span class="na"&gt;--&lt;/span&gt; &lt;span class="na"&gt;Make&lt;/span&gt; &lt;span class="na"&gt;it&lt;/span&gt; &lt;span class="na"&gt;focusable&lt;/span&gt; &lt;span class="na"&gt;--&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  onclick="doSomething()"
  onkeydown="handleKeydown(event)"  &lt;span class="c"&gt;&amp;lt;!-- Handle keyboard --&amp;gt;&lt;/span&gt;
&amp;gt;
  Click me
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleKeydown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Enter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- OR, even better: --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"doSomething()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Click me&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The real talk:&lt;/strong&gt; If you're adding &lt;code&gt;role="button"&lt;/code&gt;, you're probably doing something wrong. You should be using a &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;. &lt;code&gt;role="button"&lt;/code&gt; is for when you have a &lt;em&gt;very good reason&lt;/em&gt; to not use HTML buttons (like custom styling that breaks them, which is rare).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-liner:&lt;/strong&gt; If you use &lt;code&gt;role="button"&lt;/code&gt;, you must also add &lt;code&gt;tabindex="0"&lt;/code&gt; and keyboard event listeners. (But really, just use &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;.)&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake 6: Hiding Content With aria-hidden="true" That Users Can Still Reach
&lt;/h2&gt;

&lt;p&gt;This one creates a horrible experience: keyboard users can reach an element, but screen readers can't see it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ❌ aria-hidden but still focusable --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"doSomething()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Hidden Action
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Screen reader: Ignores the button entirely --&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- Keyboard user: Tabs to it, tries to interact, gets confused --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When this happens:&lt;/strong&gt; Usually with decorative elements that accidentally got tabindex, or off-canvas menus that aren't fully hidden.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ✅ Truly hidden: not visible, not focusable, not announced --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; 
  &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
  &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"display: none;"&lt;/span&gt;  &lt;span class="err"&gt;&amp;lt;!&lt;/span&gt;&lt;span class="na"&gt;--&lt;/span&gt; &lt;span class="na"&gt;Remove&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt; &lt;span class="na"&gt;document&lt;/span&gt; &lt;span class="na"&gt;flow&lt;/span&gt; &lt;span class="na"&gt;--&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&amp;gt;
  Decorative icon
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- ✅ OR: Off-canvas menu that's actually invisible --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;nav&lt;/span&gt; 
  &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"position: fixed; left: -300px;"&lt;/span&gt;  &lt;span class="err"&gt;&amp;lt;!&lt;/span&gt;&lt;span class="na"&gt;--&lt;/span&gt; &lt;span class="na"&gt;Off-screen&lt;/span&gt; &lt;span class="na"&gt;--&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  aria-hidden="true"
&amp;gt;
  Menu items
&lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- ✅ OR: Use visibility + aria-hidden for keyboard access but screen reader hiding --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; 
  &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"visibility: hidden;"&lt;/span&gt;
  &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Invisible to all users
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Test:&lt;/strong&gt; Inspect the element in DevTools. Is it visible? Is it focusable? If both are true but &lt;code&gt;aria-hidden="true"&lt;/code&gt; is set, that's a problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-liner:&lt;/strong&gt; Only use &lt;code&gt;aria-hidden="true"&lt;/code&gt; on elements that are actually invisible/non-interactive.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake 7: Dynamic Content Updates Not Announced (Missing aria-live)
&lt;/h2&gt;

&lt;p&gt;A screen reader user is on your page. New content loads via JavaScript. The screen reader has no idea. User keeps reading old content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ❌ Content updates, but no announcement --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"notifications"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Initially empty --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;// New notification arrives&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;notification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Order #12345 shipped!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;notifications&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Screen reader: *crickets* (no announcement)&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Good example:&lt;/strong&gt;&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="c"&gt;&amp;lt;!-- ✅ Content updates AND is announced --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"notifications"&lt;/span&gt; &lt;span class="na"&gt;aria-live=&lt;/span&gt;&lt;span class="s"&gt;"polite"&lt;/span&gt; &lt;span class="na"&gt;aria-atomic=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Initially empty --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;notification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Order #12345 shipped!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;notifications&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Screen reader: "Order #12345 shipped!" (announced immediately)&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- ✅ Or, for urgent updates: --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"alerts"&lt;/span&gt; &lt;span class="na"&gt;aria-live=&lt;/span&gt;&lt;span class="s"&gt;"assertive"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"alert"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Errors, warnings, urgent messages --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;// This gets announced immediately, interrupting other content&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alerts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your session will expire in 2 minutes.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;aria-live options:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;aria-live="polite"&lt;/code&gt; — Announce when convenient (after current speech)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aria-live="assertive"&lt;/code&gt; — Announce immediately (interrupts current speech)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;role="alert"&lt;/code&gt; — Shorthand for &lt;code&gt;aria-live="assertive" aria-atomic="true"&lt;/code&gt; (errors, warnings)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;role="status"&lt;/code&gt; — Shorthand for &lt;code&gt;aria-live="polite" aria-atomic="true"&lt;/code&gt; (status updates)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real-world examples:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Form validation errors → use &lt;code&gt;role="alert"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;"Saved successfully" message → use &lt;code&gt;role="status"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Real-time notifications → use &lt;code&gt;aria-live="polite"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;One-liner:&lt;/strong&gt; If content loads dynamically, wrap it in &lt;code&gt;aria-live&lt;/code&gt; so screen readers know it's there.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Test: Chrome DevTools
&lt;/h2&gt;

&lt;p&gt;Here's the fastest way to catch most ARIA mistakes.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open your site in Chrome.&lt;/li&gt;
&lt;li&gt;Open DevTools (F12).&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Elements&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;Right-click any element → &lt;strong&gt;Inspect accessibility properties&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Look at the &lt;strong&gt;Computed Name&lt;/strong&gt; section.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;What you're checking:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the element have a name? (If it's interactive, it should.)&lt;/li&gt;
&lt;li&gt;Is the name what you intended? (Not "button" or blank.)&lt;/li&gt;
&lt;li&gt;Are there warnings? (Red warning icon = problem.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Element: &amp;lt;button&amp;gt;
Computed Name: "Submit"
Role: button
Warnings: None ✓

---

Element: &amp;lt;div role="button" aria-label="close"&amp;gt;
Computed Name: "close"
Role: button
Warnings: None ✓

---

Element: &amp;lt;button aria-label="nonexistent-id"&amp;gt;
Computed Name: (empty)
Role: button
Warnings: ⚠️ aria-labelledby points to non-existent element
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Testing With a Real Screen Reader
&lt;/h2&gt;

&lt;p&gt;If Chrome DevTools isn't enough, test with an actual screen reader.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On Mac:&lt;/strong&gt; VoiceOver is built-in. Turn it on: System Preferences → Accessibility → VoiceOver. Press Ctrl+Option+U to start.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On Windows:&lt;/strong&gt; NVDA is free. Download from &lt;a href="https://www.nvaccess.org/" rel="noopener noreferrer"&gt;nvaccess.org&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listen for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the button announcement sound right? (Not just "button," but "submit button" or "close dialog"?)&lt;/li&gt;
&lt;li&gt;Can you navigate to all interactive elements?&lt;/li&gt;
&lt;li&gt;Do new notifications get announced?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It takes 10 minutes to learn the basics. Highly worth it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Quick Fix Checklist
&lt;/h2&gt;

&lt;p&gt;Running through a codebase? Use this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] No &lt;code&gt;&amp;lt;div role="button"&amp;gt;&lt;/code&gt; without &lt;code&gt;tabindex="0"&lt;/code&gt; and keyboard handlers&lt;/li&gt;
&lt;li&gt;[ ] Every &lt;code&gt;aria-label&lt;/code&gt; / &lt;code&gt;aria-labelledby&lt;/code&gt; has a real target&lt;/li&gt;
&lt;li&gt;[ ] Icon-only buttons have &lt;code&gt;aria-label&lt;/code&gt; or text&lt;/li&gt;
&lt;li&gt;[ ] No &lt;code&gt;aria-hidden="true"&lt;/code&gt; on focusable elements&lt;/li&gt;
&lt;li&gt;[ ] Dynamic content has &lt;code&gt;aria-live&lt;/code&gt; or &lt;code&gt;role="alert"&lt;/code&gt; / &lt;code&gt;role="status"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] ARIA attributes only add information, not replace semantic HTML&lt;/li&gt;
&lt;li&gt;[ ] Form fields have associated &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; elements&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ARIA Violations I See Most
&lt;/h2&gt;

&lt;p&gt;After auditing 200+ projects:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;aria-label duplicating text&lt;/strong&gt; (70% of aria-label misuse)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;role="button" without keyboard&lt;/strong&gt; (30% of role misuse)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;aria-hidden hiding interactive content&lt;/strong&gt; (25% of aria-hidden issues)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing aria-live on dynamic updates&lt;/strong&gt; (50% of single-page apps)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;aria-labelledby pointing to nothing&lt;/strong&gt; (15% of aria-labelledby)&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Real Rule
&lt;/h2&gt;

&lt;p&gt;Here's how to think about ARIA:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ARIA is for semantics and live regions. Not for fixing broken HTML.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're tempted to use ARIA, ask yourself first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is there a semantic HTML element that does this? (Usually yes.)&lt;/li&gt;
&lt;li&gt;Is the content actually hidden/dynamic? (If not, probably doesn't need ARIA.)&lt;/li&gt;
&lt;li&gt;Are screen reader users getting accurate information? (Test it.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of the time, better HTML solves the problem faster than ARIA ever could.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources (All Free, Official)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MDN ARIA documentation&lt;/strong&gt; — &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA" rel="noopener noreferrer"&gt;developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WAI-ARIA Authoring Practices Guide&lt;/strong&gt; — &lt;a href="https://www.w3.org/WAI/ARIA/apg/" rel="noopener noreferrer"&gt;w3.org/WAI/ARIA/apg&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ARIA in HTML Spec&lt;/strong&gt; — &lt;a href="https://www.w3.org/TR/html-aria/" rel="noopener noreferrer"&gt;w3.org/TR/html-aria&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't light reading, but they're authoritative. Bookmark them.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Closing Truth
&lt;/h2&gt;

&lt;p&gt;ARIA is a powerful tool for bridging the gap between modern web apps and assistive technology. But it's a tool for &lt;em&gt;filling gaps&lt;/em&gt;, not building on nothing.&lt;/p&gt;

&lt;p&gt;Master semantic HTML first. Learn ARIA second. Test with actual screen readers third.&lt;/p&gt;

&lt;p&gt;When you do that, you'll stop making these mistakes. And your sites will work better for &lt;em&gt;everyone&lt;/em&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Priya Nair&lt;/strong&gt; is a frontend developer based in Amsterdam who is genuinely tired of seeing broken ARIA in production. She tests with screen readers regularly and believes that if a feature needs ARIA to work, you probably should have built it differently. When not debugging ARIA, she advocates for better HTML.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>frontend</category>
      <category>html</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Audited My Own Portfolio for WCAG 2.2 — Here's What I Found in 30 Minutes</title>
      <dc:creator>Priya Nair</dc:creator>
      <pubDate>Wed, 08 Apr 2026 20:15:21 +0000</pubDate>
      <link>https://dev.to/priya_nair/i-audited-my-own-portfolio-for-wcag-22-heres-what-i-found-in-30-minutes-4e55</link>
      <guid>https://dev.to/priya_nair/i-audited-my-own-portfolio-for-wcag-22-heres-what-i-found-in-30-minutes-4e55</guid>
      <description>&lt;h1&gt;
  
  
  I Audited My Own Portfolio for WCAG 2.2 — Here's What I Found in 30 Minutes
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Meta: I audited my portfolio for WCAG 2.2 and found six critical accessibility issues I'd been ignoring. Here's how to find them in yours.&lt;/p&gt;

&lt;p&gt;Keyword: WCAG 2.2 audit checklist developer&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;Tags: #accessibility #webdev #wcag #frontend&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Moment I Realized I Was Part of the Problem
&lt;/h2&gt;

&lt;p&gt;Last week, I was reviewing accessibility best practices for a client project when it hit me: I haven't formally audited &lt;em&gt;my own&lt;/em&gt; portfolio in years. You know, the one I show to potential clients when I talk about how much I care about inclusive design.&lt;/p&gt;

&lt;p&gt;Oof.&lt;/p&gt;

&lt;p&gt;So I decided to do something radical—actually practice what I preach. I set a timer for 30 minutes, grabbed a couple of free tools, and started poking around. What I found was both humbling and, frankly, pretty typical. And that's exactly why I'm writing this.&lt;/p&gt;

&lt;p&gt;The web is broken for accessibility. The WebAIM Million 2025 report shows that &lt;strong&gt;95.9% of websites fail basic WCAG checks&lt;/strong&gt;. Ninety-five point nine percent. That's not bad luck. That's us. Developers. We did this. Usually without meaning to, usually while thinking about 47 other things, but we did it.&lt;/p&gt;

&lt;p&gt;Here's the good news: we can also fix it. And if the EU's Accessibility Act (EAA) being enforced as of June 2025 isn't motivation enough, maybe knowing that your actual users—whether they're blind, colorblind, using only a keyboard, or just on a tiny mobile screen in bad sunlight—will actually be able to use your site, is.&lt;/p&gt;

&lt;p&gt;Let me walk you through what I found, how I found it, and how to fix it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Tools (All Free, All Good)
&lt;/h2&gt;

&lt;p&gt;Before we dig in, let me be clear: you don't need expensive software.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;axe DevTools&lt;/strong&gt; (browser extension, free) — This is my workhorse. It runs automated accessibility scans and flags issues with severity and WCAG criteria. Chrome, Firefox, Edge. Get it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WAVE&lt;/strong&gt; (browser extension, free) — Shows you a visual representation of content structure, labels, and warnings. Slightly different angle than axe, so both are worth running.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chrome DevTools — Accessibility Tree&lt;/strong&gt; (built in, free) — Under DevTools → Elements → Accessibility, you can see how your page is parsed by assistive technology. This is eye-opening.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keyboard testing&lt;/strong&gt; (your keyboard, free) — Press Tab. See what happens. Try to navigate your entire page without touching the mouse. This matters more than any tool.&lt;/p&gt;

&lt;p&gt;That's it. Ten minutes to set up. Let's go.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Found: Six Things That Made Me Wince
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Missing and Terrible Alt Text on Images
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The issue:&lt;/strong&gt; I had a gorgeous hero image on my portfolio landing page. Beautiful. Completely undescribed.&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="c"&gt;&amp;lt;!-- ❌ What I had --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"portfolio-hero.jpg"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- ✅ What I should have --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; 
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"portfolio-hero.jpg"&lt;/span&gt; 
  &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Portfolio website displaying three web design projects with dark theme and gradient accents"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What it means for real users:&lt;/strong&gt; Blind users get nothing. Screen readers say "image" and move on. Image broken or slow to load? Sighted users see the broken image icon. Blind users see nothing—they don't even know there was supposed to be content there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Write alt text that describes the &lt;em&gt;content and purpose&lt;/em&gt; of the image. Not poetic. Not &lt;code&gt;alt="beautiful sunset"&lt;/code&gt;. Useful. What does this image &lt;em&gt;do&lt;/em&gt; on your page? If it's decorative, use &lt;code&gt;alt=""&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Insufficient Color Contrast (The Sneaky One)
&lt;/h3&gt;

&lt;p&gt;My design had a really slick dark gray text on a slightly darker gray background. Very trendy. Very unreadable if you have low vision, color blindness, or are just squinting at your phone in daylight.&lt;/p&gt;

&lt;p&gt;axe flagged it: &lt;strong&gt;3.2:1 contrast ratio&lt;/strong&gt;. WCAG AA requires &lt;strong&gt;4.5:1&lt;/strong&gt; for normal text, &lt;strong&gt;3:1&lt;/strong&gt; for large text (18px+ or 14px bold+).&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="c"&gt;/* ❌ What I had (3.2:1 - FAIL) */&lt;/span&gt;
&lt;span class="nc"&gt;.post-date&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#666666&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#1a1a1a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* ✅ What I fixed (4.8:1 - PASS) */&lt;/span&gt;
&lt;span class="nc"&gt;.post-date&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#cccccc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#1a1a1a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14px&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;&lt;strong&gt;Quick way to check:&lt;/strong&gt; Use the &lt;a href="https://webaim.org/resources/contrastchecker/" rel="noopener noreferrer"&gt;WebAIM contrast checker&lt;/a&gt; (free, no login). Paste your hex codes, see your ratio. Aim for 4.5:1 unless your text is genuinely big.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Focus Indicators (The Invisible Tab)
&lt;/h3&gt;

&lt;p&gt;I had removed the default browser focus outline on links because it "looked ugly." Instead, I'd added a subtle &lt;code&gt;:hover&lt;/code&gt; effect. Very pretty. Very broken if you only use keyboard.&lt;/p&gt;

&lt;p&gt;Someone using a keyboard to navigate my site would Tab onto a link and... see nothing change visually. Where am I? Did that work? Panic.&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="c"&gt;/* ❌ What I had */&lt;/span&gt;
&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* THE SIN */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#0066ff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* ✅ What I fixed */&lt;/span&gt;
&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="nd"&gt;:focus-visible&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#0066ff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;outline-offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#0066ff&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;&lt;strong&gt;The rule:&lt;/strong&gt; Never remove the default focus outline without replacing it. &lt;code&gt;:focus-visible&lt;/code&gt; is your friend—it shows the outline when using keyboard, but not when clicking with a mouse. Best of both worlds.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Unlabelled Form Inputs (The Invisible Ask)
&lt;/h3&gt;

&lt;p&gt;I have a contact form. The form had inputs, but they were only labeled visually—the HTML had no &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; elements.&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="c"&gt;&amp;lt;!-- ❌ What I had --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Your email"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- ✅ What I should have --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"contact-email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Email address&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"contact-email"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Your email"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; A screen reader user reaches the input field and hears... nothing. They don't know what the field is for. The &lt;code&gt;placeholder&lt;/code&gt; disappears when they type. Without a label, they're flying blind.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Even better:&lt;/strong&gt; If you want to visually hide the label (for design reasons), use CSS:&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="nc"&gt;.sr-only&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;white-space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;nowrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"contact-email"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"sr-only"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Email address&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"contact-email"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Your email"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The label is there for assistive tech. The visual design is unchanged.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Keyboard Navigation Trap in My Project Modal
&lt;/h3&gt;

&lt;p&gt;I have a modal that displays project details. You Tab through the form, but when you reach the last button, Tab wraps back to the first button &lt;em&gt;inside the modal&lt;/em&gt;—you can't escape without clicking outside or pressing Escape.&lt;/p&gt;

&lt;p&gt;This is called &lt;strong&gt;focus trap without escape&lt;/strong&gt;, and it's particularly bad if you're keyboard-only.&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="c1"&gt;// ✅ Simple fix: Handle Tab in the last focusable element&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lastButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.modal .btn-close&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;lastButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tab&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shiftKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.modal .btn-primary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// OR, better: Use a proper focus trap library behavior&lt;/span&gt;
&lt;span class="c1"&gt;// But here's the core idea—don't trap users in a modal without a clear exit.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Real talk:&lt;/strong&gt; The best fix is making sure your modal can be closed with Escape (which browsers often handle automatically), and that focus moves &lt;em&gt;back&lt;/em&gt; to the trigger element when the modal closes.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. No Skip Navigation Link
&lt;/h3&gt;

&lt;p&gt;This is the one that made me laugh at myself. I preach about skip links, and mine had zero.&lt;/p&gt;

&lt;p&gt;A skip link is a link at the very start of your HTML that says "Skip to main content." It's hidden by default, but visible when you Tab to it. For keyboard-only users, this saves them from Tabbing through your entire navigation menu every single 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;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#main-content"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"skip-link"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Skip to main content&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;nav&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- navigation stuff --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;main&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"main-content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- your actual content --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.skip-link&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-40px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text-decoration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.skip-link&lt;/span&gt;&lt;span class="nd"&gt;:focus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&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;When you Tab onto it, it slides into view. Click it, and you jump straight to &lt;code&gt;#main-content&lt;/code&gt;. Keyboard users say a silent thank you.&lt;/p&gt;




&lt;h2&gt;
  
  
  WCAG 2.2: What's New and What I Hit
&lt;/h2&gt;

&lt;p&gt;WCAG 2.2 added 9 new criteria in October 2023. I triggered at least two:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Focus Appearance (2.4.13)&lt;/strong&gt; — That focus indicator I removed? That's criterion 2.4.13. WCAG now demands a visible focus indicator with at least 3:1 contrast against adjacent colors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Target Size (2.5.8)&lt;/strong&gt; — Interactive elements should be at least 24px × 24px (or 24px of spacing between them). My social media icons in the footer were 16px. Easy miss. Easy fix.&lt;/p&gt;

&lt;p&gt;Both are WCAG AA level. Both are things I didn't know about when I started, but both are &lt;em&gt;important&lt;/em&gt; because they make the web usable for more people.&lt;/p&gt;




&lt;h2&gt;
  
  
  Your Quick-Start Checklist: 10 Minutes
&lt;/h2&gt;

&lt;p&gt;If you only read this far, here's what to do right now:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install axe DevTools&lt;/strong&gt;, open your site, run the scan. Note the failures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check all images.&lt;/strong&gt; Does each &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; have useful alt text? (Not &lt;code&gt;alt="image"&lt;/code&gt;, actual description.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test with Tab only.&lt;/strong&gt; Close your trackpad. Can you navigate your entire site with Tab and Enter? Is focus always visible?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check form labels.&lt;/strong&gt; Every input should have a &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; with a matching &lt;code&gt;for&lt;/code&gt; attribute.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test color contrast.&lt;/strong&gt; Pick your key text colors. Run them through WebAIM's contrast checker. Aim for 4.5:1.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. Thirty minutes, max.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Thing About Being a Developer Who Cares
&lt;/h2&gt;

&lt;p&gt;I think we fail accessibility not because we're careless, but because the defaults are broken. The web platform doesn't make inclusive design automatic. You have to opt in.&lt;/p&gt;

&lt;p&gt;But once you see it—once you understand that real humans depend on this stuff—it becomes harder to ignore. And the good news is that it's not hard. It's just... intentional.&lt;/p&gt;

&lt;p&gt;I fixed my six issues in an afternoon. My portfolio loads faster, is more usable for everyone, and I can talk to clients with actual credibility now.&lt;/p&gt;

&lt;p&gt;You can too.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Priya Nair&lt;/strong&gt; is a frontend developer based in Amsterdam. She writes about accessibility, inclusive design, and the occasional CSS rabbit hole. When she's not auditing her own code, you'll find her advocating for the web that works for everyone.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>frontend</category>
      <category>testing</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
