HTML5 Mastery: The Complete Web Foundation
HomeInsightsCoursesHTMLKeyboard Navigation & Focus Management
Accessibility (A11Y)

Keyboard Navigation & Focus Management

Make your website fully keyboard accessible. Learn focus management, tab order, keyboard shortcuts, and focus trapping for accessible web applications.

Why Keyboard Accessibility Matters

Many users rely on keyboards: motor impairments, blind users with screen readers, power users who prefer keyboard shortcuts, and users with broken/missing mouse.

  • ✅ Screen reader users navigate with keyboard
  • ✅ Motor impairment users may only use keyboard
  • ✅ Power users prefer keyboard efficiency
  • ✅ Temporary situations (broken mouse, remote access)

Focusable Elements

Naturally Focusable

These elements are focusable by default:

HTML
<!-- Naturally focusable elements --&gt;
<a href="/page">Link</a>
<button>Button</button>
<input type="text">
<textarea></textarea>
<select></select>
<audio controls></audio>
<video controls></video>

<!-- Users can Tab to these elements --&gt;

Making Elements Focusable

HTML
<!-- Add tabindex="0" to make focusable --&gt;
<div tabindex="0" role="button" onclick="doSomething()">
    Custom Button
</div>

<!-- tabindex values: --&gt;
<!-- 0: Natural tab order --&gt;
<!-- -1: Programmatically focusable (not in tab order) --&gt;
<!-- 1+: Custom tab order (avoid - confusing!) --&gt;

Removing from Tab Order

HTML
<!-- Remove from tab order --&gt;
<button tabindex="-1">Not keyboard accessible</button>

<!-- Useful for: --&gt;
<!-- - Disabled states --&gt;
<!-- - Hidden content --&gt;
<!-- - Programmatic focus only --&gt;

Focus Indicators

Always Show Focus

CSS
<style>
/* Default browser focus (usually blue outline) */
button:focus {
    /* NEVER do this: */
    /* outline: none; ❌ */
}

/* ✅ Good: Custom but visible focus */
button:focus {
    outline: 3px solid #4CAF50;
    outline-offset: 2px;
}

/* Modern approach: focus-visible (keyboard only) */
button:focus-visible {
    outline: 3px solid #4CAF50;
    outline-offset: 2px;
}

/* Remove focus for mouse clicks only */
button:focus:not(:focus-visible) {
    outline: none;
}
</style>

Focus Styles for Different Elements

CSS
<style>
/* Links */
a:focus {
    outline: 2px solid #2196F3;
    outline-offset: 2px;
    background-color: #E3F2FD;
}

/* Buttons */
button:focus {
    outline: 3px solid #4CAF50;
    box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.3);
}

/* Inputs */
input:focus,
textarea:focus,
select:focus {
    border-color: #2196F3;
    box-shadow: 0 0 0 3px rgba(33, 150, 243, 0.2);
    outline: none;
}

/* Custom elements */
[tabindex]:focus {
    outline: 2px dashed #FF9800;
    outline-offset: 4px;
}
</style>

Tab Order

Natural Tab Order (Best)

HTML
<!-- Tab order follows DOM order --&gt;
<input type="text">      <!-- Tab order: 1 --&gt;
<button>Submit</button>  <!-- Tab order: 2 --&gt;
<a href="/help">Help</a> <!-- Tab order: 3 --&gt;

<!-- ✅ This is best - predictable and logical --&gt;

Custom Tab Order (Avoid)

HTML
<!-- ❌ Don't use positive tabindex values --&gt;
<input type="text" tabindex="3">
<button tabindex="1">Submit</button>
<a href="/help" tabindex="2">Help</a>

<!-- Confusing! Tab order is now: Button → Link → Input --&gt;
<!-- Very hard to maintain --&gt;

Keyboard Event Handling

Custom Interactive Elements

HTML
<div role="button" 
     tabindex="0" 
     onclick="handleClick()"
     onkeydown="handleKeyDown(event)">
    Custom Button
</div>

<script>
function handleKeyDown(event) {
    // Enter or Space activates buttons
    if (event.key === 'Enter' || event.key === ' ') {
        event.preventDefault();
        handleClick();
    }
}

function handleClick() {
    console.log('Button activated!');
}
</script>

<!-- Must handle both click AND keyboard events --&gt;

Common Keyboard Patterns

JAVASCRIPT
<script>
// Enter and Space for buttons
if (event.key === 'Enter' || event.key === ' ') {
    button.click();
}

// Escape to close dialogs
if (event.key === 'Escape') {
    closeDialog();
}

// Arrow keys for navigation
if (event.key === 'ArrowDown') {
    focusNextItem();
}
if (event.key === 'ArrowUp') {
    focusPreviousItem();
}

// Home/End for first/last
if (event.key === 'Home') {
    focusFirstItem();
}
if (event.key === 'End') {
    focusLastItem();
}
</script>

Focus Trapping (Modals)

Trap Focus in Modal Dialog

HTML
<div role="dialog" aria-modal="true" id="myDialog">
    <h2>Dialog Title</h2>
    <button id="close-btn">Close</button>
    <input type="text">
    <button id="save-btn">Save</button>
