npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bernierllc/validators-a11y-focus-order

v0.6.0

Published

Accessibility focus order validation for the BernierLLC validators ecosystem - validates keyboard navigation, tab order, and focus visibility

Readme

@bernierllc/validators-a11y-focus-order

Accessibility focus order validation for keyboard navigation and WCAG compliance. Part of the BernierLLC validators ecosystem.

Overview

This validator ensures proper keyboard navigation and focus management in HTML by checking:

  • Tab Index Validation - Detects positive tabindex values that disrupt natural tab order
  • Focus Visibility - Ensures focusable elements have visible focus indicators
  • Keyboard Trap Detection - Identifies potential keyboard traps where focus cannot escape
  • Skip Link Validation - Validates skip link targets and implementation

Installation

npm install @bernierllc/validators-a11y-focus-order

Quick Start

import { validateA11yFocusOrder } from '@bernierllc/validators-a11y-focus-order';

// Validate HTML for focus order issues
const problems = await validateA11yFocusOrder({
  html: '<button tabindex="5">Click me</button>',
  validateTabIndex: true,
  validateFocusVisibility: true,
  validateKeyboardTraps: true,
  validateSkipLinks: true
});

problems.forEach(problem => {
  console.log(`${problem.severity}: ${problem.message}`);
  console.log(`Suggestion: ${problem.suggestion}`);
});

Usage

Basic Validation

import { validateA11yFocusOrder } from '@bernierllc/validators-a11y-focus-order';

const html = `
  <nav>
    <a href="#main">Skip to main content</a>
    <button tabindex="3">Bad practice</button>
  </nav>
  <main id="main">Content</main>
`;

const problems = await validateA11yFocusOrder({ html });

// Output:
// error: Element has positive tabindex="3" which disrupts natural keyboard navigation order

Detailed Analysis

import { checkFocusOrder } from '@bernierllc/validators-a11y-focus-order';

const result = checkFocusOrder({
  html: document.body.innerHTML,
  validateTabIndex: true,
  validateFocusVisibility: true,
  validateKeyboardTraps: true,
  validateSkipLinks: true
});

console.log(`Total focusable elements: ${result.totalFocusableElements}`);
console.log(`Elements with positive tabindex: ${result.positiveTabIndices.length}`);
console.log(`Missing focus indicators: ${result.missingFocusVisibility.length}`);
console.log(`Potential keyboard traps: ${result.keyboardTraps.length}`);
console.log(`Skip link issues: ${result.skipLinkIssues.length}`);
console.log(`All checks pass: ${result.passes}`);

Selective Validation

// Only validate tab index usage
const problems = await validateA11yFocusOrder({
  html,
  validateTabIndex: true,
  validateFocusVisibility: false,
  validateKeyboardTraps: false,
  validateSkipLinks: false
});

// Only validate skip links
const skipLinkProblems = await validateA11yFocusOrder({
  html,
  validateTabIndex: false,
  validateSkipLinks: true
});

Custom Tab Index Tolerance

// Allow positive tabindex up to 3
const problems = await validateA11yFocusOrder({
  html,
  validateTabIndex: true,
  maxPositiveTabIndex: 3
});

Validation Rules

1. Positive Tab Index (a11y-focus-order/positive-tabindex)

WCAG Reference: 2.4.3 Focus Order

Positive tabindex values (tabindex="1", tabindex="2", etc.) disrupt the natural document flow and create confusing navigation patterns for keyboard users.

Bad Practice:

<button tabindex="5">First button</button>
<button tabindex="1">Second button (focused first!)</button>
<button>Third button (focused last)</button>

Best Practice:

<!-- Natural tab order follows DOM order -->
<button>First button</button>
<button>Second button</button>
<button>Third button</button>

<!-- Use tabindex="0" to make non-focusable elements focusable -->
<div tabindex="0" role="button">Custom button</div>

