Skip to content

Feat: Dark Mode Support for APISIX Dashboard #3322

@DSingh0304

Description

@DSingh0304

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

  1. 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.

  2. 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.

  3. forceColorScheme on MantineProvider: This overrides system preference entirely, making "Auto" mode a silent no-op. Using defaultColorScheme="auto" with useMantineColorScheme() is the correct approach.

  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions