Feature request
Please describe your feature
Add dark mode theme support to the APISIX Dashboard, allowing users to switch between light, dark, and system preferred color schemes.
Users frequently work with the dashboard for extended periods, and the current light only theme causes eye strain in low light environments. Most modern admin dashboards (Grafana, Kibana, Cloudflare) offer dark mode as a standard feature. Additionally, system-level dark mode preferences (prefers-color-scheme) are widely adopted across operating systems, and the dashboard currently does not respect them.
Describe the solution you'd like
A three-way theme switcher (Light / Dark / Auto) in the dashboard header, implemented as a segmented control. The "Auto" option follows the user's OS preference via the prefers-color-scheme media query.
The implementation leverages the existing stack's built-in dark mode support:
- Mantine v8: Set
defaultColorScheme="auto" on MantineProvider and use useMantineColorScheme() for runtime toggling. Mantine handles persistence to localStorage internally no additional state management is needed.
- Ant Design v5: Use Mantine's
useComputedColorScheme('light') to resolve the actual scheme (since Ant Design does not handle 'auto' internally), then conditionally apply theme.darkAlgorithm on ConfigProvider.
- Monaco Editor: Imperatively call
monaco.editor.setTheme() via a useEffect + onMount ref, since Monaco does not re-theme on React prop changes after initial mount.
- FOUC prevention: Add an inline
<script> in index.html that reads the persisted scheme from localStorage and sets data-mantine-color-scheme on <html> before CSS parses.
Files to modify
| File |
Change |
index.html |
Inline <script> for FOUC prevention |
src/main.tsx |
defaultColorScheme="auto" on MantineProvider |
src/config/antdConfigProvider.tsx |
useComputedColorScheme() → conditional theme.darkAlgorithm |
src/components/form/Editor.tsx |
Imperative monaco.editor.setTheme() via useEffect + onMount ref |
src/components/Header/index.tsx |
Add ThemeToggle component |
src/styles/global.css |
Audit for any hardcoded colors |
New files
| File |
Purpose |
src/components/Header/ThemeToggle.tsx |
Segmented control theme switcher (Light / Dark / Auto) |
A detailed proposal with the full implementation plan is available at docs/proposal-dark-mode.md.
Describe alternatives you've considered
-
Two-way toggle (Light / Dark only): Simpler, but ignores the user's OS preference. Users who switch OS themes throughout the day would need to manually toggle the dashboard each time.
-
Separate Jotai atom for color scheme state: Considered using atomWithStorage (already used for admin key persistence), but Mantine already persists the color scheme to localStorage internally under mantine-color-scheme-value. Adding a separate atom would create a dual source of truth.
-
forceColorScheme on MantineProvider: This overrides system preference entirely, making "Auto" mode a silent no-op. Using defaultColorScheme="auto" with useMantineColorScheme() is the correct approach.
-
Mantine's ColorSchemeScript React component in index.html: ColorSchemeScript is a React component and cannot be used as JSX in a plain HTML file. An inline <script> that reads localStorage and sets the data-mantine-color-scheme attribute achieves the same FOUC prevention without requiring React to mount first.
Additional context
- The existing custom CSS in
global.css already uses Mantine CSS variables (--mantine-color-*), which automatically adapt when the color scheme changes no manual CSS overrides are needed for most components.
- This change is purely additive no structural or architectural changes are required.
- Ant Design is only used for
ProTable on list pages; a single ConfigProvider algorithm swap covers all table instances.
- E2E tests should verify auto mode resolution (using Playwright's
emulateMedia({ colorScheme: 'dark' })) and theme persistence across page reloads.
Feature request
Please describe your feature
Add dark mode theme support to the APISIX Dashboard, allowing users to switch between light, dark, and system preferred color schemes.
Users frequently work with the dashboard for extended periods, and the current light only theme causes eye strain in low light environments. Most modern admin dashboards (Grafana, Kibana, Cloudflare) offer dark mode as a standard feature. Additionally, system-level dark mode preferences (
prefers-color-scheme) are widely adopted across operating systems, and the dashboard currently does not respect them.Describe the solution you'd like
A three-way theme switcher (Light / Dark / Auto) in the dashboard header, implemented as a segmented control. The "Auto" option follows the user's OS preference via the
prefers-color-schememedia query.The implementation leverages the existing stack's built-in dark mode support:
defaultColorScheme="auto"onMantineProviderand useuseMantineColorScheme()for runtime toggling. Mantine handles persistence tolocalStorageinternally no additional state management is needed.useComputedColorScheme('light')to resolve the actual scheme (since Ant Design does not handle'auto'internally), then conditionally applytheme.darkAlgorithmonConfigProvider.monaco.editor.setTheme()via auseEffect+onMountref, since Monaco does not re-theme on React prop changes after initial mount.<script>inindex.htmlthat reads the persisted scheme fromlocalStorageand setsdata-mantine-color-schemeon<html>before CSS parses.Files to modify
index.html<script>for FOUC preventionsrc/main.tsxdefaultColorScheme="auto"onMantineProvidersrc/config/antdConfigProvider.tsxuseComputedColorScheme()→ conditionaltheme.darkAlgorithmsrc/components/form/Editor.tsxmonaco.editor.setTheme()viauseEffect+onMountrefsrc/components/Header/index.tsxThemeTogglecomponentsrc/styles/global.cssNew files
src/components/Header/ThemeToggle.tsxA detailed proposal with the full implementation plan is available at
docs/proposal-dark-mode.md.Describe alternatives you've considered
Two-way toggle (Light / Dark only): Simpler, but ignores the user's OS preference. Users who switch OS themes throughout the day would need to manually toggle the dashboard each time.
Separate Jotai atom for color scheme state: Considered using
atomWithStorage(already used for admin key persistence), but Mantine already persists the color scheme tolocalStorageinternally undermantine-color-scheme-value. Adding a separate atom would create a dual source of truth.forceColorSchemeonMantineProvider: This overrides system preference entirely, making "Auto" mode a silent no-op. UsingdefaultColorScheme="auto"withuseMantineColorScheme()is the correct approach.Mantine's
ColorSchemeScriptReact component inindex.html:ColorSchemeScriptis a React component and cannot be used as JSX in a plain HTML file. An inline<script>that readslocalStorageand sets thedata-mantine-color-schemeattribute achieves the same FOUC prevention without requiring React to mount first.Additional context
global.cssalready uses Mantine CSS variables (--mantine-color-*), which automatically adapt when the color scheme changes no manual CSS overrides are needed for most components.ProTableon list pages; a singleConfigProvideralgorithm swap covers all table instances.emulateMedia({ colorScheme: 'dark' })) and theme persistence across page reloads.