Input — accessibility

Keyboard

  • Native <input>: Tab moves focus in and out, typing edits, Enter submits the enclosing form. disabled removes it from the tab order; readOnly does NOT — a read-only field stays focusable so the value can be selected and copied from the keyboard.
  • No shortcut chip on the plain input; a shortcut belongs to Input with icon and kbd.
  • Inside .acrm-range the two inputs are two tab stops in reading order (from, then to).

ARIA

  • No role needed; the element is a native text input.
  • Accessible name: <Field> renders the label as a sibling <label> without htmlFor, so the name is not linked automatically — pass aria-label, or id on the input and aria-labelledby / wrap the control in the label yourself.
  • Error: put aria-invalid="true" on the input together with .is-error; the class only draws the frame. Link the message with aria-describedby pointing at the .acrm-field-error element when the screen renders one.
  • type="email" / type="url" / type="number" expose the matching virtual keyboard and browser validation.

Focus

  • The foundation ring in ds.css (:where(button, a[href], [role=…], [tabindex])) does not cover inputs; .acrm-input:focus draws its own: outline: none, border-color: var(--accent), 3px ring color-mix(in srgb, var(--accent) 16%, transparent).
  • The ring shows on mouse focus too (:focus, not :focus-visible) — intended, the caret position matters for pointer users as well.
  • Error keeps the same geometry with --critical; a focused error field shows the accent ring (focus rule wins by order).

Contrast

Computed from packages/ui/src/tokens/tokens.css (WCAG 2.x relative luminance):

  • Value --fg #18191b on --surface-100 #ffffff = 17.59:1 (light); #eceded on --surface-50 #232529 = 13.09:1 (dark).
  • Placeholder --fg-muted #98999c on white = 2.85:1 (light); #717479 on #232529 = 3.27:1 (dark) — below AA 4.5:1 for text.
  • Disabled value --fg-muted on --surface-75 #fbfbfb = 2.75:1 (light); #717479 on #161719 = 3.82:1 (dark).
  • Resting border --border-strong #dcdbdd on white = 1.38:1 (light); #3a3c42 on #1b1c1f = 1.55:1 (dark) — hairline, below the 3:1 non-text minimum.
  • Focus border --accent #0072f5 on white = 4.44:1 (light); on #1b1c1f = 3.83:1 (dark) — passes 3:1.
  • Error border --critical #e5484d on white = 3.91:1 (light); on #1b1c1f = 4.35:1 (dark) — passes 3:1.

Read-only vs disabled

  • Read-only = the value is real and relevant, only not editable here: focusable, announced by screen readers with its value and "read only", selectable, full-contrast text (--fg on --surface-75, 17:1 light / 15:1 dark).
  • Disabled = the control does not apply right now: out of the tab order, muted text (below AA, by design). Never use disabled for a value the user needs to read or copy.

Known limits

  • Placeholder and disabled text sit below AA; the placeholder is a hint only, the real label and format live in <Field>.
  • The resting hairline is far below 3:1; the field is found by its label and the focus ring, not by its border.
  • <Field> does not link label and control — the accessible name must be added by the screen.
  • .is-error is visual only; aria-invalid is not set by the component.

Source: docs/text-field/input.a11y.md · GitLab