MiHCM
primitives/@mihcm/ui·v0.21.0·stable

AccessLevelGroup

4-state segmented button: None / View / Edit / Full Access. Each option has its own brand color when active.

WCAG 2.1 AA — release blocker

Web (React DOM)

  • Renders <div role="radiogroup" aria-label="Access level">.
  • Each option is a <button role="radio" aria-checked={isActive}>.
  • The group label defaults to "Access level" — pass aria-label when the surrounding context already names the field (e.g. on a per-module row, set aria-label="Access level for Payroll").
  • Focus ring: 2 px ring on focus-visible, mapped to --color-ring.
  • 4.5:1 contrast verified on every tone × active/inactive × light/dark.

React Native

  • Group renders with accessibilityRole="tablist" (RN has no radiogroup).
  • Each option is accessibilityRole="radio" with accessibilityState={{ selected, disabled }}.

Keyboard

  • Tab moves into the group.
  • Arrow keys are not custom-handled — each radio is independently focusable via Tab. This is intentional for short option lists (4 items); for longer radio groups consider a custom keyboard model.
  • Enter / Space activates the focused option.

Reduced motion

  • Background-colour transitions limited to duration-short (200 ms). User agents honour prefers-reduced-motion.

Color is never the only signal

  • The active option is bold (font-semibold) — not just a colour shift. A colour-blind user can still parse which level is selected.
  • Each tone is paired with a distinct label string ("None", "View", "Edit", "Full Access") that disambiguates standalone.

Audit checklist

  • Group has a meaningful label (aria-label / accessibilityLabel).
  • Tab reaches every option.
  • Active option distinguishable without colour (weight + label).
  • Disabled state at 50 % opacity AND disabled attribute on each button.