<!-- Use tabindex="-1" for programmatic focus only -->
<div tabindex="-1" id="error-message">Error: form invalid</div>

2. Missing Focus Visibility (a11y-focus-order/missing-focus-visibility)

WCAG Reference: 2.4.7 Focus Visible

All focusable elements must have visible focus indicators so keyboard users can see which element has focus.

Bad Practice:

<button style="outline:none">No visible focus indicator</button>
<a href="#" style="outline:0">Link without focus indicator</a>

Best Practice:

<!-- Use default focus outline -->
<button>Default focus outline</button>

<!-- Custom focus styles with :focus or :focus-visible -->
<button class="custom-focus">Custom focus indicator</button>

<style>
.custom-focus:focus {
  outline: 2px solid blue;
  outline-offset: 2px;
}

/* Modern approach with :focus-visible */
.custom-focus:focus-visible {
  box-shadow: 0 0 0 3px rgba(0, 100, 255, 0.5);
}
</style>

3. Keyboard Trap Detection (a11y-focus-order/keyboard-trap)

WCAG Reference: 2.1.2 No Keyboard Trap

Users must be able to navigate away from any component using only the keyboard. Trapping focus violates accessibility requirements.

Bad Practice:

<div onkeydown="if(event.key === 'Tab') event.preventDefault()">
  Trapped content
</div>

Best Practice:

<!-- Modal with proper escape mechanism -->
<div role="dialog" aria-labelledby="modal-title">
  <h2 id="modal-title">Modal Dialog</h2>
  <button>Action</button>
  <button onclick="closeModal()">Close</button>
</div>

<script>
function setupModal() {
  document.addEventListener('keydown', (event) => {
    if (event.key === 'Escape') {
      closeModal();
    }
  });
}
</script>

4. Invalid Skip Links (a11y-focus-order/invalid-skip-link)

WCAG Reference: 2.4.1 Bypass Blocks

Skip links must point to valid targets within the page to help keyboard users bypass repetitive navigation.

Bad Practice:

<!-- Target doesn't exist -->
<a href="#main-content">Skip to main content</a>
<main id="content">Content here</main>

<!-- Not a fragment identifier -->
<a href="/main">Skip to main</a>

Best Practice:

<!-- Valid skip link with matching target -->
<a href="#main-content" class="skip-link">Skip to main content</a>
<main id="main-content">
  <h1>Main Content</h1>
  <p>Content here...</p>
</main>

