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.