<?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.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>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>
