Skip to content

Rules reference

Eight rules. Every one cited, tested, and CI-gated.

This page mirrors the repo’s rule registry — the file a reviewer reads to learn a rule’s tier, basis, and spec citation without reading implementation code. The registry is the source of truth; nothing is marked shipped there that isn’t tested and CI-gated in the repo.

Format tier — basis native | declared. Auto-applied on save / --fix. Never add asserted values.

no-redundant-role

formatSHIPPED

Removes an explicit role that duplicates the element's implicit role (<button role="button"> → <button>).

Resolves ancestor-dependent roles statically and stays silent on anything undecidable — a broken ancestor chain, a spread, a dynamic role.

Basis
native
Spec
WAI-ARIA 1.2 §6.3 — "Authors MUST NOT use an explicit role that is the same as the element’s implicit ARIA role."

no-unsupported-aria

formatSHIPPED

Removes aria-* attributes WAI-ARIA doesn't support on the element's resolved role (<button aria-checked> → <button>).

Global ARIA properties are never touched, and an unresolved role means every aria-* on the element stays put.

Basis
native
Spec
ARIA in HTML §2.4 / WAI-ARIA 1.2 §6.5 — ARIA attributes not in the allowed set for a role SHOULD be ignored. Removing them makes source honest.

aria-syntax-normalize

formatSHIPPED

Canonical lowercase for ARIA attribute names (aria-Label → aria-label) and enumerated values (aria-hidden="True" → "true").

Only ever changes character case — tested as a property, not a promise. Attribute ordering and role-value casing are deliberately out of scope.

Basis
native
Spec
WAI-ARIA 1.2 §6.1 — ARIA attribute names and state/property token values are processed case-insensitively; lowercase is canonical and lossless.

Lint tier — located diagnostics, human-reviewed, never auto-applied.

Most carry inferred basis. Several are the interesting exception: a native fact that is still lint-tier — because the finding is advisory (idref-resolves), because only a human can author the repair (img-needs-alt, control-needs-name), or because multiple valid repairs exist and Aria refuses to pick (aria-hidden-not-focusable). Basis and tier deliberately diverge, and the registry documents each reason.

interactive-role-required

lintSHIPPED

Flags a generic element (div, span) with a click handler and no role, then inspects its children to decide what to say.

A button-like element gets a role="button" suggestion; an ambiguous one is report-only. Every intrinsic diagnostic is inferred basis, so the gate keeps it a suggestion — until config declares the component with injectRole, when it graduates to a real auto-fix.

Basis
inferred → declared via config
Spec
WCAG 2.1 SC 4.1.2 — UI components must have a role. Non-semantic elements with event handlers need one.

control-needs-name

lintSHIPPED

Flags an interactive control with no accessible name — an icon-only <button>/<a href>, an unlabeled <input>/<textarea>/<select>.

A name can come from text content, aria-label, a resolving aria-labelledby, or an associated <label>. A placeholder is explicitly not a name.

Basis
native (report-only) · declared for the component path
Spec
WCAG 2.1 SC 4.1.2 — UI components must have an accessible name. Cannot author the name text — flagging only.

img-needs-alt

lintSHIPPED

Flags an <img> exposed as an image with no accessible name and no decorative signal.

alt="", role="presentation", aria-label, aria-labelledby, and aria-hidden all legitimately silence it — the point is presence of some valid encoding, not the word "alt".

Basis
native (report-only)
Spec
WCAG 2.1 SC 1.1.1 — All non-decorative images need alt text. Cannot author the text — flagging only.

idref-resolves

lintSHIPPED

Flags aria-labelledby / aria-describedby / aria-controls references to an id that doesn't exist anywhere in the file.

The first rule where basis and tier deliberately diverge: the detection is a native fact, but it stays advisory because a reference can legitimately point across files. Literal-to-literal only.

Basis
native (report-only)
Spec
WAI-ARIA 1.2 §7 — aria-labelledby/describedby/controls MUST reference a valid id. In-file check only.

aria-hidden-not-focusable

lintSHIPPED

Flags aria-hidden="true" on a focusable element — or on a subtree that still contains one (the common modal/dropdown bug).

A "focusable ghost" a keyboard user can reach but assistive tech can't see. Report-only because several valid repairs exist and which is right depends on intent the tool can't see.

Basis
native (report-only)
Spec
WAI-ARIA 1.2 — aria-hidden=true MUST NOT be applied to a focusable element (or a subtree containing one). Fix is ambiguous → lint.

Watch queue — roadmap, awaiting host platform support. Not shipped.

  • Vue SFC template rules — blocked on the oxlint Vue parser (in progress upstream).
  • Svelte component rules — blocked on an oxlint Svelte parser (not started).
  • HTML template rules — blocked on oxlint / parse5 integration (not started).

This page is generated from a structured snapshot of the registry (see data/rules.ts in the site repo), checked against the live registry by a drift script. If the registry and this page ever disagree, the registry wins.