{"object":"a11y_ruleset","ruleset_version":"2026.07.16","updated":"2026-07-16","wcag_version":"WCAG 2.2","object_scanned":"static UI markup (JSX/TSX/Vue/Angular/HTML) — WCAG accessibility","rules_total":14,"rules":[{"rule_id":"img-alt","wcag_sc":"1.1.1","wcag_level":"A","severity":"block","category":"text-alternatives","checks":"Image has no alt attribute (screen readers cannot describe it)."},{"rule_id":"input-label","wcag_sc":"4.1.2","wcag_level":"A","severity":"block","category":"forms","checks":"Form control has no associated label or accessible name."},{"rule_id":"button-name","wcag_sc":"4.1.2","wcag_level":"A","severity":"block","category":"name-role-value","checks":"Button has no accessible name (no text, aria-label or title)."},{"rule_id":"link-name","wcag_sc":"2.4.4","wcag_level":"A","severity":"block","category":"name-role-value","checks":"Link has no accessible name (no text, image alt, aria-label or title)."},{"rule_id":"anchor-no-href","wcag_sc":"4.1.2","wcag_level":"A","severity":"caution","category":"name-role-value","checks":"<a> with a click handler but no href is not keyboard-focusable."},{"rule_id":"aria-role-invalid","wcag_sc":"4.1.2","wcag_level":"A","severity":"caution","category":"aria","checks":"Element uses an ARIA role that is not a valid WAI-ARIA role."},{"rule_id":"aria-required-attr","wcag_sc":"4.1.2","wcag_level":"A","severity":"caution","category":"aria","checks":"Element's ARIA role is missing a required state/property."},{"rule_id":"click-noninteractive","wcag_sc":"4.1.2","wcag_level":"A","severity":"caution","category":"interaction","checks":"Click handler on a non-interactive element with no role and no keyboard focusability."},{"rule_id":"positive-tabindex","wcag_sc":"2.4.3","wcag_level":"A","severity":"caution","category":"focus-order","checks":"Element uses a positive tabindex, distorting the natural focus order."},{"rule_id":"heading-order","wcag_sc":"1.3.1","wcag_level":"A","severity":"caution","category":"structure","checks":"Heading level is skipped (e.g. h1 followed by h3)."},{"rule_id":"html-lang","wcag_sc":"3.1.1","wcag_level":"A","severity":"caution","category":"language","checks":"The <html> element has no lang attribute."},{"rule_id":"duplicate-id","wcag_sc":"4.1.1","wcag_level":"A","severity":"caution","category":"parsing","checks":"The same id value is used on more than one element."},{"rule_id":"table-headers","wcag_sc":"1.3.1","wcag_level":"A","severity":"caution","category":"structure","checks":"Data table has no header cells (<th>)."},{"rule_id":"contrast","wcag_sc":"1.4.3","wcag_level":"AA","severity":"block","category":"contrast","checks":"Text colour contrast is below the WCAG AA minimum."}],"categories":["aria","contrast","focus-order","forms","interaction","language","name-role-value","parsing","structure","text-alternatives"],"supported_frameworks":["html","jsx","tsx","vue","angular"],"levels":["A","AA"],"verdicts":["pass","caution","block"],"verdict_thresholds":{"block":"any block-severity finding (missing alt / label / accessible name, failing static contrast)","caution":"only caution-severity findings (heading order, invalid ARIA, duplicate id, positive tabindex, ...)","pass":"no findings"},"static_scope":{"detected_statically":["missing/empty alt, form-control labels, accessible names for buttons/links, heading-order, ARIA role validity + required states, click handlers on non-interactive elements, duplicate ids, positive tabindex, missing html lang, data tables without headers"],"contrast_requires_static_colors":"colour contrast (1.4.3) is computed ONLY when BOTH foreground and background resolve from inline style on the element/an ancestor within the fragment OR the supplied palette/theme; the full CSS cascade / external stylesheets are NOT resolved, so those cases are reported in coverage.skipped_needs_render and NEVER produce a false block.","needs_render_out_of_scope":["computed CSS cascade / external + linked stylesheets","focus visibility and actual keyboard operability at runtime","dynamic content inserted by scripts","reflow / zoom / orientation / target-size geometry","media captions / audio descriptions"]},"fp_suppression":{"dynamic_attribute_aware":"a present-but-dynamic attribute (JSX alt={x}, Vue :alt, Angular [alt]) counts as PRESENT and is NOT flagged as missing","decorative_images":"alt=\"\" and role=presentation/none are treated as valid","hidden_subtrees":"aria-hidden=true / hidden / role=presentation are skipped"},"limits":{"batch_max":50,"max_markup_bytes":524288,"max_findings":500,"max_nodes":20000},"provenance":"Our own clean-room Python checks of the PUBLIC W3C WCAG 2.2 Success Criteria. axe-core (MPL-2.0) is NOT vendored and eslint-plugin-jsx-a11y (MIT) source is NOT copied — only the public WCAG methodology informs the rules. Lightweight stdlib html.parser — no browser, no render, no network, no LLM.","licence":"Our own clean-room checks of the PUBLIC W3C WCAG 2.2 Success Criteria. axe-core (MPL-2.0) is not vendored; eslint-plugin-jsx-a11y (MIT) source is not copied. WCAG methodology is a public standard.","disclaimer":"Automated accessibility indicators, NOT a legal compliance guarantee. Deterministic STATIC analysis of the markup YOU supply; it does NOT render the page, resolve the full CSS cascade or run assistive technology, so it can miss real barriers (false negatives) and cannot certify conformance. A pass verdict is not proof of accessibility — always test with real assistive technology.","note":"Freshness = a data update of the rule registry (no live daemon). The paid /a11y/preflight and /a11y/batch routes evaluate against this ruleset."}