fix(a11y): make Settings and the Downloads tabs keyboard-reachable

a11y.1 is the audit's last Critical and reproduced exactly: seven
config-section headers, seven bare `<div @click>`s with no tabindex,
no role and no aria-expanded, and every section collapsed by default —
so every setting in the app was behind a control that could not be
tabbed to. a11y.2 is the same bug in Downloads' two `<div class=tab>`s.

Both now follow patterns the app already had: a real
`<button aria-expanded aria-controls>` (explore-artist-details has five),
and a role=tablist/tab/tabpanel with a roving tab stop and
Left/Right/Home/End. The section body renders unconditionally and is
toggled with `hidden`, because aria-controls has to name an element
that exists and the slot's light-DOM children exist either way.

H-22's reorder ships with them: Libraries is first and the only
expanded section, Search Index — configured once, if ever — is second
to last. The Playback/Audio section H-22 also asks for is deliberately
not here: there is no output-device, gapless, crossfade or replay-gain
setting in backend/config to expose, and a section of controls that do
nothing is worse than admitting it does not exist.

Settings also stops advertising `tracklist.delete`, which was bound to
Delete and configurable in the UI while nothing listened for the event
it dispatched.
This commit is contained in:
2026-08-12 12:12:33 -04:00
parent a150b24e71
commit 24887d6840
6 changed files with 240 additions and 42 deletions
@@ -163,12 +163,6 @@ export class ConfigPage extends ViewLifecycleMixin(LitElement) {
scope: 'panel:track-list',
defaultKey: 'Enter',
},
'tracklist.delete': {
label: 'Remove Selected',
category: 'Navigation',
scope: 'panel:track-list',
defaultKey: 'Delete',
},
'autotag.apply': {
label: 'Apply Match',
category: 'Autotag',
@@ -1500,14 +1494,20 @@ export class ConfigPage extends ViewLifecycleMixin(LitElement) {
return html`
<h2>Settings</h2>
${this.renderSearchSection()}
<!--
Ordered by how often a setting is *reached*, not by how
the sections were written (H-22). Libraries was last and
below the fold while Search Index — which is configured
once, if ever — was first and the only expanded one.
-->
${this.renderLibrarySection()}
${this.renderNowPlayingSection()}
${this.renderThemeSection()}
${this.renderFavoritesSection()}
${this.renderTrackListSection()}
${this.renderFavoritesSection()}
${this.renderShortcutsSection()}
${this.renderSearchSection()}
<download-clients></download-clients>
${this.renderLibrarySection()}
`;
}
@@ -1520,7 +1520,6 @@ export class ConfigPage extends ViewLifecycleMixin(LitElement) {
<config-section
heading="Search Index"
description="The explore search index is built from the MusicBrainz/ListenBrainz data dumps — popular artists, albums, and tracks with listen counts — for fast offline search."
.open=${true}
>
<div class="index-status">
${s
@@ -2035,6 +2034,7 @@ export class ConfigPage extends ViewLifecycleMixin(LitElement) {
heading="Libraries"
description="Manage your music library folders. Scanning and its
progress live in the Jobs panel."
.open=${true}
>
<div class="scan-actions">
<button