From e227f8e4f3879d3e5aadee51612a6b9041d59fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 8 Oct 2026 19:17:50 +0200 Subject: [PATCH] Give off switches and disabled primary buttons light-mode contrast In the light theme an off switch's track was the input grey at ~1.4:1 on the card, and a disabled primary button was white on a half-opacity violet at ~2:1. An off track now has its own token at ~3.5:1, and a disabled primary button a pale violet fill with a dark violet label at ~5.5:1. The dark theme keeps its look through the same tokens. --- src/app/globals.css | 25 +++++++++++++++++++++++++ src/components/ui/button.tsx | 5 ++++- src/components/ui/switch.tsx | 2 +- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index 81e798468..90bd16bd4 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -106,6 +106,9 @@ --color-chart-1: var(--chart-1); --color-ring: var(--ring); --color-input: var(--input); + --color-switch-off: var(--switch-off); + --color-primary-disabled: var(--primary-disabled); + --color-primary-disabled-foreground: var(--primary-disabled-foreground); --color-input-focus: var(--input-focus); --color-border: var(--border); --color-destructive: var(--destructive); @@ -292,6 +295,19 @@ --destructive: #ff5555; --border: #44475a; --input: #44475a; + /* An off switch's track, and a disabled primary button. Dark keeps the + * look these had (input at 80 %, primary at half strength). */ + --switch-off: color-mix(in oklab, var(--input) 80%, transparent); + --primary-disabled: color-mix( + in oklab, + var(--primary) 50%, + var(--background) + ); + --primary-disabled-foreground: color-mix( + in oklab, + var(--primary-foreground) 70%, + var(--primary-disabled) + ); /* Text-field focus border: a neutral, foreground-based tone, no colour * ring. ~5.2:1 on --background, ~6:1 on --card. */ --input-focus: #9a9b9d; @@ -423,6 +439,15 @@ --success-foreground: #ffffff; --border: oklch(0.885 0.008 300); --input: oklch(0.87 0.009 300); + /* An off switch's track: the input grey sat at ~1.4:1 on the card, so an + * off switch all but vanished. ~3.5:1 on --card clears the 3:1 floor for + * a control boundary. * + --switch-off: oklch(0.62 0.012 295); + /* A disabled primary button: opacity-50 left white text on a washed + * violet at ~2:1. A pale violet fill with a dark violet label (~5.5:1) + * still reads as the primary action, and as unavailable. */ + --primary-disabled: oklch(0.88 0.035 290); + --primary-disabled-foreground: oklch(0.44 0.06 290); /* Text-field focus border, neutral: ~4.2:1 on --background. */ --input-focus: oklch(0.56 0.012 295); --ring: oklch(0.48 0.012 290); /* neutral: no purple focus frames anywhere */ diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index 07a1b5fb3..16fa2f862 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -34,7 +34,10 @@ const buttonVariants = cva( { variants: { variant: { - default: "bg-primary text-primary-foreground hover:bg-primary/90", + // Disabled keeps its label readable in both themes: a token pair + // rather than half opacity (white on a washed violet, ~2:1 light). + default: + "bg-primary text-primary-foreground hover:bg-primary/90 disabled:bg-primary-disabled disabled:text-primary-disabled-foreground disabled:opacity-100", destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx index daff86a60..cef45ddef 100644 --- a/src/components/ui/switch.tsx +++ b/src/components/ui/switch.tsx @@ -25,7 +25,7 @@ function Switch({ // without disturbing layout. v1.4.43 W10 design-M1 — `-13px` (not // `-12px`) takes the 18.4 px track + 26 px padding to 44.4 px and // closes the 1.6 px vertical short fall to spec-strict 2.5.5. - "peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none before:absolute before:inset-[-13px] before:content-[''] focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6", + "peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-switch-off focus-visible:border-ring focus-visible:ring-ring/50 group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none before:absolute before:inset-[-13px] before:content-[''] focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6", className, )} {...props}