Field — accessibility
Keyboard
- Field adds no tab stop; the control inside keeps its own keyboard behaviour.
labelExtrathat is interactive (a help icon button) becomes a tab stop before the control — keep it a real<button>so the foundation focus ring applies.
ARIA
- The label is a
<label>element but<Field>sets nohtmlForand the control is a sibling, not a child — the association is NOT automatic. Give the control an accessible name:aria-label, or setidon the control andaria-labelledbyon it pointing at the label (add anidto the label vialabelExtra-free markup or your own wrapper). aria-describedbyis automatic as of acrm MR !4085 (fix(ui): chyba pod DS Field se čtečce provází sama, AD 28 Sep 2026, nález from M1's "Nová firma" review):<Field>gives the rendered hint/error span an automaticid(useId()) — error takes precedence over hint, same as the visual rule — and exposes that id through a context (export const useFieldDescribedBy = () => useContext(FieldDescribedBy), inField.tsx, re-exported from@acrm/ui). A control rendered as<Field>'s child readsuseFieldDescribedBy()and falls back to it for its ownaria-describedbyunless the caller passed an explicit one —MaskedInputandPhoneInputboth do this (aria-describedby={describedBy ?? fieldDescribedBy}). Any other control placed inside<Field>/<DrawerField>still needs to opt in the same way (import { useFieldDescribedBy } from '@acrm/ui') — the context exists, but nothing forces a consumer to read it; a bespoke<input>that ignores it is back to the pre-!4085 gap below.- The error span has no
role="alert"/aria-live; a validation message that appears after submit is not announced unless the screen moves focus to the field or adds a live region.
Focus
- Nothing to focus in the wrapper itself; focus styling belongs to the control (Input ring, foundation outline on buttons).
- Clicking the label does not move focus to the control (no
htmlFor).
Contrast
Computed from packages/ui/src/tokens/tokens.css:
- Label
--fg-secondary#7d8184 on--surface-100#ffffff = 3.93:1 (light) — below AA 4.5:1 for 12px text; #9a9ca1 on #1b1c1f = 6.20:1 (dark). - Hint
--fg-muted#98999c on white = 2.85:1 (light); #717479 on #1b1c1f = 3.63:1 (dark) — below AA. - Error
--critical-ink#cb2a2f on white = 5.37:1 (light); #ff6166 on #1b1c1f = 5.80:1 (dark) — passes AA.
Known limits
- No label–control association out of the box (no
htmlFor/idprops on<Field>). - Hint/error now carry an automatic
idand are reachable viauseFieldDescribedBy()(acrm MR !4085), but nothing wires them by itself: only controls that explicitly call the hook (MaskedInput,PhoneInput) pick it up. A plain<input>or a third-party control dropped into<Field>still needsaria-describedby={useFieldDescribedBy()}added by hand. - Neither the hint nor the error span has
role="alert"/aria-live. - Label (3.93:1) and hint (2.85:1) in light theme are below AA for 12px text.
Source: docs/text-field/field.a11y.md · GitLab