Add a "System" option to the dark/light mode setting (follow OS appearance)

Open Beginner friendly
#5,638 1 comment 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@fabianfrankwerner is already working on this.

Since Oct 9, 2026.

  • #5639 by @fabianfrankwerner — open

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
62/100
Issue type
Feature
Clarity
Clearly specified
Activity status
Active
Tech stack
nextjs, typescript
Domain
frontend

Research direction

Start in apps/dokploy/components/ui/modeToggle.tsx, which currently calls setTheme with only "dark" or "light", and check how it is rendered from apps/dokploy/components/layouts/user-nav.tsx. Replace the single button with a DropdownMenuTrigger that offers Light, Dark and System through DropdownMenuRadioGroup and DropdownMenuRadioItem from apps/dokploy/components/ui/dropdown-menu.tsx. Done means all three options persist via next-themes, the trigger icon still follows the resolved dark class on html, and the checked radio item does not cause a hydration mismatch.

Written by the indexing model from the issue text.

Description

enhancement
What problem will this feature address?

Dokploy currently only offers a two-state theme toggle. There is no way to select "follow the OS / system appearance" as an explicit, sticky choice.

The relevant code:

  • apps/dokploy/pages/_app.tsx already configures next-themes with defaultTheme="system" and enableSystem, so a first-time visitor with no stored preference gets system behaviour.
  • apps/dokploy/components/ui/modeToggle.tsx only ever calls setTheme("dark") / setTheme("light"), and next-themes persists that choice to localStorage.

The consequence is that "system" is effectively a one-time default and not a mode:

  1. After a single click on the toggle, the stored preference is a fixed light or dark.
  2. From that point on there is no UI anywhere in Dokploy that can restore "system". The only way back is manually clearing localStorage.
  3. Consequently the UI stops following OS-level appearance changes (macOS/iOS Dark Mode switch, Chrome/Safari per-site theme, forced-colors / high-contrast profiles on Windows and Linux desktops, GNOME/KDE "Night Light"-style scheduled switches).

This matters for self-hosters in particular: Dokploy is a self-hosted panel that is usually opened in a browser tab alongside other tools, and several of those users change their OS appearance on a schedule (night shift, dark mode toggling with daylight). They currently have to pick one mode and lose the other.

There is precedent that this is a real request: PR #3163 (feat: add support for a system theme) attempted to add it and was closed without merging, with feedback that the implementation over-engineered a simple toggle, dropped the existing CSS animations, and had a bug in the sr-only span. This issue is intended to re-propose the feature with a minimal implementation that keeps the current visuals intact.

Describe the solution you'd like

Turn the existing single button into a small dropdown with three explicit, selectable options:

  • Light — forces light
  • Dark — forces dark
  • System — follows the OS, and keeps following it live

Concretely, the least invasive version that preserves the current look and feel:

  • Keep the existing Button exactly as it is today, including the Sun/Moon rotate + scale CSS animation classes, and use it as the DropdownMenuTrigger.
  • The trigger icon keeps reflecting the resolved appearance (driven by the dark class on <html>, which next-themes already sets during the pre-hydration script). So in "System" mode the icon still shows a sun on a light OS and a moon on a dark OS, and the animation still plays when the OS theme changes. This is what makes the mode legible at a glance without needing a third icon on the trigger.
  • Add a DropdownMenuRadioGroup with a DropdownMenuRadioItem per option so the current selection is checked and screen-reader accessible.
  • Add a mounted guard for the radio value only, so the checkmark does not cause a hydration mismatch (theme is undefined on the first render).

No new dependency, no change to _app.tsx (it already has enableSystem), and sonner.tsx already forwards "system" through to its toaster, so toast styling stays correct.

Describe alternatives you've considered
  • Keep the button and cycle through light → dark → system on each click. Fewer lines, but the current mode is not visible without clicking, and a stray click silently changes behaviour. It also requires the trigger icon to change per mode, which is what caused the confusion discussed in #3163 (the icon showing "dark" while the OS is dark). A dropdown makes the state explicit.
  • Add the option to Settings and keep the button as a two-state toggle. Rejected because it still leaves the quick-access control misleading, and it spreads the setting across two places for a preference users expect to change often.
  • Persist a per-user preference in the database instead of localStorage. Out of scope for this request. localStorage via next-themes already works for a per-browser preference, and doing it server-side would require schema changes, an API route and permission handling for a purely cosmetic setting.
Additional context
  • Affected file: apps/dokploy/components/ui/modeToggle.tsx
  • Only consumer today: apps/dokploy/components/layouts/user-nav.tsx (renders <ModeToggle /> inside the account dropdown next to the "My Account" label), so the change is contained and does not affect other layouts.
  • apps/dokploy/components/ui/dropdown-menu.tsx already exports DropdownMenuRadioGroup and DropdownMenuRadioItem, so no new primitive is needed.
  • Reference: #1155 (the original light mode feature) and #3163 (previous attempt at a system theme).

I have a minimal implementation ready that keeps the existing animations and fixes the accessibility bug flagged in #3163, and I will open it as a PR against canary.

Dominant language
TypeScript
Stars
38.2k
Forks
3.1k
Avg merge
2d 11h
Merged PRs (30d)
65

Getting set up

Open in Codespaces

Starts the project's dev container in your browser, under your own GitHub account.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from Dokploy/dokploy

All issues in Dokploy/dokploy

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.