Keyboard Navigation: A Day in the Life - Understanding Web Accessibility Challenges

Posted by D2i Team on February 09, 2025

It’s 7 AM, and like millions of users with motor disabilities, RSI, or simply a preference for keyboard efficiency, I start my day navigating the web without a mouse. Join me on this journey to understand the challenges of keyboard-only navigation and how we, as developers, can build a more accessible web.

Morning News: The Tab Trap

My morning begins with checking the news. I press Tab to navigate through the site’s header, but immediately encounter our first challenge. The mega-menu dropdown traps keyboard focus, forcing me through 15 navigation items before I can reach the main content. This is a common frustration that developers can easily fix:

// Allow users to escape navigation menus with the Escape key
document.addEventListener('keydown', (e) => {
  if (e.key === 'Escape' && isMenuOpen) {
    closeMenu();
    returnFocusToTrigger();
  }
});

Work Email: The Invisible Focus State

Opening my work email, I struggle to track my current position because the website doesn’t show keyboard focus indicators. Many developers remove the default focus outline for aesthetic reasons, not realizing they’re making their sites unusable for keyboard users. The solution is simple:

/* Never remove focus outlines without providing an alternative */
:focus {
  outline: 2px solid #007bff;
  outline-offset: 2px;
}

/* If you must remove outline, ensure an alternative is visible */
:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px #007bff;
}

Lunchtime Shopping: The Modal Maze

During lunch, I try to order food online. A modal popup appears, but I can’t interact with it using my keyboard. The focus remains on the main page elements behind the modal, a critical accessibility failure. Here’s how developers should handle modal focus management:

class AccessibleModal {
  constructor() {
    this.focusableElements = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
    this.modal = document.querySelector('#modal');
    this.firstFocusableElement = this.modal.querySelectorAll(this.focusableElements)[0];
    this.focusableContent = this.modal.querySelectorAll(this.focusableElements);
    this.lastFocusableElement = this.focusableContent[this.focusableContent.length - 1];
  }

  trapFocus(e) {
    let isTabPressed = e.key === 'Tab';
    if (!isTabPressed) return;

    if (e.shiftKey) {
      if (document.activeElement === this.firstFocusableElement) {
        this.lastFocusableElement.focus();
        e.preventDefault();
      }
    } else {
      if (document.activeElement === this.lastFocusableElement) {
        this.firstFocusableElement.focus();
        e.preventDefault();
      }
    }
  }
}

Afternoon Meeting: The Form Frustration

Scheduling a meeting reveals another common issue: form controls that can only be activated by clicking. The date picker calendar isn’t keyboard accessible, and the custom dropdown menu ignores my arrow keys. Here’s a pattern for creating keyboard-friendly custom controls:

// Make custom dropdowns keyboard accessible
class AccessibleDropdown {
  constructor(dropdown) {
    this.dropdown = dropdown;
    this.options = Array.from(dropdown.querySelectorAll('[role="option"]'));
    this.selectedIndex = 0;
    
    this.handleKeyboard = this.handleKeyboard.bind(this);
    this.dropdown.addEventListener('keydown', this.handleKeyboard);
  }

  handleKeyboard(event) {
    switch (event.key) {
      case 'ArrowUp':
        event.preventDefault();
        this.selectedIndex = Math.max(0, this.selectedIndex - 1);
        this.focusOption();
        break;
      case 'ArrowDown':
        event.preventDefault();
        this.selectedIndex = Math.min(this.options.length - 1, this.selectedIndex + 1);
        this.focusOption();
        break;
      case 'Enter':
      case ' ':
        event.preventDefault();
        this.selectOption();
        break;
    }
  }

  focusOption() {
    this.options[this.selectedIndex].focus();
  }

  selectOption() {
    // Implementation details
  }
}

Evening Social Media: The Infinite Scroll Trap

As I wind down my day browsing social media, I encounter the infamous infinite scroll trap. New content loads automatically, but my keyboard focus gets lost in the process. Here’s how developers can maintain focus during dynamic content updates:

class AccessibleInfiniteScroll {
  constructor() {
    this.lastFocusedElement = null;
    this.observer = new IntersectionObserver(this.handleIntersection.bind(this));
  }

  saveCurrentFocus() {
    this.lastFocusedElement = document.activeElement;
    this.lastFocusedPosition = this.lastFocusedElement.getBoundingClientRect();
  }

  restoreFocus() {
    if (this.lastFocusedElement) {
      this.lastFocusedElement.focus();
      // Scroll to maintain relative position
      window.scrollTo(0, this.lastFocusedPosition.top);
    }
  }

  async handleIntersection(entries) {
    if (entries[0].isIntersecting) {
      this.saveCurrentFocus();
      await this.loadMoreContent();
      this.restoreFocus();
    }
  }
}

Conclusion: Building for Everyone

This day in the life reveals how crucial keyboard navigation is for many users. By implementing proper focus management, providing visible focus indicators, and ensuring all interactive elements are keyboard accessible, we can create websites that truly work for everyone.

Remember these key takeaways:

  1. Never remove focus indicators without providing an alternative
  2. Implement proper focus trapping for modals
  3. Ensure all interactive elements are keyboard accessible
  4. Maintain focus position during dynamic content updates
  5. Test your site using only a keyboard

The web is meant to be accessible to everyone. As developers, it’s our responsibility to ensure that keyboard users have the same quality experience as mouse users. The solutions provided here are just the beginning – regular accessibility testing and user feedback should be part of every development cycle.

Share this blog on:

Click to Copy Link