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:

  1. Present state — the icon element receives aria-label so assistive technology announces its meaning. Alternatively, wrap the icon in an sr-only span carrying the label.
  2. Absent state — a visually hidden sr-only span 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 be sr-only if 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-only text 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:

  1. Cmd+F5 to enable VoiceOver.
  2. Ctrl+Option+U to open the rotor, arrow to “Tables”, select the table.
  3. 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.

  • 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