Skip to content

feat(frontend): add a follow-system theme mode - #7026

Open
ridersaying wants to merge 1 commit into
MHSanaei:mainfrom
ridersaying:main
Open

ridersaying wants to merge 1 commit into
MHSanaei:mainfrom
ridersaying:main

Conversation

@ridersaying

Copy link
Copy Markdown

Summary

Adds a third theme mode, system, so the panel can follow the OS light/dark preference instead of forcing an explicit choice. The theme switch now cycles light → dark → ultra dark → system, and in system mode the panel repaints whenever the desktop scheme changes, without a reload.

Why

The panel only offered light, dark and ultra dark, stored as a boolean in dark-mode. Anyone running a scheduled dark mode (macOS auto appearance, GNOME night light, Windows auto dark) has to flip the switch twice a day by hand, and the panel never matches the rest of the desktop.

I searched the issue tracker and the PR list first — prefers-color-scheme, "auto theme", "theme follow system" and "system dark" return nothing, and no open or closed PR implements it. #6827 (token-based theme customisation) and #6949 (accent colours) are the nearest neighbours and neither touches the mode itself. So this is a new feature rather than a fix to something existing.

Type of change

  • Bug fix
  • New feature
  • Refactoring (no behavior change)
  • Documentation
  • Tests only
  • Build / CI / tooling
  • Other

Areas affected

  • Frontend (UI / panel pages)
  • Backend (API endpoints, login, settings)
  • Xray config generation
  • Subscription (share links / Clash / JSON)
  • Statistics / traffic counters
  • Database / migrations
  • Install / upgrade script
  • Docker image
  • Multi-node (sub-nodes)
  • Telegram bot

How was this tested?

Unit / component (jsdom)

npx vitest run --project components src/test/theme-system.test.tsx — 4 tests, each written to fail on a specific regression:

  • honours the legacy dark-mode boolean when theme-mode was never written — a stored dark-mode=false with no theme-mode must open light, so existing users keep their theme through the upgrade.
  • follows the OS scheme in system mode — flipping the matchMedia listener must repaint body class and color-scheme without a remount.
  • keeps an explicit mode pinned when the OS scheme changes — an explicit light/dark choice must ignore OS changes; this is the regression that would make the feature annoying rather than useful.
  • cycles light → dark → ultra dark → system → light — pins the cycle order, including that ultra dark is dropped on the way out.

src/test/storybook-theme.test.tsx needed one change: localStorage.clear() between cases, because the provider now seeds from storage at mount instead of a module-level snapshot, so a toggle in one case leaked into the next.

Real browser, against a built panel

The branch was built into a linux/amd64 binary and served a real panel; Chromium drove the login page with CDP colour-scheme emulation:

step aria-label color-scheme body class
fresh profile 主题: 深色 dark dark
click 1 主题: 极致深色 dark dark
click 2 主题: 跟随系统 light light
OS → dark, no reload 主题: 跟随系统 dark dark
OS → light, no reload 主题: 跟随系统 light light
click 3 主题: 浅色 light light
reload 主题: 深色 dark dark

Rows 3–5 are the point of the change: same tab, same session, only the emulated OS scheme changing.

Three platforms

npm run typecheck, npm run lint, npm run format:check and npx vite build were run on macOS (Node 26), Windows 11 (Node 26) and Ubuntu (Node 24): all clean, no warnings. The full component suite (100 files, 501 tests) passes locally.

Screenshots / recordings

The toggle sits next to the docs and donate buttons in the sidebar brand row and in the login toolbar, and shows a desktop icon in system mode; the command palette action (Ctrl/Cmd+K → Theme) is titled with the current mode too.

I have full-size captures of each state and can attach them here if you want them in the review — tell me and I will upload.

Breaking changes

None.

  • dark-mode (existing) and isUltraDarkThemeEnabled are still written on every change, so older panel builds, user scripts and anything reading the boolean keep working.
  • theme-mode is new and optional; when absent, the stored boolean seeds it, so nobody's theme changes after the upgrade.
  • useTheme() keeps its existing shape (isDark, isUltra, toggleTheme, toggleUltra, antdThemeConfig) and adds mode, systemDark, setMode, cycleTheme. Existing consumers (HostsPage, NodesPage, ApiDocsPage, Sparkline, …) needed no changes.
  • New i18n keys are menu.themeLight, menu.themeDark, menu.themeUltraDark, menu.themeSystem, translated in all 14 locales. i18n-dead-keys.test.ts passes, so no locale is missing them.

Checklist

  • I tested the change locally and confirmed the described behavior.
  • I added or updated tests for the new behavior (when applicable).
  • go build ./... passes locally, and the linux/amd64 binary built from this branch is running on a production panel.
  • For frontend changes: npm run lint, npm run typecheck, and npm run build pass.

Note on the third item: no Go file is touched by this PR, so the Go test suite was not run.

The theme switch only offered light, dark and ultra dark, all stored as a
boolean, so the panel never matched the OS. Users on a schedule-based dark
mode had to flip it by hand twice a day.

A third stored value, theme-mode (light | dark | system), now drives the
switch, which cycles light -> dark -> ultra dark -> system. In system mode
the panel subscribes to prefers-color-scheme and repaints when the desktop
changes, without a reload. Ultra dark stays a variant of dark and is
dropped when the resolved theme is light.

The legacy dark-mode boolean is still written so older builds and scripts
keep working, and it seeds theme-mode on first load.
@github-actions

Copy link
Copy Markdown
Contributor

No review ran on this head: the account's usage limit was already spent when this run started. Nothing in this pull request was examined. A maintainer can ask for one with @claude review.

This branch has not been deployed

No deployments
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