fix(a11y): accessible names for switches, swatches, selects and inputs

Lighthouse a11y across the authenticated module pages flagged: the clickable
Siftlode logo had aria-label='Feed' (accessible name didn't include its visible
text); the shared Switch, theme swatches, sort selects and several number/range
inputs had no accessible name/label. Give Switch role='switch'+aria-checked and
an optional label (passed at every call site), aria-label the swatches, selects
(feed/playlists sort) and the settings/config/scheduler inputs, and drop the
mismatched logo aria-label so its visible text is the name. All 11 module pages
now score 100 accessibility (settings 86->100, playlists 89->100, others 92-95->100).
This commit is contained in:
npeter83 2026-07-04 19:13:11 +02:00
parent 2ff517fb74
commit 92eed1ec9b
8 changed files with 40 additions and 9 deletions

View file

@ -138,6 +138,8 @@ function Appearance({ prefs }: { prefs: PrefsController }) {
<Tooltip key={s.id} hint={s.name}>
<button
onClick={() => setTheme({ ...theme, scheme: s.id as Scheme })}
aria-label={s.name}
aria-pressed={theme.scheme === s.id}
className={`h-9 w-full rounded-lg border-2 transition ${
theme.scheme === s.id ? "border-fg" : "border-transparent"
}`}
@ -151,24 +153,29 @@ function Appearance({ prefs }: { prefs: PrefsController }) {
<Section title={t("settings.appearance.display")}>
<SettingRow label={t("settings.appearance.darkMode")}>
<Switch
label={t("settings.appearance.darkMode")}
checked={theme.mode === "dark"}
onChange={(v) => setTheme({ ...theme, mode: v ? "dark" : "light" })}
/>
</SettingRow>
<SettingRow label={t("settings.appearance.listView")} hint={t("settings.appearance.listViewHint")}>
<Switch checked={view === "list"} onChange={(v) => setView(v ? "list" : "grid")} />
<Switch
label={t("settings.appearance.listView")}
checked={view === "list"}
onChange={(v) => setView(v ? "list" : "grid")}
/>
</SettingRow>
<SettingRow
label={t("settings.appearance.performanceMode")}
hint={t("settings.appearance.performanceModeHint")}
>
<Switch checked={perf} onChange={setPerf} />
<Switch label={t("settings.appearance.performanceMode")} checked={perf} onChange={setPerf} />
</SettingRow>
<SettingRow
label={t("settings.appearance.showHints")}
hint={t("settings.appearance.showHintsHint")}
>
<Switch checked={hints} onChange={setHints} />
<Switch label={t("settings.appearance.showHints")} checked={hints} onChange={setHints} />
</SettingRow>
</Section>
@ -180,6 +187,7 @@ function Appearance({ prefs }: { prefs: PrefsController }) {
step={0.02}
value={theme.fontScale}
onChange={(e) => setTheme({ ...theme, fontScale: Number(e.target.value) })}
aria-label={t("settings.appearance.textSize")}
className="w-full accent-accent"
/>
<div className="text-right text-xs text-muted">{Math.round(theme.fontScale * 100)}%</div>
@ -202,13 +210,21 @@ function Notifications({ prefs }: { prefs: PrefsController }) {
label={t("settings.notifications.sound")}
hint={t("settings.notifications.soundHint")}
>
<Switch checked={cfg.sound} onChange={(v) => update({ sound: v })} />
<Switch
label={t("settings.notifications.sound")}
checked={cfg.sound}
onChange={(v) => update({ sound: v })}
/>
</SettingRow>
<SettingRow
label={t("settings.notifications.autoDismiss")}
hint={t("settings.notifications.autoDismissHint")}
>
<Switch checked={autoOn} onChange={(v) => update({ durationMs: v ? 6000 : 0 })} />
<Switch
label={t("settings.notifications.autoDismiss")}
checked={autoOn}
onChange={(v) => update({ durationMs: v ? 6000 : 0 })}
/>
</SettingRow>
{autoOn && (
<div className="py-1.5 text-sm">
@ -223,6 +239,7 @@ function Notifications({ prefs }: { prefs: PrefsController }) {
step={1000}
value={cfg.durationMs}
onChange={(e) => update({ durationMs: Number(e.target.value) })}
aria-label={t("settings.notifications.dismissAfter")}
className="w-full accent-accent mt-1"
/>
</div>