From e6aaea563e2cf273becb5df59683917dcf7fb1c0 Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Fri, 3 Jul 2026 14:39:09 +0200 Subject: [PATCH] fix(login): migrate legacy Tailwind v4 opacity utilities and fix checkbox contrast color (#12360) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Which Problems Are Solved 1. **Secondary button hover effect** was rendering a solid white/gray background instead of a subtle transparent overlay. The legacy `bg-opacity-*` utility (e.g. `hover:bg-gray-500 hover:bg-opacity-20`) doesn't compose with `bg-*` in Tailwind v4 — the opacity is ignored, leaving a solid color. The same issue affected `ring-opacity-*` and `border-opacity-*` across other components. 2. **Checkbox checkmark color** was hardcoded to white (`fill='white'` in the SVG), ignoring the theme's primary contrast color. On themes with a light primary color, the white checkmark was invisible. # How the Problems Are Solved 1. Migrated all legacy opacity utilities to the Tailwind v4 slash syntax: - `hover:bg-gray-500 hover:bg-opacity-20` → `hover:bg-gray-500/20` - `ring-primary-light-500 ring-opacity-60` → `ring-primary-light-500/60` - `focus:ring-opacity-50` + `focus:ring-indigo-200` → `focus:ring-indigo-200/50` - Removed redundant `border-opacity-20` where `border-black/10` was already applied 2. Replaced the static `background-image` checkbox SVG with a `mask-image` + `::after` pseudo-element approach. The checkmark color now uses `var(--theme-light-primary-contrast-500)` / `var(--theme-dark-primary-contrast-500)`, so it dynamically follows the theme's contrast color. --------- Co-authored-by: Livio Spring <9405495+livio-a@users.noreply.github.com> --- .../authentication-method-radio.tsx | 2 +- apps/login/src/components/button.tsx | 4 +-- apps/login/src/components/checkbox.tsx | 2 +- apps/login/src/lib/theme.ts | 2 +- apps/login/src/styles/globals.scss | 28 ++++++++++++++++++- 5 files changed, 32 insertions(+), 6 deletions(-) diff --git a/apps/login/src/components/authentication-method-radio.tsx b/apps/login/src/components/authentication-method-radio.tsx index 71e435d5c..165f0e468 100644 --- a/apps/login/src/components/authentication-method-radio.tsx +++ b/apps/login/src/components/authentication-method-radio.tsx @@ -29,7 +29,7 @@ export function AuthenticationMethodRadio({ value={method} data-testid={method + "-radio"} className={({ focus, checked }) => - `${focus ? "ring-primary-light-500 ring-opacity-60 ring-2 dark:ring-white/20" : ""} ${ + `${focus ? "ring-primary-light-500/60 ring-2 dark:ring-white/20" : ""} ${ checked ? "bg-background-light-400 ring-primary-light-500 dark:bg-background-dark-400 dark:ring-primary-dark-500 ring-2" : "bg-background-light-400 dark:bg-background-dark-400" diff --git a/apps/login/src/components/button.tsx b/apps/login/src/components/button.tsx index ed975a336..925d7217b 100644 --- a/apps/login/src/components/button.tsx +++ b/apps/login/src/components/button.tsx @@ -42,9 +42,9 @@ export const getButtonClasses = ( variant === ButtonVariants.Primary && color !== ButtonColors.Warn, "bg-warn-light-500 dark:bg-warn-dark-500 hover:bg-warn-light-400 hover:dark:bg-warn-dark-400 text-white dark:text-white": variant === ButtonVariants.Primary && color === ButtonColors.Warn, - "border border-button-light-border dark:border-button-dark-border text-gray-950 hover:bg-gray-500 hover:bg-opacity-20 hover:dark:bg-white hover:dark:bg-opacity-10 focus:bg-gray-500 focus:bg-opacity-20 focus:dark:bg-white focus:dark:bg-opacity-10 dark:text-white disabled:text-gray-600 disabled:hover:bg-transparent disabled:dark:hover:bg-transparent disabled:cursor-not-allowed disabled:dark:text-gray-900": + "border border-button-light-border dark:border-button-dark-border text-gray-950 hover:bg-gray-500/20 hover:dark:bg-white/10 focus:bg-gray-500/20 focus:dark:bg-white/10 dark:text-white disabled:text-gray-600 disabled:hover:bg-transparent disabled:dark:hover:bg-transparent disabled:cursor-not-allowed disabled:dark:text-gray-900": variant === ButtonVariants.Secondary, - "border border-button-light-border dark:border-button-dark-border text-warn-light-500 dark:text-warn-dark-500 hover:bg-warn-light-500 hover:bg-opacity-10 dark:hover:bg-warn-light-500 dark:hover:bg-opacity-10 focus:bg-warn-light-500 focus:bg-opacity-20 dark:focus:bg-warn-light-500 dark:focus:bg-opacity-20": + "border border-button-light-border dark:border-button-dark-border text-warn-light-500 dark:text-warn-dark-500 hover:bg-warn-light-500/10 dark:hover:bg-warn-light-500/10 focus:bg-warn-light-500/20 dark:focus:bg-warn-light-500/20": color === ButtonColors.Warn && variant !== ButtonVariants.Primary, "px-16 py-2": size === ButtonSizes.Large, "px-4 h-[36px]": size === ButtonSizes.Small, diff --git a/apps/login/src/components/checkbox.tsx b/apps/login/src/components/checkbox.tsx index a262ee357..4fc798145 100644 --- a/apps/login/src/components/checkbox.tsx +++ b/apps/login/src/components/checkbox.tsx @@ -31,7 +31,7 @@ export const Checkbox = forwardRef(function Che disabled={disabled} type="checkbox" className={classNames( - "form-checkbox text-primary-light-500 focus:ring-opacity-50 dark:text-primary-dark-500 rounded border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-offset-0", + "form-checkbox text-primary-light-500 dark:text-primary-dark-500 rounded border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200/50 focus:ring-offset-0", className, )} {...props} diff --git a/apps/login/src/lib/theme.ts b/apps/login/src/lib/theme.ts index 103301fc0..f95c5ebda 100644 --- a/apps/login/src/lib/theme.ts +++ b/apps/login/src/lib/theme.ts @@ -128,7 +128,7 @@ export const SPACING_STYLES = { // Appearance styling (complete design philosophies) export const APPEARANCE_STYLES = { flat: { - card: "bg-background-light-400 dark:bg-background-dark-500 border border-opacity-20 border border-black/10 dark:border-white/10", + card: "bg-background-light-400 dark:bg-background-dark-500 border border-black/10 dark:border-white/10", button: "border border-button-light-border dark:border-button-dark-border", // No shadows for flat design "idp-button": "border border-button-light-border dark:border-button-dark-border", // No shadows for flat design typography: "font-normal", diff --git a/apps/login/src/styles/globals.scss b/apps/login/src/styles/globals.scss index 287c44ab3..5437c3ffc 100755 --- a/apps/login/src/styles/globals.scss +++ b/apps/login/src/styles/globals.scss @@ -18,8 +18,34 @@ html { --dark-background-color: #000000; } +.form-checkbox { + position: relative; +} + .form-checkbox:checked { - background-image: url("/checkbox.svg"); + background-image: none !important; + background-color: var(--theme-light-primary-500); +} + +.dark .form-checkbox:checked { + background-color: var(--theme-dark-primary-500); +} + +.form-checkbox:checked::after { + content: ""; + position: absolute; + inset: 0; + -webkit-mask-image: url("/checkbox.svg"); + mask-image: url("/checkbox.svg"); + -webkit-mask-size: 100% 100%; + mask-size: 100% 100%; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + background-color: var(--theme-light-primary-contrast-500); +} + +.dark .form-checkbox:checked::after { + background-color: var(--theme-dark-primary-contrast-500); } .skeleton {