a11y: RecipesView audit — 6 critical, 8 high, 9 medium (WCAG 2.1 AA + ND-informed) #80

Closed
opened 2026-04-14 08:36:42 -07:00 by pyr0ball · 0 comments
Owner

Context

Full accessibility audit of RecipesView.vue conducted 2026-04-14 covering WCAG 2.1 AA compliance and ND-informed design (ADHD, autism, executive function, depleted states).

Two layers audited:

  • WCAG compliance (technical): contrast, keyboard nav, screen reader semantics, focus management
  • ND-informed design: cognitive load, decision fatigue, sensory overwhelm, calm-only policy violations

CRITICAL — ADA/WCAG Hard Failures

C1: Level selector buttons missing aria-pressed

Lines: 53–66 (also dietary presets ~99–105, cuisine style ~225–230)
Plain <button> elements with CSS .active class only. Screen readers cannot communicate which level is selected.
Fix: :aria-pressed="recipesStore.level === lvl.value" on each level button; same pattern on dietary preset and cuisine style buttons.

C2: Filter chips missing aria-pressed + unclear clear label

Lines: 319–338
Result filter chips use CSS .active with no ARIA state. ✕ Clear may be announced as "multiplication sign".
Fix: :aria-pressed on each chip; aria-label="Clear all recipe filters" on clear chip.

C3: <details>/<summary> need aria-expanded fallback

Lines: 88–177, 180–246, 459–476
Native <details> marker suppressed via CSS. Older JAWS+Chrome may lose expanded/collapsed announcement.
Fix: Track open state with ref bound to @toggle; bind :aria-expanded on <summary>.

C4: Bookmark/dismiss buttons below minimum touch target

Lines: 368–379
12–14px font, 2px padding — under 24×24px minimum (WCAG 2.5.8).
Fix: Apply .btn-icon class (32×32px in theme.css) or increase padding.

C5: Loading state has no screen reader announcement

Lines: 255–258, 514–516
Visual spinner only. Screen reader users cannot tell if button worked.
Fix: aria-busy="true" on results container; update aria-live to announce "Finding recipes…"

C6: Error div missing role="alert"

Lines: 275–277
.status-error has no role="alert". API failures are invisible to screen readers.
Fix: Add role="alert" to the error div.


HIGH — Significant Barriers

H1/H2: Contrast failures

  • --color-text-muted ~3.1:1 dark, ~2.9:1 light — fails WCAG 1.4.3 at 11px usage sites
  • --color-success-light: #6aac60 ~4.3:1 at 11px in dark mode (threshold is 4.5:1)
    Fix: Darken light muted to #7a5c2e; bump dark opacity to 0.52; lighten success to #7fc073.

H3: Tab activation doesn't move focus to panel

Lines: 583–593
APG pattern requires focus to move to tabpanel. tabindex="0" missing on panels.
Fix: tabindex="0" on panels; nextTick(() => panelRef.value?.focus()) on tab select.

H4: openRecipeById silent failure

Lines: 603–609
Catch block discards error. User taps a recipe and nothing happens.
Fix: Set error state + aria-live announcement: "Recipe couldn't load. Please try again."

H5: Nutrition chips — emoji are the only label

Lines: 400–427
🔥 🧈 💪 etc. announced verbosely and inconsistently across screen readers.
Fix: aria-hidden="true" on emoji; <span class="sr-only">Calories:</span> alongside each value.

H6/H7/H8: Five form inputs with unlinked <label> elements

Lines: 164–175 (Max Missing), 191–215 (4× nutrition filters), 237–243 (Category)
Sibling <label> elements with no for/id linking.
Fix: Add matching for/id pairs to all five.


MEDIUM — ND / Calm-UX Policy

  • M1: Hard Day Mode — no signal that re-search needed after toggle
  • M2: Level description appears after selection — forces try/undo loop
  • M3: Rate limit "You've used your N free suggestions" — depletion framing violates calm-only policy. Fix: "Today's free suggestions are used up. Resets tomorrow."
  • M4: Element gap card reports deficit, no next action
  • M5: "No recipes found — try adding more items" is slightly blaming
  • M6: Lv1/Lv2 filter chips require working memory — use level names
  • M7: Wildcard "that's part of the fun" mismatch for autistic users who find unexpected outputs distressing
  • M8: Swaps collapsible above directions — interrupts reading flow
  • M9: openRecipeById silent fail (ND dimension of H4)

