Skip to main content

Dark Mode Hover Fix — Design Spec

Date: 2026-04-25

Problem

In dark mode, virtually all hover states across the app are invisible or broken. The root cause is that --muted (dark: hsl(240 6% 14%)) and --accent (dark: hsl(250 60% 18%)) are both near-black. Every component using hover:bg-muted/* or hover:bg-accent produces a hover color nearly identical to the dark card/background, making rows, buttons, and interactive elements appear to not respond to hover at all. The existing partial fix in index.css (.dark table tr:hover { ... !important }) uses the same near-black accent variable and is equally broken. Affected scope: 168 files with hover:bg-* classes; ~30+ components across every module (patients, appointments, staff, inventory, billing, doctor, settings, notifications, dashboard-widgets) using hover:bg-muted/* variants.

Approach: CSS Variable Redesign

Add one canonical --hover-overlay dark-mode token, then use a @layer utilities CSS override to remap all existing hover:bg-muted/* variants to it. Zero individual component file changes required.

Changes

1. ui/src/index.css — Three changes

Add --hover-overlay to .dark block:
--hover-overlay: hsl(250 85% 65% / 0.1);
10% of the primary purple. Renders as a subtle but clearly visible purple-tinted highlight on dark backgrounds. Single place to tune app-wide hover color. Update .dark table tr:hover rule: Replace color-mix(in srgb, var(--accent) 40%, transparent) with var(--hover-overlay). Remove the !important (no longer needed once the layer override is in place). Add @layer utilities override block:
@layer utilities {
  .dark .hover\:bg-muted:hover,
  .dark .hover\:bg-muted\/5:hover,
  .dark .hover\:bg-muted\/10:hover,
  .dark .hover\:bg-muted\/20:hover,
  .dark .hover\:bg-muted\/30:hover,
  .dark .hover\:bg-muted\/35:hover,
  .dark .hover\:bg-muted\/40:hover,
  .dark .hover\:bg-muted\/50:hover,
  .dark .hover\:bg-muted\/60:hover,
  .dark .hover\:bg-secondary:hover,
  .dark .hover\:bg-secondary\/50:hover,
  .dark .hover\:bg-accent:hover {
    background-color: var(--hover-overlay);
  }
}
These selectors have specificity 0,2,1 (.dark class + hover class + :hover pseudo) vs Tailwind’s generated 0,1,1. Same layer, higher specificity wins — no !important needed.

2. ui/src/components/shared/GlobalError.tsx line 35

Add dark:hover:bg-neutral-700 to the white button that currently has only hover:bg-neutral-200.

3. ui/src/components/chat/ChatWindow.tsx line 305

Add dark:hover:bg-zinc-800 to the mobile back button that currently has only hover:bg-gray-100.

What is NOT changed

  • Individual module components (patients, appointments, staff, etc.) — the layer override handles all of them globally
  • Light mode hover behavior — all changes are scoped to .dark
  • The sidebar hover — already uses hover:bg-sidebar-accent (CSS var, correct)
  • Button component — already uses CSS-var-based variants
  • Landing page / auth pages — intentionally dark-background components with hover:bg-white/5, hover:bg-neutral-800 etc. are fine as-is

Visual Result

Before: hover on table rows, notification toggles, patient cards, appointment list items — near-invisible, same color as background. After: all hover states show a subtle hsl(250 85% 65% / 0.1) purple overlay — clearly visible, consistent with the primary color, not harsh.

Files Changed

FileChange
ui/src/index.cssAdd --hover-overlay var, update table rule, add @layer utilities override block
ui/src/components/shared/GlobalError.tsxAdd dark:hover:bg-neutral-700
ui/src/components/chat/ChatWindow.tsxAdd dark:hover:bg-zinc-800
Total: 3 files.