fix(a11y): switch names, contrast floors, and chart-toggle pressed state

- Admin SettingsToggle: name the Radix switch via aria-label so it no longer
  announces as a bare 'switch, on/off' (one shared component, six call sites).
- Replace the phantom text-destructive-foreground / text-warning-foreground
  utilities with the vetted destructive pattern (bg-destructive text-white
  dark:bg-destructive/60) and a readable foreground on the warning wash — no
  more dead classes riding on cascade order.
- Drop the sub-AA opacity tiers: lab sparkline + titration marker off
  muted/70, state icons (query-error, chart empty/error, med sort) off
  muted/60 to the full token so they clear the light-theme floor.
- Chart range toggles expose aria-pressed (lab, custom-metric, health, mood,
  compliance) so the active window is conveyed to assistive tech.
This commit is contained in:
Marc-André Bombeck committed 2026-07-08 23:51:22 +02:00
1 parent 9f4c7e48fe
commit 930fa81124
16 files changed
+21 -11

No files matched your search

+5
View File
@@ -192,6 +192,11 @@ export function SettingsToggle({
</div>
</div>
<Switch
// The visible label is a sibling `<p>` with no association to the
// Radix switch button, so a screen reader would announce a bare
// "switch, on/off". Name it explicitly — one fix covers every admin
// toggle that mounts this shared component.
aria-label={label}
checked={checked}
onCheckedChange={onCheckedChange}
disabled={disabled}
+1 -1
View File
@@ -56,7 +56,7 @@ export function ChartEmptyState({
style={height === undefined ? undefined : { height }}
>
<LineChartIcon
className="text-muted-foreground/60 h-8 w-8"
className="text-muted-foreground h-8 w-8"
aria-hidden="true"
/>
<p className="text-sm font-medium">{title}</p>
+1 -1
View File
@@ -73,7 +73,7 @@ export function ChartErrorState({
style={height === undefined ? undefined : { height }}
>
<AlertTriangle
className="text-muted-foreground/60 h-8 w-8"
className="text-muted-foreground h-8 w-8"
aria-hidden="true"
/>
<p className="text-sm font-medium">{title}</p>
@@ -90,6 +90,7 @@ export function ComplianceLineChart({
<Button
key={r.label}
variant={activeRangePoints === r.points ? "default" : "ghost"}
aria-pressed={activeRangePoints === r.points}
size="sm"
className="min-h-11 px-3 text-xs"
onClick={() => setActiveRangePoints(r.points)}
+1
View File
@@ -1666,6 +1666,7 @@ export function HealthChart({
<Button
key={r.labelKey}
variant={rangePoints === r.points ? "default" : "ghost"}
aria-pressed={rangePoints === r.points}
size="sm"
className="min-h-11 px-2 text-xs sm:px-3"
onClick={() => {
+1
View File
@@ -1050,6 +1050,7 @@ export function MoodChart({
<Button
key={r.labelKey}
variant={rangePoints === r.points ? "default" : "ghost"}
aria-pressed={rangePoints === r.points}
size="sm"
className="min-h-11 px-2 text-xs sm:px-3"
onClick={() => setRangePoints(r.points)}
@@ -106,6 +106,7 @@ export function CustomMetricChart({
key={r.key}
size="sm"
variant={range === r.key ? "secondary" : "ghost"}
aria-pressed={range === r.key}
className="h-7 px-2 text-xs"
onClick={() => setRange(r.key)}
>
@@ -115,6 +115,7 @@ export function LabBiomarkerChart({
key={r.key}
size="sm"
variant={range === r.key ? "secondary" : "ghost"}
aria-pressed={range === r.key}
className="h-7 px-2 text-xs"
onClick={() => setRange(r.key)}
>
+1 -1
View File
@@ -54,7 +54,7 @@ export function LabTrendSparkline({
width={width}
height={height}
viewBox={`0 0 ${width} ${height}`}
className="text-muted-foreground/70 overflow-visible"
className="text-muted-foreground overflow-visible"
aria-hidden
role="presentation"
>
@@ -927,7 +927,7 @@ function DeleteRowConfirm({
e.preventDefault();
onConfirm();
}}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
className="bg-destructive dark:bg-destructive/60 text-white hover:bg-destructive/90"
>
{t("medications.detail.intake.deleteRow.confirmAction")}
</AlertDialogAction>
@@ -228,7 +228,7 @@ export function IntakeHistoryEditable({
}}
disabled={bulkBusy}
aria-busy={bulkBusy || undefined}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
className="bg-destructive dark:bg-destructive/60 text-white hover:bg-destructive/90"
>
{t("medications.detail.intake.bulkDelete.confirmAction")}
</AlertDialogAction>
@@ -262,7 +262,7 @@ export function IntakeHistoryEditable({
}}
disabled={rowDeleteBusy}
aria-busy={rowDeleteBusy || undefined}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
className="bg-destructive dark:bg-destructive/60 text-white hover:bg-destructive/90"
>
{t("medications.detail.intake.deleteRow.confirmAction")}
</AlertDialogAction>
@@ -327,7 +327,7 @@ export function MedicationTable({
)
) : (
<ChevronsUpDown
className="text-muted-foreground/60 size-3.5"
className="text-muted-foreground size-3.5"
aria-hidden="true"
/>
)}
@@ -183,7 +183,7 @@ export function TitrationTimeline({
? "border-primary bg-primary/15 text-primary"
: step.isPast
? "border-border bg-muted text-muted-foreground"
: "border-border/60 text-muted-foreground/70 border-dashed",
: "border-border text-muted-foreground border-dashed",
)}
aria-hidden="true"
>
@@ -228,7 +228,7 @@ export function WithingsCard({
<a
href="/api/withings/connect"
data-testid="withings-reconnect-banner"
className="border-warning/30 bg-warning/10 text-warning-foreground hover:bg-warning/20 block rounded-md border px-3 py-2 text-sm transition-colors"
className="border-warning/30 bg-warning/10 text-foreground hover:bg-warning/20 block rounded-md border px-3 py-2 text-sm transition-colors"
>
<span className="font-medium">
{t("settings.integrations.withings.reconnect.banner.title")}
+1 -1
View File
@@ -204,7 +204,7 @@ function ShareLinksCard() {
{t("common.cancel")}
</AlertDialogCancel>
<AlertDialogAction
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
className="bg-destructive dark:bg-destructive/60 text-white hover:bg-destructive/90"
onClick={() => revokeMutation.mutate(link.id)}
>
{t("settings.sharing.revoke")}
+1 -1
View File
@@ -40,7 +40,7 @@ export function QueryErrorCard({
className="flex flex-col items-center justify-center gap-3 px-6 py-10 text-center"
>
<AlertTriangle
className="text-muted-foreground/60 h-8 w-8"
className="text-muted-foreground h-8 w-8"
aria-hidden="true"
/>
<p className="text-sm font-medium">{title ?? t("common.loadFailed")}</p>