Docs
Accessibility
How Proa catches common WCAG, WAI-ARIA, and ARIA-in-HTML issues at compile time.
Proa checks accessibility rules at compile time via the html! macro. No separate linter to install, no runtime cost, no CI step to forget. Violations are caught the same way type errors are, during cargo build.
What Proa Checks
Proa's compile-time checks cover three specs:
- WCAG 2.2, Web Content Accessibility Guidelines (Level A and AA)
- WAI-ARIA 1.2, Accessible Rich Internet Applications
- ARIA in HTML, Rules for which ARIA roles and attributes are valid on which HTML elements
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>
}
| Role | Required attribute(s) |
|---|---|
checkbox | aria-checked |
combobox | aria-controls, aria-expanded |
heading | aria-level |
menuitemcheckbox | aria-checked |
menuitemradio | aria-checked |
meter | aria-valuenow |
option | aria-selected |
radio | aria-checked |
scrollbar | aria-valuenow |
separator (with tabindex) | aria-valuenow |
slider | aria-valuenow |
spinbutton | aria-valuenow |
switch | aria-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:
| Concern | Why | What to use |
|---|---|---|
| Color contrast ratios | Requires computed CSS styles | axe-core, Lighthouse |
| Keyboard navigation | Requires runtime focus testing | Manual testing, axe-core |
| Focus order | Requires runtime DOM | axe-core |
| Focus visibility | Requires CSS + runtime | Browser DevTools |
| Screen reader announcements | Requires assistive technology | NVDA, VoiceOver |
| Dynamic ARIA state changes | Requires runtime JS | axe-core |
| Cross-component nesting | Fragments composed at different call sites | Nu 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 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
| Standard | What it covers | Link |
|---|---|---|
| WCAG 2.2 | Success criteria for accessible web content (A, AA, AAA) | w3.org/TR/WCAG22 |
| WAI-ARIA 1.2 | Roles, states, and properties for rich UI widgets | w3.org/TR/wai-aria-1.2 |
| ARIA in HTML | Which ARIA is valid on which HTML elements | w3.org/TR/html-aria |
| HTML-AAM | How HTML maps to platform accessibility APIs | w3.org/TR/html-aam-1.0 |
| Section 508 | US federal accessibility law (references WCAG 2.0 AA) | section508.gov |
| EN 301 549 | EU accessibility standard (references WCAG 2.1 AA) | etsi.org |