Enforced Accessibility
XMLUI treats common accessibility requirements as framework contracts instead of informal component conventions. Enforced accessibility means the framework does three things for app authors:
- It generates predictable accessibility attributes when the component already has enough information.
- It asks you for missing intent when only the app author can know the right accessible name or label.
- It reports accessibility diagnostics through the XMLUI analyzer surfaces before the problem reaches users.
The accessibility analyzer checks parsed markup in the language server, the
Vite plugin, and xmlui check --a11y. XMLUI also adds reusable runtime
primitives for keyboard and screen-reader flows, validates theme contrast
during theme resolution, and exposes a stable automationId surface for test
and assistive automation tooling.
What problems this prevents
- Icon-only buttons and interactive controls that ship without an accessible name.
- Modals without a title and form controls that are not associated with a label.
- Duplicate landmarks and pages with navigation but no skip-link path to main content.
- Themes that produce low text/background contrast for the framework's well-known color pairs.
- Tests coupled to incidental DOM structure instead of a stable
data-automation-id.
How it works
The XMLUI accessibility analyzer reads component metadata from the same registry
used by type-contract validation. Components declare their accessibility role,
accessible-name props, and landmark role in metadata. The analyzer reports
diagnostics against the parsed XMLUI tree, and those diagnostics appear in the
LSP, Vite builds, and the xmlui check --a11y CLI path.
Vite supports the same rollout modes used by the other Managed React analyzers:
"off", "warn", and "strict".
What XMLUI generates
XMLUI does not guess application meaning, but it does reuse meaning you already provided in markup.
For many wrapped components, XMLUI resolves an aria-label in this order:
your explicit aria-label, a component-specific derived label, the component's
default accessibility label, and finally the component's label prop.
<TextBox label="Email address" />
<TextBox placeholder="Search orders" />
<Avatar name="Ada Lovelace" />
<Spinner />These produce accessible names from existing values:
<input aria-label="Email address" />
<input aria-label="Search orders" />
<div aria-label="Ada Lovelace">...</div>
<div aria-label="Loading">...</div>TextBox, Avatar, and Spinner all reuse values already present in the markup. Form components such as FormItem also connect visible labels, required state, and validation messages to the underlying control:
<FormItem label="Email address" required="true">
<TextBox bindTo="email" />
</FormItem>XMLUI gives the input a stable generated id, connects the visible label with
htmlFor, marks the control with aria-required, and connects validation text
with aria-describedby when messages are shown.
Composite components generate their own navigation state where the component knows the correct value. For example:
<Pagination totalItems="{250}" pageSize="{25}" />
<NavGroup label="Reports">...</NavGroup>Pagination renders a navigation region
labelled "Pagination" and page buttons with names such as "Next page" or
"Page 2 (current)". Disclosure-style components such as
NavGroup,
Accordion, and
ExpandableItem update aria-expanded
as they open and close.
When XMLUI cannot infer a meaningful name, the analyzer tells you what to add:
<!-- Reports an accessibility diagnostic: icon-only button has no name. -->
<Button icon="trash" />
<!-- OK: the action has a screen-reader name. -->
<Button icon="trash" aria-label="Delete order" />XMLUI also ships dedicated accessibility components and behaviors:
SkipLinkrenders a focus-visible skip link that lets keyboard, switch-device, and screen-reader users bypass repeated navigation and move focus to the active page's main content. Itstargetcan point at a DOM id, an XMLUI component id, or a test id.FocusScopecentralizes focus trapping, initial focus, and focus restoration for custom overlays and panels.LiveRegionprovides polite or assertive screen-reader announcements for updates that should not move focus. Toasts and runtime errors announce through the shared global live region.withLiveRegionadds the same announcement pattern to supported text-like components such as Text, headings (H1-H6), Badge, NoResult, and ProgressBar.
Theme resolution runs a contrast checker over known foreground/background token pairs. In development it warns; with strict accessibility enabled it escalates those findings.
Enabling strict mode
strictAccessibility remains opt-in for the current migration window:
{
"xmluiConfig": {
"strictAccessibility": true
}
}With strict mode enabled, warn-level accessibility diagnostics escalate to errors and can fail the Vite build. The default flip is reserved for the next major release so existing apps get a warning-first path.
For navigation-heavy apps, autoSkipLink can insert the default skip link before the app content:
{
"xmluiConfig": {
"autoSkipLink": true
}
}Use an explicit SkipLink when different
routes need different first focus targets, for example a dashboard filter on /
and an order search field on /orders.
Use automationId when a component needs a stable automation hook:
<Button automationId="save-order" label="Save" />XMLUI renders this Button as
data-automation-id="save-order" on the component decorator without making the
value part of the public visual design.