Skip to content

feat: add global dark mode toggle via URL flag#1108

Open
TaprootFreak wants to merge 3 commits into
developfrom
feat/dark-mode-toggle
Open

feat: add global dark mode toggle via URL flag#1108
TaprootFreak wants to merge 3 commits into
developfrom
feat/dark-mode-toggle

Conversation

@TaprootFreak

Copy link
Copy Markdown
Collaborator

Summary

  • Switch Tailwind from darkMode: 'media' (system-preference) to 'class' so dark mode is opt-in via JavaScript instead of OS-driven.
  • New ThemeContextProvider reads ?dark=1/?dark=0 (also ?theme=dark/?theme=light) from the URL on mount, persists the choice in localStorage under dfx.srv.darkMode, and toggles a dark class on <html> and <body>. The flag is stripped from the URL after consumption.
  • Globally, html.dark / body.dark flip the background to #072440 (dfxBlue.800) so library components (Buy/Sell/Swap/KYC/Compliance/etc.) inherit a dark surface even without per-component dark: variants.
  • Dashboard screens (which used hardcoded inline styles) now consume theme tokens from useThemeContext() and forward an isDark flag to the dashboard chart components so ApexCharts pick up the dark theme. The Financial Overview page keeps its always-dark rendering and now integrates cleanly with the global theme.

The flag is intentionally URL-only — no UI toggle is exposed yet (internal/staging use).

Activation

URL param Effect
?dark=1 enable dark mode
?theme=dark enable dark mode
?dark=0 disable dark mode
?theme=light disable dark mode

Once set, the choice persists across navigations and reloads via localStorage. The param itself is removed from the URL on consumption.

Files

  • tailwind.config.jsdarkMode: 'media' -> 'class'
  • src/index.csshtml.dark / body.dark background + dark-aware .scroll-shadow
  • src/contexts/theme.context.tsx (new) — provider, hook, light/dark token palette
  • src/hooks/store.hook.ts — new darkMode storage entry
  • src/App.tsx — wrap routes in ThemeContextProvider
  • src/screens/dashboard*.screen.tsx — consume theme tokens, forward isDark to charts
  • src/components/dashboard/{financial-changes,balance-by-type-area,total-balance-short}-chart.tsx — accept dark prop, switch Apex theme + grid color
  • Tests: src/__tests__/theme.context.test.tsx (new), extended store.hook.test.ts

Test plan

  • npm run lint clean
  • npm run test — 298/298 passing (incl. new theme.context test suite)
  • npm run build:dev succeeds (only pre-existing source-map warnings from @dfx.swiss/core)
  • Manual: open https://services.dfx.swiss/?dark=1, verify whole app renders dark and URL param is stripped
  • Manual: reload without param — dark mode persists
  • Manual: open with ?dark=0 — switches back to light
  • Manual: dashboard screens render dark cards and dark chart theme
  • Manual: Financial Overview keeps its dark rendering in either mode

Switch Tailwind from media-based to class-based dark mode and add a
ThemeContextProvider that reads `?dark=1`, `?dark=0`, `?theme=dark` or
`?theme=light` from the URL, persists the choice in localStorage, and
toggles the `dark` class on `<html>` and `<body>` so the rest of the
app picks it up via Tailwind dark variants and the global CSS overrides
in index.css.

Dashboard screens (which use inline styles) consume the new theme
tokens and forward an `isDark` flag to the chart components so Apex
charts render with the correct theme. The Financial Overview page
continues to render dark by default (admin-internal page) but now
integrates cleanly with the global theme.

The flag is intended for internal use only — there is no UI toggle.
Activate dark mode by appending `?dark=1` or `?theme=dark` to any URL,
deactivate with `?dark=0` or `?theme=light`. The param is stripped
after consumption.
Replaces a weak cleanup test that only asserted the dark class was
present with a proper setDark round-trip test that verifies state,
dom classes (html + body), and localStorage all flip in both
directions.
@TaprootFreak TaprootFreak marked this pull request as ready for review May 19, 2026 20:44
@TaprootFreak TaprootFreak requested a review from davidleomay as a code owner May 19, 2026 20:44
Use createContext<ThemeContextInterface | undefined>(undefined) and throw a
clear error when consumed outside ThemeContextProvider. Removes the unsafe
undefined as any cast and gives developers an actionable failure mode.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant