Accessibility
- Zeta Flutter
- Zeta Web
Semantic annotations
Use Semantics widgets to annotate interactive elements that lack an implicit label — icon-only buttons, image buttons, and custom controls:
Semantics(
label: 'Close dialog',
button: true,
child: ZetaIconButton(icon: ZetaIcons.close, onTap: onClose),
)
Set excludeFromSemantics: true on purely decorative widgets to keep the accessibility tree clean.
Touch targets
All tappable targets must meet the minimum touch target size:
- Android: 48x48 logical pixels (
androidTapTargetGuideline) - iOS: 44x44 logical pixels (
iOSTapTargetGuideline)
Use ZetaSpacing tokens — the standard sizes are calibrated to meet these requirements.
Colour contrast
Use ZetaColors — semantic colors already satisfy WCAG AA contrast in all four theme modes (light, dark, high-contrast light, high-contrast dark). Do not apply additional opacity to text without re-checking contrast.
Focus
Never suppress keyboard focus for interactive components. Focus traversal must work correctly in all components.
Testing
We should always have tests for accessibility guidelines. See Testing for the full guide on writing tests.
Semantic annotations
Use semantic HTML inside shadow DOM (<button>, <input>, <label>) rather than <div> with ARIA roles wherever possible. Native elements give you the correct implicit role, keyboard handling, and browser affordances for free.
Set internals.role in the constructor when an explicit ARIA role is needed beyond what the native element provides:
constructor() {
super();
this.internals = this.attachInternals();
this.internals.role = "checkbox";
}
See Component structure for the full guide on aliasing native HTML elements.
Touch targets
All interactive elements must meet minimum touch target sizes to pass WCAG 2.5.8 (AA):
- Minimum: 24x24 CSS pixels (WCAG 2.5.8)
- Recommended: 48x48 CSS pixels (WCAG 2.5.5 / Google)
Enforce with CSS, not just padding — padding alone does not increase the hit area when overflow: hidden is applied by an ancestor:
:host {
display: inline-flex;
min-height: 44px;
min-width: 44px;
align-items: center;
justify-content: center;
}
For icon-only controls where the visual size must stay small, use a transparent padding zone:
.icon-button {
padding: calc((44px - var(--icon-size)) / 2);
}
Focus
- Set
delegatesFocus: trueonshadowRootOptionsfor any component wrapping a native focusable element. - Never suppress focus on disabled elements with
display: none— use theInteractivemixin's built-in disabled-focus handler. - Always remove event listeners added in
connectedCallbackwhendisconnectedCallbackfires.
Testing
We should always have tests for accessibility guidelines. See Testing for the full guide on writing tests.