fix(a11y): add aria-pressed and aria-label to Browse panel buttons (WCAG 2.1)

Screen readers had no way to determine which domain, category, subcategory,
or sort button was selected — the active CSS class is invisible to assistive
technology.

  - aria-pressed on all toggle buttons (domain, category, subcategory, sort)
  - aria-label="Previous page" / "Next page" on pagination buttons
  - aria-live="polite" on results count span — announces filter result changes
  - Equipment chip-remove: "Remove" → "Remove equipment: {item}"

Addresses WCAG 2.1 AA criteria 4.1.2 (Name, Role, Value) and 1.3.1
(Info and Relationships). Part of kiwi UX audit (2026-05-11).
This commit is contained in:
pyr0ball 2026-05-11 11:33:10 -07:00
parent 8c765b7da2
commit 0ef57618bf
2 changed files with 18 additions and 3 deletions

View file

@ -6,6 +6,7 @@
v-for="domain in domains"
:key="domain.id"
:class="['btn', activeDomain === domain.id ? 'btn-primary' : 'btn-secondary']"
:aria-pressed="activeDomain === domain.id"
@click="selectDomain(domain.id)"
>
{{ domain.label }}
@ -24,6 +25,7 @@
<div v-else class="category-list mb-sm flex flex-wrap gap-xs">
<button
:class="['btn', 'btn-secondary', 'cat-btn', { active: activeCategory === '_all' }]"
:aria-pressed="activeCategory === '_all'"
@click="selectCategory('_all')"
>
All
@ -32,6 +34,7 @@
v-for="cat in categories"
:key="cat.category"
:class="['btn', 'btn-secondary', 'cat-btn', { active: activeCategory === cat.category }]"
:aria-pressed="activeCategory === cat.category"
@click="selectCategory(cat.category)"
>
{{ cat.category }}
@ -57,6 +60,7 @@
<template v-else>
<button
:class="['btn', 'btn-secondary', 'subcat-btn', { active: activeSubcategory === null }]"
:aria-pressed="activeSubcategory === null"
@click="selectSubcategory(null)"
>
All {{ activeCategory }}
@ -65,6 +69,7 @@
v-for="sub in subcategories"
:key="sub.subcategory"
:class="['btn', 'btn-secondary', 'subcat-btn', { active: activeSubcategory === sub.subcategory }]"
:aria-pressed="activeSubcategory === sub.subcategory"
@click="selectSubcategory(sub.subcategory)"
>
{{ sub.subcategory }}
@ -105,21 +110,25 @@
<div class="sort-btns flex gap-xs">
<button
:class="['btn', 'btn-secondary', 'sort-btn', { active: sortOrder === 'default' }]"
:aria-pressed="sortOrder === 'default'"
@click="setSort('default')"
title="Corpus order"
>Default</button>
<button
:class="['btn', 'btn-secondary', 'sort-btn', { active: sortOrder === 'alpha' }]"
:aria-pressed="sortOrder === 'alpha'"
@click="setSort('alpha')"
title="Alphabetical A→Z"
>AZ</button>
<button
:class="['btn', 'btn-secondary', 'sort-btn', { active: sortOrder === 'alpha_desc' }]"
:aria-pressed="sortOrder === 'alpha_desc'"
@click="setSort('alpha_desc')"
title="Alphabetical Z→A"
>ZA</button>
<button
:class="['btn', 'btn-secondary', 'sort-btn', { active: sortOrder === 'match' }]"
:aria-pressed="sortOrder === 'match'"
:disabled="pantryCount === 0"
@click="setSort('match')"
:title="pantryCount > 0 ? 'Sort by pantry match %' : 'Add items to pantry to sort by match'"
@ -128,7 +137,11 @@
</div>
<div class="results-header flex-between mb-sm">
<span class="text-sm text-secondary">
<span
class="text-sm text-secondary"
aria-live="polite"
aria-atomic="true"
>
{{ total }} recipes
<span v-if="pantryCount > 0"> pantry match shown</span>
<span v-if="requiredIngredient.trim()"> must include "{{ requiredIngredient.trim() }}"</span>
@ -137,12 +150,14 @@
<button
class="btn btn-secondary btn-xs"
:disabled="page <= 1"
aria-label="Previous page"
@click="changePage(page - 1)"
> Prev</button>
<span class="text-sm text-secondary page-indicator">{{ page }} / {{ totalPages }}</span>
<span class="text-sm text-secondary page-indicator" aria-live="polite">{{ page }} / {{ totalPages }}</span>
<button
class="btn btn-secondary btn-xs"
:disabled="page >= totalPages"
aria-label="Next page"
@click="changePage(page + 1)"
>Next </button>
</div>

View file

@ -19,7 +19,7 @@
class="tag-chip status-badge status-info"
>
{{ item }}
<button class="chip-remove" @click="removeEquipment(item)" aria-label="Remove">×</button>
<button class="chip-remove" @click="removeEquipment(item)" :aria-label="'Remove equipment: ' + item">×</button>
</span>
</div>