Out of Order

GitHub
Rules

Every rule, and the clause behind it

All rules are on by default. The severity shown is the default grade, and you can override any of them per call. Every rule maps to a specific WCAG, WAI-ARIA, or HTML-spec clause, linked at the end of its row.

The rule set

Rule Default What it catches Clause
no-positive-tabindex error tabindex > 0, which hijacks the natural order WCAG 2.4.3
visual-order-mismatch warning tab order that doesn't follow top→bottom, left→right reading order WCAG 2.4.3
missing-accessible-name error focusable interactive elements with no accessible name WCAG 4.1.2
aria-hidden-focusable error tabbable element inside aria-hidden="true" WAI-ARIA
hidden-while-focusable error tabbable but invisible (opacity:0, zero size, off-screen, clipped); a :hover reveal doesn't count, only :focus WCAG 2.4.7
clickable-not-focusable error mouse-only control (role/onclick) the keyboard can't reach WCAG 2.1.1
composite-roving-tabindex warning composite widget (toolbar, tablist, ...) with one tab stop per item ARIA APG
focus-escapes-modal error background controls still tabbable while a modal is open ARIA APG
tabindex-on-noninteractive error tabindex="0" on a role-less, non-interactive element ARIA APG
prefer-native-element warning generic tag reimplementing a native control via an interactive role First rule of ARIA
duplicate-autofocus warning more than one focusable autofocus (only the first ever wins) HTML spec
autofocus-not-focusable warning autofocus on a non-focusable element (a no-op on load) HTML spec
nested-interactive error a focusable control nested inside another focusable element WCAG 4.1.2
redundant-tabindex warning tabindex="0" on an already-focusable native control (a no-op) HTML spec

Changing a rule's grade

Pass per-rule overrides through AuditOptions.rules. A rule you do not list keeps its default. For any other rule, re-grade it to "error" or "warning", or switch it "off".

audit(document.body, {
  rules: {
    "visual-order-mismatch": "warning", // re-grade
    "redundant-tabindex": "error", // promote
    "prefer-native-element": "off", // disable
  },
});

// The same overrides reach the overlay through its `audit` option.
trace({ audit: { rules: { "prefer-native-element": "off" } } });

The CLI takes the same overrides through --rule, for example --rule prefer-native-element=off.

Approve a finding in place

Some findings are not bugs: a known false positive, or a deliberate choice you have already weighed. Approve one by putting data-ooo-ignore on the flagged element.

<!-- Approve every finding on this element -->
<button data-ooo-ignore>…</button>

<!-- Approve only the named rules, leave the rest live -->
<div data-ooo-ignore="hidden-while-focusable visual-order-mismatch">…</div>

It is element-scoped, so it approves findings on that element only and is never inherited by its descendants.

Adding your own rule

A rule is a pure function over the computed sequence. Pass yours through the customRules option and it runs with the built-ins, both in the result and on the overlay.

import { trace } from "@out-of-order/trace";
import type { Rule } from "@out-of-order/core";

const noShouting: Rule = {
  id: "no-shouting",
  severity: "warning",
  run: (sequence) =>
    sequence
      .filter((entry) => {
        const text = entry.element.textContent?.trim() ?? "";
        return text !== "" && text === text.toUpperCase();
      })
      .map((entry) => ({
        message: "This label is ALL CAPS, which reads as shouting.",
        fix: "Use sentence case.",
        target: entry,
      })),
};

trace({ audit: { customRules: [noShouting] } });

The full Rule and Finding types are on the API page.