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

View file

@ -19,7 +19,7 @@
class="tag-chip status-badge status-info" class="tag-chip status-badge status-info"
> >
{{ item }} {{ 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> </span>
</div> </div>