Accessibility work at Form Factory is a first-class engineering concern, not a post-launch polish pass. The theme is tracked explicitly in the backlog — see the Accessibility (A11Y) theme in user-story-themes. This page collects worked examples of common accessibility defects and the patterns that fix them.
Icon-Only Table Cells
A common failure mode: a comparison table (pricing, loyalty tiers, feature matrix) uses a dot or check icon to mean “included” and an empty cell to mean “not included”. Sighted users read it fine. Screen-reader users hear nothing — the icon has no accessible name, and the empty cell announces as empty.
The fix is symmetric. Both the icon and the empty cell need a text equivalent.
The Pattern
<td>
{available ? (
<div className="flex items-center justify-center">
<Dot aria-label="Available" className="w-8 h-8" />
</div>
) : (
<span className="sr-only">Not available</span>
)}
</td>Two halves:
- Present state — the icon element receives
aria-labelso assistive technology announces its meaning. Alternatively, wrap the icon in ansr-onlyspan carrying the label. - Absent state — a visually hidden
sr-onlyspan stands in for the empty cell so it reads as “Not available” rather than silence.
If the project is localized, route the strings through the i18n layer
(aria-label={t('available')}) and add keys to every locale file. If it is
single-language, literal strings work — the mechanism is the aria-label and
sr-only span, not the translation.
Table Structure Is Part Of The Fix
Per-cell labels only land if the table itself is structured:
- A
<caption>describing the table (may besr-onlyif a visible title already exists). <th scope="col">for column headers and<th scope="row">for row headers so each cell inherits context.- Text labels for columns that visually use a logo — pair the logo with a
visible or
sr-onlytext label.
With scopes in place, a cell announcement becomes “Free shipping, Gold, Available” instead of a disconnected “Available”.
Verification
Automated tools (axe, Lighthouse) will flag some of this, but they miss icon-as-SVG cases because the icon renders as a generic graphic. Verify manually with a real screen reader.
On macOS + Safari + VoiceOver:
Cmd+F5to enable VoiceOver.Ctrl+Option+Uto open the rotor, arrow to “Tables”, select the table.- Navigate cells with
Ctrl+Option+←/→.
Each status cell should announce the row header, the column header, and the available/not-available state.
When To Invert The Pattern
If the icon is purely decorative (it duplicates adjacent text), do the opposite
— mark it aria-hidden="true" to prevent a duplicate announcement. The test
question: “If the icon were removed, would the meaning still be conveyed?” If
yes, hide it; if no, give it a name.
Related
- user-story-themes — Accessibility as a first-class backlog theme
- pull-requests — PR requirements and QA testing steps, which should include accessibility verification for frontend changes