Description
Frontend a11y is below WCAG 2.1 AA in a few areas. HR users often include users with accessibility needs; fixing these is both table-stakes and a moderate legal risk for an HR compliance product.
Current State
Missing <label htmlFor> or aria-label on inputs in most components. htmlFor appears in only 4 files. Placeholders are not WCAG labels (they disappear on focus):
src/components/employees/EmployeePanel.tsx:36-49 — search input, no label
src/components/CommandPalette.tsx:440-458 — combobox role without aria-label
- (Many more — do a full sweep)
Streaming aria-live too broad — src/components/chat/MessageList.tsx:165-170 uses role="log" aria-live="polite" on the outer scroll container. With 100ms chunk flushing, VoiceOver re-announces the entire conversation on every chunk.
Focus management — Modal.tsx is well done. Other popovers / dropdowns should be spot-checked.
Suggested Fix
Verification
Automation Hints
scope: src/components/
do-not-touch: src-tauri/
approach: add-declarations
risk: low
max-files-changed: 20
blocked-by: none
bail-if: none
Priority
Medium — polish; would be high if you intend to sell to government / education.
Description
Frontend a11y is below WCAG 2.1 AA in a few areas. HR users often include users with accessibility needs; fixing these is both table-stakes and a moderate legal risk for an HR compliance product.
Current State
Missing
<label htmlFor>oraria-labelon inputs in most components.htmlForappears in only 4 files. Placeholders are not WCAG labels (they disappear on focus):src/components/employees/EmployeePanel.tsx:36-49— search input, no labelsrc/components/CommandPalette.tsx:440-458— combobox role withoutaria-labelStreaming
aria-livetoo broad —src/components/chat/MessageList.tsx:165-170usesrole="log" aria-live="polite"on the outer scroll container. With 100ms chunk flushing, VoiceOver re-announces the entire conversation on every chunk.Focus management — Modal.tsx is well done. Other popovers / dropdowns should be spot-checked.
Suggested Fix
<input>,<select>,<textarea>acrosssrc/components/; add visible<label htmlFor>where feasible,aria-labelotherwise.aria-live="polite" aria-atomic="false"on the currently-streaming assistant bubble only; set outer container toaria-live="off".aria-live="off"and announce only the final complete response once via an ephemeral live region ondone.Verification
npm run type-checkpassesAutomation Hints
scope: src/components/
do-not-touch: src-tauri/
approach: add-declarations
risk: low
max-files-changed: 20
blocked-by: none
bail-if: none
Priority
Medium — polish; would be high if you intend to sell to government / education.