LOW

  • L1: Neon mode — add explicit prefers-reduced-motion block (belt-and-suspenders)
  • L2: autocomplete="off" on recipe text inputs
  • L3: aria-label="Clear all filters" on ✕ chip
  • L4: "You'd need:" uses text-warning amber for informational content
  • L5: "Affiliate links" needs plain-language parenthetical per CF consent policy

What Is Already Correct

  • Tab bar ARIA (tablist, tab, aria-selected, roving tabindex, arrow keys) — well above average
  • Allergen and dietary preset buttons correctly use aria-pressed
  • Global prefers-reduced-motion guard in theme.css blanket-applied correctly
  • aria-live results region: polite, aria-atomic, .sr-only, clean count format
  • aria-describedby on wildcard checkbox and constraint/allergy inputs
  • lang="en" on <html>
  • Bookmark/dismiss aria-label includes recipe title (correct disambiguation)
  • Hard Day Mode aria-pressed correctly implemented

Scope

  • C1 aria-pressed on level, dietary preset, cuisine style buttons
  • C2 aria-pressed on filter chips; fix clear label
  • C3 aria-expanded on collapsible <summary> elements
  • C4 Increase bookmark/dismiss touch target to ≥24×24px
  • C5 aria-busy + loading aria-live announcement
  • C6 role="alert" on error div
  • H1/H2 theme.css contrast fixes
  • H3 Focus management on tab activation
  • H4 Error state in openRecipeById
  • H5 aria-hidden emoji + .sr-only nutrition labels
  • H6/H7/H8 for/id pairs on five form inputs
  • M1–M9 ND friction and calm-UX policy fixes
  • L1–L5 Low-priority improvements

Related: cf-a11y audit 2026-04-14 · circuitforge-plans/kiwi/superpowers/specs/2026-04-14-build-your-own-recipe-tab-design.md