</div>

<script>
const dialog = document.getElementById('myDialog');
const focusableElements = dialog.querySelectorAll(
    'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];

// Trap focus
dialog.addEventListener('keydown', (e) => {
    if (e.key === 'Tab') {
        if (e.shiftKey) {
            // Shift+Tab
            if (document.activeElement === firstElement) {
                e.preventDefault();
                lastElement.focus();
            }
        } else {
            // Tab
            if (document.activeElement === lastElement) {
                e.preventDefault();
                firstElement.focus();
            }
        }
    }
    
    // Escape to close
    if (e.key === 'Escape') {
        closeDialog();
    }
});

// Focus first element when dialog opens
firstElement.focus();
</script>

Skip Links

HTML
<body>
    <a href="#main-content" class="skip-link">
        Skip to main content
    </a>
    <a href="#nav" class="skip-link">
        Skip to navigation
    </a>
    
    <header>
        <nav id="nav">
            <!-- Many navigation links --&gt;
        </nav>
    </header>
    
    <main id="main-content">
        <!-- Main content --&gt;
    </main>
</body>

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

.skip-link:focus {
    top: 0;
}
</style>

Managing Focus

Moving Focus Programmatically

HTML
<button onclick="openDialog()">Open Dialog</button>

<div id="dialog" hidden>
    <h2 id="dialog-title">Dialog</h2>
    <button id="dialog-close">Close</button>
</div>

<script>
let previousFocus;

function openDialog() {
    // Store current focus
    previousFocus = document.activeElement;
    
    // Show dialog
    dialog.hidden = false;
    
    // Move focus to dialog
    document.getElementById('dialog-close').focus();
}

function closeDialog() {
    // Hide dialog
    dialog.hidden = true;
    
    // Return focus to trigger element
    if (previousFocus) {
        previousFocus.focus();
    }
}
</script>

Focus After Dynamic Content

HTML
<button onclick="loadMore()">Load More</button>
<div id="content"></div>

<script>
async function loadMore() {
    const newContent = await fetchContent();
    
    // Add new content
    const newItem = document.createElement('article');
    newItem.innerHTML = newContent;
    newItem.tabIndex = -1; // Make focusable
    content.appendChild(newItem);
    
    // Move focus to new content
    newItem.focus();
}
</script>

Keyboard Shortcuts

HTML
<script>
// Global keyboard shortcuts
document.addEventListener('keydown', (e) => {
    // Don't trigger if typing in input
    if (e.target.matches('input, textarea')) return;
    
    // Cmd/Ctrl + K: Search
    if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
        e.preventDefault();
        openSearch();
    }
    
    // ? : Show keyboard shortcuts help
    if (e.key === '?') {
        showKeyboardHelp();
    }
    
    // / : Focus search
    if (e.key === '/') {
        e.preventDefault();
        document.getElementById('search').focus();
    }
});
</script>

<!-- Provide keyboard shortcuts guide --&gt;
<dialog id="keyboard-help">
    <h2>Keyboard Shortcuts</h2>
    <dl>
        <dt><kbd>Ctrl</kbd> + <kbd>K</kbd></dt>
        <dd>Open search</dd>
        
        <dt><kbd>?</kbd></dt>
        <dd>Show this help</dd>
        
        <dt><kbd>/</kbd></dt>
        <dd>Focus search box</dd>
    </dl>
</dialog>

Testing Keyboard Accessibility

Manual Testing Checklist

  • ☑ Unplug mouse and navigate entire site with keyboard
  • ☑ Press Tab - can you reach all interactive elements?
  • ☑ Press Shift+Tab - can you go backwards?
  • ☑ Press Enter/Space on buttons - do they activate?
  • ☑ Press Escape - do modals close?
  • ☑ Are focus indicators always visible?
  • ☑ Is tab order logical (left-to-right, top-to-bottom)?
  • ☑ Can you access dropdown menus?
  • ☑ Can you submit forms?
  • ☑ Can you close overlays/modals?

Common Keyboard Issues

  • ❌ Removed focus outline (outline: none)
  • ❌ Div/span "buttons" without keyboard support
  • ❌ Custom dropdowns not keyboard accessible
  • ❌ Modals without focus trapping
  • ❌ Click-only interactions (no keyboard handler)
  • ❌ Content that appears on hover only
  • ❌ Skip links missing

Best Practices

✅ Do

  • Use semantic HTML (button, a, input)
  • Always show focus indicators
  • Use tabindex="0" for custom controls
  • Handle both click and keyboard events
  • Trap focus in modals
  • Return focus after closing dialogs
  • Provide skip links
  • Test with keyboard only
  • Use logical tab order

❌ Don't

  • Remove focus outlines
  • Use positive tabindex values
  • Create keyboard traps (except modals)
  • Rely on mouse-only interactions
  • Use div/span for buttons
  • Forget to handle Escape key
  • Make custom controls without keyboard support
  • Skip keyboard testing

What's Next?

Learn SEO basics and how HTML structure affects search engine rankings.