Fix Keyboard Navigation Accessibility Issues | OpsBlu Docs

Fix Keyboard Navigation Accessibility Issues

Diagnose and fix keyboard accessibility issues to ensure all functionality is usable without a mouse

What This Means

Keyboard navigation ensures that all website functionality can be accessed and operated using only a keyboard, without requiring a mouse or trackpad. This is essential for users who cannot use a mouse due to motor disabilities, vision impairments, or those who simply prefer keyboard navigation for efficiency.

Impact on Your Business

Legal Compliance:

  • Keyboard accessibility is required under WCAG 2.1 Level A
  • One of the most fundamental accessibility requirements
  • Failure to support keyboard navigation is a critical violation
  • Required for ADA and Section 508 compliance

User Populations Affected:

  • Users with motor disabilities - Cannot use a mouse
  • Blind and low-vision users - Navigate with screen readers
  • Power users - Prefer keyboard for efficiency
  • Temporary disabilities - Broken arm, RSI
  • Potentially millions of users excluded

Business Impact:

  • Keyboard-inaccessible sites exclude entire user segments
  • Critical functionality (checkout, forms) may be unusable
  • Increases bounce rates for affected users
  • Damages brand reputation and trust

SEO and Technical Benefits:

  • Semantic HTML improves SEO
  • Better code structure and maintainability
  • Improved compatibility with assistive technologies
  • Benefits mobile and touch device users

How to Diagnose

This is the most important test:

  1. Close or ignore your mouse/trackpad

  2. Navigate using only keyboard:

    • Tab - Move to next interactive element
    • Shift+Tab - Move to previous interactive element
    • Enter - Activate links and buttons
    • Space - Activate buttons, check checkboxes
    • Arrow keys - Navigate within components (dropdowns, radio groups)
    • Esc - Close modals and menus
  3. Check every page section:

    • Can you reach all links?
    • Can you activate all buttons?
    • Can you use all forms?
    • Can you close modals?
    • Can you use dropdown menus?

