FSD Frontend System Design

Part IV β€” The Reach Β· Lesson 8.3

Screen Reader

In one lineSemantics decide what gets announced β€” ARIA patches the gaps, and wrong ARIA is worse than none.

Where you've seen itA row of icon-only buttons that all announce as "button, button, button".

  • Time17 min
  • DiagramDOM β†’ accessibility tree, and what each element contributes
  • Chapter8 Β· Accessibility

Diagram

diagram
DOM β†’ ACCESSIBILITY TREE β€” what the screen reader actually reads

  DOM                              ACCESSIBILITY TREE
  ─────────────────────────────    ────────────────────────────────
  <button>                         name:  "Delete invoice 42"
    <svg aria-hidden="true">   ──► role:  button
    <span class="sr-only">           state: enabled
      Delete invoice 42
    </span>
  </button>

  <div onclick>πŸ—‘</div>        ──► (nothing β€” not in the tree)

  Every element contributes: NAME Β· ROLE Β· STATE Β· VALUE
diagram
WHERE THE NAME COMES FROM β€” first match wins

  1. aria-labelledby="id"      points at visible text  ← preferred
  2. aria-label="Delete"       invisible string
  3. <label for="…">           form controls
  4. text content              <button>Save</button>   ← simplest
  5. title attribute           unreliable, don't rely on it
diagram
THE FIVE ARIA RULES THAT MATTER

  1. Don't use ARIA if HTML can do it       <button> > role="button"
  2. Don't change native semantics          βœ— <h2 role="button">
  3. All interactive ARIA must be keyboard  role="button" needs keys (8.2)
     operable
  4. Don't hide focusable elements          βœ— aria-hidden on a tab stop
  5. Every control needs an accessible name
diagram
LIVE REGIONS β€” announcing things that change

  aria-live="polite"     waits for a pause   search results, saved
  aria-live="assertive"  interrupts NOW      errors, session expiry
  role="status"          = polite            "3 items added"
  role="alert"           = assertive         "Payment failed"

  The region must exist in the DOM BEFORE the text changes,
  or nothing is announced.

How it works

  1. Start semantic. Headings in order, landmarks (main, nav, aside), lists as lists, and real form controls.
  2. Name every control. Icon-only buttons need aria-label or visually hidden text; aria-labelledby is better when visible text already exists.
  3. Hide decoration with aria-hidden="true" on icons β€” but never on anything focusable.
  4. Announce dynamic changes through a live region that's already mounted; toggling the region itself into existence announces nothing.
  5. Test with a real screen reader: VoiceOver (Cmd+F5) on macOS, NVDA on Windows. Fifteen minutes teaches more than any checklist.
html
<button aria-label="Delete invoice 42">
  <svg aria-hidden="true">…</svg>
</button>

<div role="status" aria-live="polite" class="sr-only">
  <!-- mounted always; text swapped in when results change -->
</div>

Trade-offs

TechniqueWhen
Visible textAlways best β€” helps everyone
aria-labelledbyVisible text exists elsewhere
aria-labelIcon-only controls
sr-only textExtra context for a visual pattern
role="alert"Genuine errors only β€” it interrupts

Interview angle

"How do you make a custom dropdown screen-reader friendly?"

  • Use <select> if the design allows; the platform version is correct everywhere for free.
  • If not: role="combobox" with aria-expanded, aria-controls, aria-activedescendant, plus the full arrow/Enter/Escape key contract.
  • Announce results with a polite live region, and verify in VoiceOver and NVDA β€” the specs agree, the implementations don't always.

Recap

  • Name, role, state, value β€” every control needs all four.
  • Semantic HTML gives them free; ARIA only patches genuine gaps.
  • Live regions must exist before their content changes.