<style>
/* Visually hidden but available to screen readers and keyboard users */
.skip-link {
  position: absolute;
  left: -10000px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.skip-link:focus {
  position: static;
  width: auto;
  height: auto;
}
</style>

API Reference

validateA11yFocusOrder(input, utils?)

Main validation function that checks all focus order rules.

Parameters:

  • input: FocusOrderCheckInput - Configuration object
    • html: string - HTML content to validate (required)
    • validateTabIndex?: boolean - Check for positive tabindex values (default: true)
    • validateFocusVisibility?: boolean - Check for missing focus indicators (default: true)
    • validateKeyboardTraps?: boolean - Check for keyboard traps (default: true)
    • validateSkipLinks?: boolean - Validate skip link targets (default: true)
    • maxPositiveTabIndex?: number - Maximum allowed positive tabindex (default: 0)
  • utils?: SharedUtils - Optional shared utilities

Returns: Promise<Problem[]> - Array of validation problems

checkFocusOrder(input)

Analyzes HTML and returns detailed focus order metrics.

Parameters:

  • input: FocusOrderCheckInput - Configuration object

Returns: FocusOrderCheckResult

  • passes: boolean - Whether all checks pass
  • positiveTabIndices: FocusableElement[] - Elements with positive tabindex
  • missingFocusVisibility: FocusableElement[] - Elements without focus indicators
  • keyboardTraps: FocusableElement[] - Potential keyboard traps
  • skipLinkIssues: FocusableElement[] - Invalid skip links
  • totalFocusableElements: number - Total focusable elements found

Individual Rules

Each rule can be imported and used separately:

import {
  positiveTabindex,
  missingFocusVisibility,
  keyboardTrap,
  invalidSkipLink
} from '@bernierllc/validators-a11y-focus-order';

Integration with BernierLLC Ecosystem

With Validators Runner

import { createRunner } from '@bernierllc/validators-runner';
import { a11yFocusOrderValidator } from '@bernierllc/validators-a11y-focus-order';

const runner = createRunner({
  validators: [a11yFocusOrderValidator]
});

const results = await runner.validate({
  html: document.body.innerHTML
});

With Custom Reporters

import { createConsoleReporter } from '@bernierllc/validators-reporters';
import { validateA11yFocusOrder } from '@bernierllc/validators-a11y-focus-order';

const problems = await validateA11yFocusOrder({ html });
const reporter = createConsoleReporter();
reporter.report(problems);

Real-World Examples

Navigation with Skip Link

const html = `
  <body>
    <a href="#main" class="skip-link">Skip to main content</a>
    <nav>
      <ul>
        <li><a href="/">Home</a></li>
        <li><a href="/about">About</a></li>
      </ul>
    </nav>
    <main id="main">
      <h1>Welcome</h1>
    </main>
  </body>
`;

const result = checkFocusOrder({ html, validateSkipLinks: true });
// result.passes === true
// result.skipLinkIssues.length === 0

Form with Proper Focus Management

const html = `
  <form>
    <label for="username">Username:</label>
    <input id="username" type="text" />

    <label for="password">Password:</label>
    <input id="password" type="password" />

    <button type="submit">Log In</button>
    <button type="button" tabindex="-1" id="hidden-helper">
      Helper (programmatic focus only)
    </button>
  </form>
`;

const result = checkFocusOrder({ html });
// result.passes === true
// result.totalFocusableElements === 4
// result.positiveTabIndices.length === 0

Modal Dialog with Escape Handling

const html = `
  <div role="dialog" aria-labelledby="dialog-title" aria-modal="true">
    <h2 id="dialog-title">Confirm Action</h2>
    <p>Are you sure?</p>
    <button onclick="confirm()">Yes</button>
    <button onclick="cancel()">No</button>
  </div>
`;

const problems = await validateA11yFocusOrder({
  html,
  validateKeyboardTraps: true
});
// Should not flag properly implemented modals

WCAG Compliance

This validator helps ensure compliance with:

  • WCAG 2.1.2: No Keyboard Trap (Level A)
  • WCAG 2.4.1: Bypass Blocks (Level A)
  • WCAG 2.4.3: Focus Order (Level A)
  • WCAG 2.4.7: Focus Visible (Level AA)

Best Practices

  1. Avoid Positive Tab Index: Never use positive tabindex values. Use DOM order for natural tab sequence.

  2. Always Provide Focus Indicators: Ensure all interactive elements have visible focus states.

  3. Test with Keyboard Only: Verify your site is fully navigable using only the keyboard.

  4. Implement Skip Links: Provide skip links to bypass repetitive navigation.

  5. Avoid Keyboard Traps: Ensure users can always escape from modals and components.

  6. Use Semantic HTML: Prefer native focusable elements (button, a, input) over custom implementations.

Integration Status

Logger Integration

Not applicable for primitive validators. This package provides pure validation functions without side effects. Logging is handled by the calling application or validators-runner.

Docs-Suite Integration

Ready - Complete markdown documentation and API documentation available via TypeDoc. All functions are fully documented with JSDoc comments for automatic documentation generation.

NeverHub Integration

Not applicable for primitive validators. NeverHub integration is handled at the runner/orchestrator level. This package is designed to be framework-agnostic and can be used standalone or through validators-runner with NeverHub support.

License

Copyright (c) 2025 Bernier LLC. All rights reserved.

This package is part of the BernierLLC validators ecosystem for accessibility and WCAG compliance validation.

See Also