Docs

Accessibility

How Proa catches common WCAG, WAI-ARIA, and ARIA-in-HTML issues at compile time.

Open Markdown

html_sync! and html! check accessibility rules visible in the authored template at compile time. Invalid roles and required attributes can fail compilation; other findings are warnings. Use proa check in CI and browser checks for behavior that static templates cannot prove.

What Proa Checks

Proa checks a subset of rules from three specifications:

Text Alternatives (WCAG 1.1.1)

Every non-text content element needs a text alternative for screen readers.

// Warning: <img> without alt
html! {
    <img src="/photo.jpg" />
}
//       ^^^^ missing `alt` attribute — screen readers cannot describe this image.
//            Use alt="description" or alt="" for decorative images.

// OK: descriptive alt
html! {
    <img src="/photo.jpg" alt="Team photo from the company retreat" />
}

// OK: decorative image (empty alt)
html! {
    <img src="/divider.svg" alt="" />
}

This also applies to <input type="image"> (must have alt) and <area> with href (must have alt).

ARIA Role Validation

The role attribute must be one of the 82 roles defined in WAI-ARIA 1.2. Abstract roles (like widget, landmark, structure) cannot be used directly.

// Error: invalid role
html! {
    <div role="buttn">"Click"</div>
}
//                 ^^^^^^ "buttn" is not a valid ARIA role. Did you mean "button"?

// Error: abstract role
html! {
    <div role="widget">"Click"</div>
}
//                 ^^^^^^^ "widget" is an abstract role and cannot be used in content.
//                          Use a concrete role like "button", "checkbox", or "slider".

Required ARIA Attributes

Some roles require specific ARIA attributes to function correctly with assistive technology:

// Error: role="checkbox" requires aria-checked
html! {
    <div role="checkbox">"Accept terms"</div>
}
//       ^^^^ role="checkbox" requires the `aria-checked` attribute.
//            Add aria-checked="true", aria-checked="false", or aria-checked="mixed".

// OK
html! {
    <div role="checkbox" aria_checked="false">"Accept terms"</div>
}
RoleRequired attribute(s)
checkboxaria-checked
comboboxaria-controls, aria-expanded
headingaria-level
menuitemcheckboxaria-checked
menuitemradioaria-checked
meteraria-valuenow
optionaria-selected
radioaria-checked
scrollbararia-valuenow
separator (with tabindex)aria-valuenow
slideraria-valuenow
spinbuttonaria-valuenow
switcharia-checked

ARIA Attribute Validation

ARIA attribute names must be ones the macro recognizes (the 48 defined in WAI-ARIA 1.2, plus the newer braille and description attributes), and enumerated values must be valid:

// Error: not a real ARIA attribute
html! {
    <div aria_labeledby="title">"content"</div>
}
//            ^^^^^^^^^^^^^^^ "aria-labeledby" is not a valid ARIA attribute.
//                             Did you mean "aria-labelledby"?

// Error: invalid value
html! {
    <div aria_hidden="yes">"content"</div>
}
//                        ^^^^ "yes" is not a valid value for aria-hidden.
//                              Use "true" or "false".

Redundant Roles

HTML elements have implicit ARIA roles. Adding an explicit role that matches is unnecessary noise:

// Warning: redundant role
html! {
    <button role="button">"Save"</button>
}
//               ^^^^^^^^^^^^^ <button> already has an implicit role of "button".
//                              Remove the role attribute.

// Warning: prefer native element
html! {
    <div role="button" tabindex="0">"Save"</div>
}
//            ^^^^^^^^^^^^^ prefer <button> over <div role="button"> —
//                           native elements provide keyboard support and
//                           accessibility semantics for free.

Tabindex

Positive tabindex values disrupt the natural tab order and are almost always wrong:

// Warning: positive tabindex
html! {
    <input tabindex="5" />
}
//              ^^^^^^^^^^^^^ tabindex="5" overrides the natural document tab order.
//                            Use tabindex="0" to add to natural order, or
//                            tabindex="-1" for programmatic focus only.

Language

Screen readers use the lang attribute to determine pronunciation rules:

// Warning: <html> without lang
html! {
    <html>
        <head>
            <title>"My Page"</title>
        </head>
    </html>
}
// ^^^^^ <html> should have a `lang` attribute (e.g., lang="en").
//       Screen readers use this to select the correct pronunciation rules.

What Proa Cannot Check

These accessibility requirements depend on runtime behavior, computed CSS, or cross-component context that the html! macro can't see:

ConcernWhyWhat to use
Color contrast ratiosRequires computed CSS stylesaxe-core, Lighthouse
Keyboard navigationRequires runtime focus testingManual testing, axe-core
Focus orderRequires runtime DOMaxe-core
Focus visibilityRequires CSS + runtimeBrowser DevTools
Screen reader announcementsRequires assistive technologyNVDA, VoiceOver
Dynamic ARIA state changesRequires runtime JSaxe-core
Cross-component nestingFragments composed at different call sitesNu HTML Checker

Proa's compile-time checks cover what's visible in the HTML output. For the full picture, pair them with axe-core in your integration tests:

// Example: axe-core in Playwright
import { test, expect } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";

test("home page is accessible", async ({ page }) => {
  await page.goto("/");
  const results = await new AxeBuilder({ page }).analyze();
  expect(results.violations).toEqual([]);
});

Standards Reference

StandardWhat it coversLink
WCAG 2.2Success criteria for accessible web content (A, AA, AAA)w3.org/TR/WCAG22
WAI-ARIA 1.2Roles, states, and properties for rich UI widgetsw3.org/TR/wai-aria-1.2
ARIA in HTMLWhich ARIA is valid on which HTML elementsw3.org/TR/html-aria
HTML-AAMHow HTML maps to platform accessibility APIsw3.org/TR/html-aam-1.0
Section 508US federal accessibility law (references WCAG 2.0 AA)section508.gov
EN 301 549EU accessibility standard (references WCAG 2.1 AA)etsi.org

Search

Type at least 2 characters