HTML Course 11.4 Keyboard navigation and tab order (tabindex)


Keyboard Navigation Basics

Keyboard navigation in HTML relies primarily on the Tab key to move focus from one element to the next,
following the document’s focusable elements in source order. Holding Shift and pressing Tab moves focus backward.

By default, the following elements are focusable in tab order:

  • <a> with an href
  • <button>, <input>, <select>, <textarea>
  • <area> with an href
  • <iframe>

Non-interactive elements (e.g. <div>, <span>) are not focusable by default but can be made so with
the tabindex attribute.

The tabindex Attribute

The tabindex attribute controls an element’s participation in the tab order. Valid values are:

Value Effect Use Case
-1 Element is focusable programmatically (element.focus()) but skipped by Tab. Container elements for scripts, dialogs, focus management.
0 Element is focusable in natural document order. Making non-interactive elements focusable (e.g. custom widgets).
1, 2, … Explicit focus order: lowest positive number first, then increasing, then tabindex='0' elements,
then native focusable elements without a tabindex.
Rarely advised—can confuse users and assistive tech if misused.

tabindex=’-1′

Use when you want to move focus programmatically (via JavaScript) but not include the element in the keyboard tab sequence.
Common for dialog containers or skip-link targets.

tabindex=’0′

Makes an element keyboard-focusable in its default position. Ideal for custom interactive components:

tabindex=’>0′

Defines an explicit tab order. Generally discouraged because:

  • Breaks source order semantics.
  • Hard to maintain—adding or removing elements requires renumbering.
  • Inconsistent across assistive technologies.

Example 1: Simple Focusable Div

A non-interactive <div> made keyboard-focusable with tabindex='0'. Notice the outline appears when focused.

  <!-- A regular div without native focusability -->
  <div tabindex='0'>
    <p>This div can receive keyboard focus.</p>
    <button>Inside button</button>
  </div>
  
  <!-- JavaScript can also focus it programmatically -->
  <script>
    const myDiv = document.querySelector('div[tabindex='0']');
    // myDiv.focus(); // moves focus here without pressing Tab
  </script>
  

Example 2: Skip Navigation Link

A common accessibility pattern: an off-screen “Skip to main content” link that becomes visible when focused.
It uses tabindex='0' on the target to ensure focus lands there.

  <!-- Skip Link -->
  <a href='#main-content'>Skip to main</a>
  
  <!-- ... page navigation, banners ... -->
  
  <!-- Main Content Section -->
  <section id='main-content' tabindex='0'>
    <h2>Main Content</h2>
    <p>Here is the primary content.</p>
  </section>
  

Example 3: Pitfalls of tabindex>0

Explicitly numbered tabindex values can lead to confusing tab order if not managed carefully.

  <!-- Bad Practice: tabindex positives -->
  <button tabindex='3'>Third in order</button>
  <button tabindex='1'>First in order</button>
  <button tabindex='2'>Second in order</button>
  
  <!-- Natural document order would be simpler: -->
  <!-- 
    <button>First</button>
    <button>Second</button>
    <button>Third</button>
  -->
  

Advanced: Roving tabindex Pattern

For custom widgets (e.g., menus, radio groups), only one child has tabindex='0' and others -1.
Arrow keys move the “roving” focus, preserving Tab behavior.

  <!-- Custom tab-style navigation -->
  <ul role='menu'>
    <li role='menuitem' tabindex='0'>Item A</li>
    <li role='menuitem' tabindex='-1'>Item B</li>
    <li role='menuitem' tabindex='-1'>Item C</li>
  </ul>
  
  <script>
    const items = document.querySelectorAll('[role='menuitem']');
    let current = 0;
    items.forEach((el, idx) => {
      el.addEventListener('keydown', e => {
        if (e.key === 'ArrowRight') {
          items[current].tabIndex = -1;
          current = (current + 1) % items.length;
          items[current].tabIndex = 0;
          items[current].focus();
          e.preventDefault();
        }
        if (e.key === 'ArrowLeft') {
          items[current].tabIndex = -1;
          current = (current - 1 + items.length) % items.length;
          items[current].tabIndex = 0;
          items[current].focus();
          e.preventDefault();
        }
      });
    });
  </script>
  

Best Practices

  • Avoid positive tabindex values. Use 0 or -1 only.
  • Keep focusable elements in logical source order.
  • Ensure visual focus indicators are visible (browsers provide default outlines).
  • Use ARIA roles in tandem (e.g., role='menu') for complex widgets.
  • Test with screen readers and only keyboard navigation.


Posted

in

by

Tags:

Comments

Leave a Reply