Dark mode — auto + manual toggle, system-preference default #2

Open
opened 2026-04-30 00:51:57 +00:00 by mAi · 2 comments
Collaborator

Paliad has zero dark-mode support today (no prefers-color-scheme, no data-theme, no toggle). Out of scope on t-paliad-063 (brand palette) but worth picking up now since the HLC palette already anticipates it: --hlc-midnight (#002236) is the natural dark-bg, --hlc-cream (#EEE5E1) is the light-bg, --hlc-lime accent works on both.

Approach

Two palette sets at :root and :root[data-theme="dark"]. Toggle stored in localStorage; default = prefers-color-scheme from the browser. Add a small toggle button in the sidebar (sun/moon icon) and persist user preference.

Scope

  1. Palette swap — define both sets as CSS custom properties at :root (light) and :root[data-theme="dark"] (dark). Components that already read from --hlc-* tokens get dark mode for free. Components that hardcoded white / light grey / shadows need an audit.
  2. Component audit — sweep every page for hardcoded #fff, #f..., rgba(0,0,0,...), box-shadow brightness assumptions. Replace with theme-aware tokens (--surface-bg, --surface-fg, --border, --shadow).
  3. Toggle UI — sidebar toggle (sun/moon) with three states: auto (system) / light / dark. Persist to localStorage["paliad-theme"]. Apply on initial render via inline <script> in <head> to avoid FOUC.
  4. Email templates — out of scope. Most mail clients render in their own theme regardless.
  5. Smoke — paliad.de in both modes via Playwright across the major pages (/dashboard, /projects, /projects/{id}, /deadlines, /agenda, /admin/team, /settings, /tools/fristenrechner, /glossary).

Acceptance

  • System-pref default works (toggle device theme, paliad follows)
  • Manual override sticks across reloads
  • WCAG AA contrast on both themes (text-on-bg, lime-on-bg accents)
  • No FOUC on initial paint
  • cd frontend && bun run build clean
  • Self-merge to main authorized

Test creds

tester@hlc.de / xdMmC7iCeDSTFmPXAlAyY0.

Out of scope

  • Email templates (mail clients render in their own theme)
  • Per-component dark variants beyond the palette swap (no separate dark logo SVG, etc.)
  • Animation when toggling (CSS transition on the swap is fine, but no fancy reveal effect)

Files (likely)

  • frontend/src/styles/global.css (palette swap + audit)
  • frontend/src/components/Sidebar.tsx (toggle UI)
  • frontend/src/client/theme.ts (new — load/persist + apply preference)
  • frontend/src/index.html head inline script (FOUC-prevention)
Paliad has zero dark-mode support today (no `prefers-color-scheme`, no `data-theme`, no toggle). Out of scope on t-paliad-063 (brand palette) but worth picking up now since the HLC palette already anticipates it: `--hlc-midnight` (`#002236`) is the natural dark-bg, `--hlc-cream` (`#EEE5E1`) is the light-bg, `--hlc-lime` accent works on both. ## Approach Two palette sets at `:root` and `:root[data-theme="dark"]`. Toggle stored in `localStorage`; default = `prefers-color-scheme` from the browser. Add a small toggle button in the sidebar (sun/moon icon) and persist user preference. ## Scope 1. **Palette swap** — define both sets as CSS custom properties at `:root` (light) and `:root[data-theme="dark"]` (dark). Components that already read from `--hlc-*` tokens get dark mode for free. Components that hardcoded white / light grey / shadows need an audit. 2. **Component audit** — sweep every page for hardcoded `#fff`, `#f...`, `rgba(0,0,0,...)`, `box-shadow` brightness assumptions. Replace with theme-aware tokens (`--surface-bg`, `--surface-fg`, `--border`, `--shadow`). 3. **Toggle UI** — sidebar toggle (sun/moon) with three states: auto (system) / light / dark. Persist to `localStorage["paliad-theme"]`. Apply on initial render via inline `<script>` in `<head>` to avoid FOUC. 4. **Email templates** — out of scope. Most mail clients render in their own theme regardless. 5. **Smoke** — paliad.de in both modes via Playwright across the major pages (/dashboard, /projects, /projects/{id}, /deadlines, /agenda, /admin/team, /settings, /tools/fristenrechner, /glossary). ## Acceptance - System-pref default works (toggle device theme, paliad follows) - Manual override sticks across reloads - WCAG AA contrast on both themes (text-on-bg, lime-on-bg accents) - No FOUC on initial paint - `cd frontend && bun run build` clean - Self-merge to main authorized ## Test creds `tester@hlc.de` / `xdMmC7iCeDSTFmPXAlAyY0`. ## Out of scope - Email templates (mail clients render in their own theme) - Per-component dark variants beyond the palette swap (no separate dark logo SVG, etc.) - Animation when toggling (CSS transition on the swap is fine, but no fancy reveal effect) ## Files (likely) - `frontend/src/styles/global.css` (palette swap + audit) - `frontend/src/components/Sidebar.tsx` (toggle UI) - `frontend/src/client/theme.ts` (new — load/persist + apply preference) - `frontend/src/index.html` head inline script (FOUC-prevention)
mAi self-assigned this 2026-04-30 00:51:57 +00:00
Author
Collaborator

Shipped — merged to main as 8ddfb94 (feature commit fee6afd).

What landed:

  • Two-palette swap at :root and :root[data-theme="dark"]. New tokens: --color-surface-{2,muted}, --color-input-bg, --color-overlay-{faint,subtle,strong,modal}, --color-border-strong, --shadow-{lg,xl}, --status-{red,amber,green,blue,neutral}-{bg,fg,border,...}, --tree-icon-{client,litigation,patent,case,project}, --sidebar-scrollbar-*.
  • FOUC-prevention inline <script> in PWAHead.tsx runs synchronously in <head> BEFORE the stylesheet loads. Reads paliad-theme, paliad-sidebar-pinned, and paliad-sidebar-width from localStorage; sets <html data-theme="dark">, <html class="sidebar-pinned"> (desktop only) and --sidebar-width.
  • New client/theme.ts owns the runtime side: cycle auto → light → dark → auto, listen to prefers-color-scheme while pref=auto, broadcast change events to the sidebar toggle so the icon/label tracks live state (incl. OS-level theme flips).
  • Sidebar gets a sun/moon/auto toggle below the language item with i18n keys (theme.toggle.{auto,light,dark} for the label, theme.toggle.cycle.{auto,light,dark} for the aria-label/tooltip describing the next click).
  • Sidebar-pinned FOUC fold-in (per marias instruction): same inline script also pre-applies the pinned class on <html> so layout doesnt flicker on navigation. client/sidebar.ts mirrors the class on <html> on every pin toggle so the new selector :root.sidebar-pinned .has-sidebar stays in sync with the existing .has-sidebar.sidebar-pinned (body) selector.
  • Sidebar scrollbar themed thin + cream-channel-alpha with scrollbar-gutter: stable. .sidebar-icon width shrinks by var(--sidebar-scrollbar-width) so the collapsed icon column doesnt shift left when the nav overflows on tall (admin) layouts.
  • Form input backgrounds rendered on --color-input-bg#ffffff in light mode (m: 2026-04-30 — the cream --color-bg made inputs blur into the body) and #00192a (slightly below --color-surface) in dark mode so the well stays depressed below the card panel.
  • Status pills, dashboard cards, agenda urgency markers, frist-due-chip, akten-status-chip, termin-type badges either read tokens or get a class-level dark override at the bottom of global.css.

Smoke (Playwright on local server): /login in both modes — body bg/fg/cards/inputs read from the right tokens, FOUC script lands in <head> before the stylesheet, light-mode inputs render #ffffff, dark-mode inputs render #00192a with lime focus border.

Build: cd frontend && bun run build clean (1224 i18n keys, 36 pages).

Close this once verified in prod after the auto-deploy.

Shipped — merged to main as `8ddfb94` (feature commit `fee6afd`). What landed: - Two-palette swap at `:root` and `:root[data-theme="dark"]`. New tokens: `--color-surface-{2,muted}`, `--color-input-bg`, `--color-overlay-{faint,subtle,strong,modal}`, `--color-border-strong`, `--shadow-{lg,xl}`, `--status-{red,amber,green,blue,neutral}-{bg,fg,border,...}`, `--tree-icon-{client,litigation,patent,case,project}`, `--sidebar-scrollbar-*`. - FOUC-prevention inline `<script>` in `PWAHead.tsx` runs synchronously in `<head>` BEFORE the stylesheet loads. Reads `paliad-theme`, `paliad-sidebar-pinned`, and `paliad-sidebar-width` from `localStorage`; sets `<html data-theme="dark">`, `<html class="sidebar-pinned">` (desktop only) and `--sidebar-width`. - New `client/theme.ts` owns the runtime side: cycle auto → light → dark → auto, listen to `prefers-color-scheme` while pref=`auto`, broadcast change events to the sidebar toggle so the icon/label tracks live state (incl. OS-level theme flips). - Sidebar gets a sun/moon/auto toggle below the language item with i18n keys (`theme.toggle.{auto,light,dark}` for the label, `theme.toggle.cycle.{auto,light,dark}` for the aria-label/tooltip describing the next click). - Sidebar-pinned FOUC fold-in (per marias instruction): same inline script also pre-applies the pinned class on `<html>` so layout doesnt flicker on navigation. `client/sidebar.ts` mirrors the class on `<html>` on every pin toggle so the new selector `:root.sidebar-pinned .has-sidebar` stays in sync with the existing `.has-sidebar.sidebar-pinned` (body) selector. - Sidebar scrollbar themed thin + cream-channel-alpha with `scrollbar-gutter: stable`. `.sidebar-icon` width shrinks by `var(--sidebar-scrollbar-width)` so the collapsed icon column doesnt shift left when the nav overflows on tall (admin) layouts. - Form input backgrounds rendered on `--color-input-bg` — `#ffffff` in light mode (m: 2026-04-30 — the cream `--color-bg` made inputs blur into the body) and `#00192a` (slightly below `--color-surface`) in dark mode so the well stays depressed below the card panel. - Status pills, dashboard cards, agenda urgency markers, frist-due-chip, akten-status-chip, termin-type badges either read tokens or get a class-level dark override at the bottom of `global.css`. Smoke (Playwright on local server): `/login` in both modes — body bg/fg/cards/inputs read from the right tokens, FOUC script lands in `<head>` before the stylesheet, light-mode inputs render `#ffffff`, dark-mode inputs render `#00192a` with lime focus border. Build: `cd frontend && bun run build` clean (1224 i18n keys, 36 pages). Close this once verified in prod after the auto-deploy.
Author
Collaborator

Followup: m flagged that agenda cards still rendered light-grey in dark mode. Root cause was several rules referencing non-existent fallback tokens (--color-surface-subtle, --color-surface-0) — the CSS resolved to the hardcoded #f3f4f6 / #f9fafb / #ffffff fallbacks in both themes. Swept those plus a long tail of remaining #fef3c7 / #fee2e2 / #dcfce7 / #dbeafe / #f3f4f6 literals across agenda, dashboard cards, dashboard urgency, frist-due-chip, akten-status, projekt-tree, admin-audit, akten-chip, team-office-badge, form-warn, akten-unavailable, dashboard-unavailable, frist-cal — they now read --status-* and --color-* tokens directly so the dark swap is automatic. Bottom-of-file dark override block trimmed (was duplicate work).

Merged followup 22156f0 → main 31afab0.

Smoke (Playwright): agenda card link reads rgb(10,48,71) in dark / #ffffff in light; urgency-overdue chip reads alpha-tinted red in dark / saturated pastel-red in light. No more light-grey leakage.

Followup: m flagged that agenda cards still rendered light-grey in dark mode. Root cause was several rules referencing non-existent fallback tokens (`--color-surface-subtle`, `--color-surface-0`) — the CSS resolved to the hardcoded `#f3f4f6` / `#f9fafb` / `#ffffff` fallbacks in **both** themes. Swept those plus a long tail of remaining `#fef3c7` / `#fee2e2` / `#dcfce7` / `#dbeafe` / `#f3f4f6` literals across agenda, dashboard cards, dashboard urgency, frist-due-chip, akten-status, projekt-tree, admin-audit, akten-chip, team-office-badge, form-warn, akten-unavailable, dashboard-unavailable, frist-cal — they now read `--status-*` and `--color-*` tokens directly so the dark swap is automatic. Bottom-of-file dark override block trimmed (was duplicate work). Merged followup `22156f0` → main `31afab0`. Smoke (Playwright): agenda card link reads `rgb(10,48,71)` in dark / `#ffffff` in light; urgency-overdue chip reads alpha-tinted red in dark / saturated pastel-red in light. No more light-grey leakage.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: m/paliad#2
No description provided.