What to Look For:

  • Elements you cannot reach with Tab
  • Missing or invisible focus indicators
  • Illogical tab order
  • Keyboard traps (can't Tab out)
  • Elements that require mouse (hover-only)

Method 2: WAVE Browser Extension

  1. Install WAVE Extension
  2. Navigate to your webpage
  3. Click WAVE icon
  4. Review errors:
    • "Missing form label"
    • "Empty button"
    • "Empty link"
  5. Check for structural issues:
    • "Heading" hierarchy
    • "Landmark" regions

What to Look For:

  • Links without text
  • Buttons without labels
  • Form inputs without labels
  • Broken heading hierarchy
  • Missing ARIA labels

Method 3: axe DevTools

  1. Install axe DevTools Extension
  2. Open Chrome DevTools (F12)
  3. Navigate to "axe DevTools" tab
  4. Click "Scan ALL of my page"
  5. Review keyboard-related violations:
    • "Elements must have sufficient color contrast"
    • "Links must have discernible text"
    • "Buttons must have discernible text"
    • "Form elements must have labels"

What to Look For:

  • Interactive elements without accessible names
  • Missing focus indicators
  • Keyboard trap issues
  • ARIA role violations

Method 4: Chrome DevTools Accessibility Tab

  1. Open your website
  2. Press F12 to open DevTools
  3. Right-click an element → Inspect
  4. In Elements panel, click "Accessibility" tab
  5. Review:
    • Computed Properties - Name, Role, Focusable
    • Accessibility Tree - How element appears to assistive tech

What to Look For:

  • Focusable: "false" on interactive elements
  • Missing accessible name
  • Incorrect role
  • Elements not in accessibility tree

Method 5: Focus Indicator Test

  1. Enable Chrome DevTools > Rendering
  2. Check "Emulate a focused page"
  3. Tab through the page
  4. Observe focus indicators

What to Look For:

  • Invisible focus indicators (no visual change)
  • Focus indicators removed by CSS
  • Low contrast focus indicators
  • Focus jumping unexpectedly

General Fixes

Fix 1: Make All Interactive Elements Focusable

Ensure elements can receive keyboard focus:

  1. Use proper HTML elements:

    <!-- Bad - div is not focusable by default -->
    <div me</div>
    
    <!-- Good - button is focusable -->
    <button me</button>
    
  2. If you must use non-button elements, add tabindex and role:

    <!-- Only do this if you can't use a real button -->
    <div
      role="button"
      tabindex="0"
    >
      Click me
    </div>
    
    <script>
    function handleKeyDown(event) {
      if (event.key === 'Enter' || event.key === ' ') {
        event.preventDefault();
        handleClick();
      }
    }
    </script>
    
  3. Use links for navigation, buttons for actions:

    <!-- Link - goes somewhere -->
    <a href="/products">View Products</a>
    
    <!-- Button - does something -->
    <button type="button" Details</button>
    

Fix 2: Provide Visible Focus Indicators

Make it obvious which element has focus:

  1. Never remove focus outlines without replacement:

    /* Bad - removes focus with no replacement */
    *:focus {
      outline: none;
    }
    
    /* Good - custom focus indicator */
    button:focus,
    a:focus,
    input:focus {
      outline: 2px solid #0066cc;
      outline-offset: 2px;
    }
    
  2. Use high contrast focus styles:

    /* Ensure focus visible on all backgrounds */
    :focus {
      outline: 2px solid #0066cc;
      outline-offset: 2px;
    }
    
    /* For dark backgrounds */
    .dark-theme :focus {
      outline: 2px solid #66b3ff;
    }
    
  3. Use :focus-visible for mouse vs keyboard:

    /* Only show outline for keyboard focus */
    button:focus-visible {
      outline: 2px solid #0066cc;
      outline-offset: 2px;
    }
    
    /* Remove outline for mouse clicks */
    button:focus:not(:focus-visible) {
      outline: none;
    }
    
  4. Enhanced focus styles:

    button:focus {
      outline: 2px solid #0066cc;
      outline-offset: 2px;
      box-shadow: 0 0 0 4px rgba(0, 102, 204, 0.2);
    }
    

Fix 3: Fix Tab Order

Ensure logical navigation sequence:

  1. Use natural DOM order (no tabindex > 0):

    <!-- Bad - forces unnatural order -->
    <button tabindex="3">Third</button>
    <button tabindex="1">First</button>
    <button tabindex="2">Second</button>
    
    <!-- Good - natural DOM order -->
    <button>First</button>
    <button>Second</button>
    <button>Third</button>
    
  2. Use tabindex values correctly:

    • tabindex="0" - Natural tab order (focusable)
    • tabindex="-1" - Programmatically focusable (not in tab order)
    • tabindex="1+" - Avoid! Creates unpredictable order
  3. Structure HTML in reading order:

    <!-- Good - header, nav, main, footer -->
    <header>
      <nav><!-- navigation --></nav>
    </header>
    <main>
      <h1>Page Title</h1>
      <!-- main content -->
    </main>
    <footer><!-- footer --></footer>
    

Fix 4: Prevent Keyboard Traps

Users must be able to navigate away from all elements:

  1. Test modals for keyboard traps:

    // Trap focus within modal
    function trapFocus(element) {
      const focusableElements = element.querySelectorAll(
        'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
      );
      const firstFocusable = focusableElements[0];
      const lastFocusable = focusableElements[focusableElements.length - 1];
    
      element.addEventListener('keydown', (e) => {
        if (e.key !== 'Tab') return;
    
        if (e.shiftKey) {
          // Shift + Tab
          if (document.activeElement === firstFocusable) {
            lastFocusable.focus();
            e.preventDefault();
          }
        } else {
          // Tab
          if (document.activeElement === lastFocusable) {
            firstFocusable.focus();
            e.preventDefault();
          }
        }
      });
    }
    
  2. Ensure modals can be closed with Escape:

    modal.addEventListener('keydown', (e) => {
      if (e.key === 'Escape') {
        closeModal();
      }
    });
    
  3. Return focus after modal closes:

    function openModal() {
      lastFocusedElement = document.activeElement;
      modal.style.display = 'block';
      modal.querySelector('button').focus();
    }
    
    function closeModal() {
      modal.style.display = 'none';
      lastFocusedElement.focus();
    }
    

Fix 5: Make Dropdowns and Menus Keyboard Accessible

Hover-only menus exclude keyboard users:

  1. Add keyboard event handlers:

    const menuButton = document.querySelector('.menu-button');
    const menu = document.querySelector('.menu');
    
    // Show on click
    menuButton.addEventListener('click', () => {
      menu.classList.toggle('open');
    });
    
    // Show on Enter/Space
    menuButton.addEventListener('keydown', (e) => {
      if (e.key === 'Enter' || e.key === ' ') {
        e.preventDefault();
        menu.classList.toggle('open');
      }
    });
    
    // Navigate menu items with arrow keys
    menu.addEventListener('keydown', (e) => {
      const items = menu.querySelectorAll('a');
      const currentIndex = Array.from(items).indexOf(document.activeElement);
    
      if (e.key === 'ArrowDown') {
        e.preventDefault();
        const next = items[currentIndex + 1] || items[0];
        next.focus();
      } else if (e.key === 'ArrowUp') {
        e.preventDefault();
        const prev = items[currentIndex - 1] || items[items.length - 1];
        prev.focus();
      } else if (e.key === 'Escape') {
        menu.classList.remove('open');
        menuButton.focus();
      }
    });
    
  2. Use ARIA for dropdown state:

    <button
      aria-expanded="false"
      aria-controls="dropdown-menu"
    >
      Menu
    </button>
    <ul id="dropdown-menu" hidden>
      <li><a href="#1">Item 1</a></li>
      <li><a href="#2">Item 2</a></li>
    </ul>
    
    <script>
    function toggleMenu() {
      const button = document.querySelector('[aria-controls="dropdown-menu"]');
      const menu = document.getElementById('dropdown-menu');
      const isExpanded = button.getAttribute('aria-expanded') === 'true';
    
      button.setAttribute('aria-expanded', !isExpanded);
      menu.hidden = isExpanded;
    }
    </script>
    

Fix 6: Label All Form Elements

Every input needs an associated label:

  1. Use explicit labels:

    <!-- Good - explicit association -->
    <label for="email">Email Address</label>
    <input id="email" type="email" name="email">
    
  2. Or wrap inputs in labels:

    <!-- Also good - implicit association -->
    <label>
      Email Address
      <input type="email" name="email">
    </label>
    
  3. Use aria-label for icon buttons:

    <button aria-label="Close modal">
      <svg aria-hidden="true"><!-- close icon --></svg>
    </button>
    
  4. Group related form elements:

    <fieldset>
      <legend>Shipping Address</legend>
      <label for="street">Street</label>
      <input id="street" type="text">
    
      <label for="city">City</label>
      <input id="city" type="text">
    </fieldset>
    

Allow users to skip repetitive content:

  1. Add skip to main content link:

    <body>
      <a href="#main" class="skip-link">Skip to main content</a>
    
      <header>
        <nav><!-- Navigation --></nav>
      </header>
    
      <main id="main" tabindex="-1">
        <!-- Main content -->
      </main>
    </body>
    
  2. Style skip link (visible on focus):

    .skip-link {
      position: absolute;
      top: -40px;
      left: 0;
      background: #000;
      color: #fff;
      padding: 8px;
      text-decoration: none;
      z-index: 100;
    }
    
    .skip-link:focus {
      top: 0;
    }
    

Fix 8: Use Semantic HTML and ARIA

Proper structure aids navigation:

  1. Use landmark regions:

    <header role="banner">
      <nav role="navigation" aria-label="Main navigation">
        <!-- nav items -->
      </nav>
    </header>
    
    <main role="main">
      <article>
        <h1>Article Title</h1>
        <!-- content -->
      </article>
    </main>
    
    <aside role="complementary" aria-label="Related articles">
      <!-- sidebar -->
    </aside>
    
    <footer role="contentinfo">
      <!-- footer -->
    </footer>
    
  2. Use proper heading hierarchy:

    <h1>Page Title</h1>
      <h2>Section 1</h2>
        <h3>Subsection 1.1</h3>
        <h3>Subsection 1.2</h3>
      <h2>Section 2</h2>
        <h3>Subsection 2.1</h3>
    
  3. Use ARIA when needed:

    <!-- Live region for dynamic updates -->
    <div role="status" aria-live="polite">
      Items added to cart
    </div>
    
    <!-- Current page in navigation -->
    <nav>
      <a href="/">Home</a>
      <a href="/products" aria-current="page">Products</a>
      <a href="/about">About</a>
    </nav>
    

Platform-Specific Guides

Detailed implementation instructions for your specific platform:

Platform Troubleshooting Guide
Shopify Shopify Troubleshooting
WordPress WordPress Troubleshooting
Wix Wix Troubleshooting
Squarespace Squarespace Troubleshooting
Webflow Webflow Troubleshooting

Verification

After implementing keyboard accessibility:

  1. Complete keyboard navigation test:

    • Unplug mouse
    • Navigate entire site with keyboard only
    • Verify all functionality accessible
    • Check tab order is logical
    • Confirm focus always visible
  2. Test with screen reader:

    • NVDA (Windows) or VoiceOver (Mac)
    • Navigate using screen reader shortcuts
    • Verify all elements announced correctly
    • Check ARIA labels make sense
  3. Run automated tests:

    • axe DevTools (no keyboard violations)
    • WAVE extension (no structural errors)
    • Lighthouse (passes accessibility audit)
  4. Test specific scenarios:

    • Complete a form submission
    • Open and close a modal
    • Navigate a dropdown menu
    • Use any interactive widgets
  5. Test edge cases:

    • Keyboard traps (shouldn't exist)
    • Skip links work
    • Focus management in SPAs
    • Dynamic content updates

Common Mistakes

  1. Using <div> or <span> instead of <button> - Not focusable by default
  2. Removing focus outlines - Users can't see where they are
  3. Hover-only menus - Keyboard users can't access
  4. Missing keyboard handlers - Only mouse events implemented
  5. Improper tabindex use - Using tabindex > 0
  6. Keyboard traps - Can't escape modal or widget
  7. Unlabeled form inputs - Screen readers can't identify purpose
  8. Missing skip links - Forces navigation through all content
  9. Illogical tab order - Visual order doesn't match DOM order
  10. Focus not visible - Low contrast or invisible focus indicators

Keyboard Navigation Checklist

  • All interactive elements reachable via Tab
  • Tab order is logical and follows visual order
  • Focus indicators are clearly visible
  • Focus indicators have sufficient contrast (3:1)
  • Enter/Space activate buttons and links
  • Escape closes modals and menus
  • Arrow keys work in dropdowns and menus
  • No keyboard traps exist
  • Skip links provided for main content
  • All form inputs have labels
  • Custom widgets have appropriate ARIA
  • Modals manage focus correctly
  • Dynamic content updates announced to screen readers
  • Dropdowns accessible without mouse
  • All functionality available via keyboard

Additional Resources