## Context Full accessibility audit of `RecipesView.vue` conducted 2026-04-14 covering WCAG 2.1 AA compliance and ND-informed design (ADHD, autism, executive function, depleted states). Two layers audited: - **WCAG compliance** (technical): contrast, keyboard nav, screen reader semantics, focus management - **ND-informed design**: cognitive load, decision fatigue, sensory overwhelm, calm-only policy violations --- ## CRITICAL — ADA/WCAG Hard Failures ### C1: Level selector buttons missing `aria-pressed` **Lines:** 53–66 (also dietary presets ~99–105, cuisine style ~225–230) Plain `<button>` elements with CSS `.active` class only. Screen readers cannot communicate which level is selected. **Fix:** `:aria-pressed="recipesStore.level === lvl.value"` on each level button; same pattern on dietary preset and cuisine style buttons. ### C2: Filter chips missing `aria-pressed` + unclear clear label **Lines:** 319–338 Result filter chips use CSS `.active` with no ARIA state. `✕ Clear` may be announced as "multiplication sign". **Fix:** `:aria-pressed` on each chip; `aria-label="Clear all recipe filters"` on clear chip. ### C3: `<details>`/`<summary>` need `aria-expanded` fallback **Lines:** 88–177, 180–246, 459–476 Native `<details>` marker suppressed via CSS. Older JAWS+Chrome may lose expanded/collapsed announcement. **Fix:** Track open state with ref bound to `@toggle`; bind `:aria-expanded` on `<summary>`. ### C4: Bookmark/dismiss buttons below minimum touch target **Lines:** 368–379 12–14px font, 2px padding — under 24×24px minimum (WCAG 2.5.8). **Fix:** Apply `.btn-icon` class (32×32px in theme.css) or increase padding. ### C5: Loading state has no screen reader announcement **Lines:** 255–258, 514–516 Visual spinner only. Screen reader users cannot tell if button worked. **Fix:** `aria-busy="true"` on results container; update `aria-live` to announce "Finding recipes…" ### C6: Error div missing `role="alert"` **Lines:** 275–277 `.status-error` has no `role="alert"`. API failures are invisible to screen readers. **Fix:** Add `role="alert"` to the error div. --- ## HIGH — Significant Barriers ### H1/H2: Contrast failures - `--color-text-muted` ~3.1:1 dark, ~2.9:1 light — fails WCAG 1.4.3 at 11px usage sites - `--color-success-light: #6aac60` ~4.3:1 at 11px in dark mode (threshold is 4.5:1) **Fix:** Darken light muted to `#7a5c2e`; bump dark opacity to 0.52; lighten success to `#7fc073`. ### H3: Tab activation doesn't move focus to panel **Lines:** 583–593 APG pattern requires focus to move to tabpanel. `tabindex="0"` missing on panels. **Fix:** `tabindex="0"` on panels; `nextTick(() => panelRef.value?.focus())` on tab select. ### H4: `openRecipeById` silent failure **Lines:** 603–609 Catch block discards error. User taps a recipe and nothing happens. **Fix:** Set error state + `aria-live` announcement: "Recipe couldn't load. Please try again." ### H5: Nutrition chips — emoji are the only label **Lines:** 400–427 🔥 🧈 💪 etc. announced verbosely and inconsistently across screen readers. **Fix:** `aria-hidden="true"` on emoji; `<span class="sr-only">Calories:</span>` alongside each value. ### H6/H7/H8: Five form inputs with unlinked `<label>` elements **Lines:** 164–175 (Max Missing), 191–215 (4× nutrition filters), 237–243 (Category) Sibling `<label>` elements with no `for`/`id` linking. **Fix:** Add matching `for`/`id` pairs to all five. --- ## MEDIUM — ND / Calm-UX Policy - **M1:** Hard Day Mode — no signal that re-search needed after toggle - **M2:** Level description appears after selection — forces try/undo loop - **M3:** Rate limit "You've used your N free suggestions" — depletion framing violates calm-only policy. Fix: "Today's free suggestions are used up. Resets tomorrow." - **M4:** Element gap card reports deficit, no next action - **M5:** "No recipes found — try adding more items" is slightly blaming - **M6:** `Lv1`/`Lv2` filter chips require working memory — use level names - **M7:** Wildcard "that's part of the fun" mismatch for autistic users who find unexpected outputs distressing - **M8:** Swaps collapsible above directions — interrupts reading flow - **M9:** `openRecipeById` silent fail (ND dimension of H4) --- ## LOW - **L1:** Neon mode — add explicit `prefers-reduced-motion` block (belt-and-suspenders) - **L2:** `autocomplete="off"` on recipe text inputs - **L3:** `aria-label="Clear all filters"` on ✕ chip - **L4:** "You'd need:" uses `text-warning` amber for informational content - **L5:** "Affiliate links" needs plain-language parenthetical per CF consent policy --- ## What Is Already Correct - Tab bar ARIA (tablist, tab, aria-selected, roving tabindex, arrow keys) — well above average - Allergen and dietary preset buttons correctly use `aria-pressed` - Global `prefers-reduced-motion` guard in theme.css blanket-applied correctly - `aria-live` results region: polite, aria-atomic, .sr-only, clean count format - `aria-describedby` on wildcard checkbox and constraint/allergy inputs - `lang="en"` on `<html>` - Bookmark/dismiss `aria-label` includes recipe title (correct disambiguation) - Hard Day Mode `aria-pressed` correctly implemented --- ## Scope - [ ] **C1** `aria-pressed` on level, dietary preset, cuisine style buttons - [ ] **C2** `aria-pressed` on filter chips; fix clear label - [ ] **C3** `aria-expanded` on collapsible `<summary>` elements - [ ] **C4** Increase bookmark/dismiss touch target to ≥24×24px - [ ] **C5** `aria-busy` + loading `aria-live` announcement - [ ] **C6** `role="alert"` on error div - [ ] **H1/H2** theme.css contrast fixes - [ ] **H3** Focus management on tab activation - [ ] **H4** Error state in `openRecipeById` - [ ] **H5** `aria-hidden` emoji + `.sr-only` nutrition labels - [ ] **H6/H7/H8** `for`/`id` pairs on five form inputs - [ ] **M1–M9** ND friction and calm-UX policy fixes - [ ] **L1–L5** Low-priority improvements **Related:** cf-a11y audit 2026-04-14 · `circuitforge-plans/kiwi/superpowers/specs/2026-04-14-build-your-own-recipe-tab-design.md`
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Circuit-Forge/kiwi#80
No description provided.