✨ Added system appearance mode to Ghost Admin#28911
Conversation
closes https://linear.app/ghost/issue/DES-1288/add-automatic-to-admin-darklight-mode - admin previously only had a light/dark toggle stored as a boolean; users had no way to follow their OS appearance preference - adds a third "system" mode that resolves to light/dark from the OS and tracks changes live via prefers-color-scheme - new users (owner setup + invited staff) now default to "system"; existing users keep their current preference, with legacy booleans migrated to strings on read so no data backfill is needed - the user menu's dark mode switch is replaced with a Light/Dark/System submenu - Ember remains the source of truth for the admin theme (class + dark stylesheet + OS listener); the React menu applies changes through the state bridge so editor and UI consumers stay in sync
|
Important Review skippedReview was skipped due to path filters ⛔ Files ignored due to path filters (7)
CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
WalkthroughAdmin theme handling now uses Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
apps/admin/src/layout/app-sidebar/user-menu.tsx (1)
53-73: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse radio semantics for the single-select appearance group.
These options form a mutually-exclusive selection, but they render as plain menu items with only a visual (aria-hidden) checkmark, so assistive tech can't announce the active choice. Consider
DropdownMenuRadioGroup/DropdownMenuRadioItem(orrole="menuitemradio"+aria-checked) so the selected appearance is conveyed.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/admin/src/layout/app-sidebar/user-menu.tsx` around lines 53 - 73, The appearance options in user-menu.tsx are a single-select group but are implemented as plain DropdownMenuItem entries, so the active theme is not exposed to assistive tech. Update the theme picker inside DropdownMenuSubContent to use radio-style semantics, preferably with DropdownMenuRadioGroup and DropdownMenuRadioItem, and keep the selected value tied to the current theme in THEME_OPTIONS / setTheme. If you keep the current structure, add the proper menuitemradio role and aria-checked state on the item rendered for each theme so the checked appearance is announced.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@apps/admin/src/layout/app-sidebar/user-menu.tsx`:
- Around line 53-73: The appearance options in user-menu.tsx are a single-select
group but are implemented as plain DropdownMenuItem entries, so the active theme
is not exposed to assistive tech. Update the theme picker inside
DropdownMenuSubContent to use radio-style semantics, preferably with
DropdownMenuRadioGroup and DropdownMenuRadioItem, and keep the selected value
tied to the current theme in THEME_OPTIONS / setTheme. If you keep the current
structure, add the proper menuitemradio role and aria-checked state on the item
rendered for each theme so the checked appearance is announced.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 4b0d21fc-6d97-4f17-91dd-315553fa15dd
📒 Files selected for processing (13)
apps/admin/src/ember-bridge/ember-bridge.tsxapps/admin/src/hooks/use-theme.tsapps/admin/src/hooks/user-preferences.test.tsxapps/admin/src/hooks/user-preferences.tsapps/admin/src/layout/app-sidebar/user-menu.tsxe2e/helpers/pages/admin/sidebar/sidebar-page.tse2e/playwright.config.mjse2e/tests/admin/sidebar/navigation.test.tsghost/admin/app/services/feature.jsghost/admin/app/services/state-bridge.jsghost/admin/tests/integration/services/feature-test.jsghost/core/core/server/models/user.jsghost/core/test/unit/server/models/user.test.js
|
| Command | Status | Duration | Result |
|---|---|---|---|
nx run @tryghost/admin:build |
✅ Succeeded | 6s | View ↗ |
nx run ghost:build:assets |
✅ Succeeded | 2s | View ↗ |
nx run ghost:build:tsc |
✅ Succeeded | 7s | View ↗ |
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗
☁️ Nx Cloud last updated this comment at 2026-06-25 19:33:25 UTC
ref https://linear.app/ghost/issue/DES-1288/add-automatic-to-admin-darklight-mode - new users now persist accessibility {"nightShift":"system"} at creation, so every serialised user reflects the new default — including authors embedded in posts/pages responses and webhook payloads, and the owner created during blog setup - regenerated the affected admin API, webhook, and setup snapshots; every change is limited to the accessibility value and the resulting content-length headers
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #28911 +/- ##
==========================================
- Coverage 74.38% 74.35% -0.04%
==========================================
Files 1559 1559
Lines 135013 135014 +1
Branches 16416 16408 -8
==========================================
- Hits 100424 100383 -41
- Misses 33599 33612 +13
- Partials 990 1019 +29
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|

Closes https://linear.app/ghost/issue/DES-1288/add-automatic-to-admin-darklight-mode
Why
Ghost Admin only had a light/dark toggle stored as a boolean, so users had no way to follow their operating system's appearance preference. This adds a "system" mode that mirrors the OS and updates live when it changes.
What
prefers-color-scheme.nightShiftmoves from a boolean to a string enum (light/dark/system). Legacy booleans are migrated to strings on read in both the React and Ember layers, so there's no database backfill.system. Existing users keep their current preference; users with no stored preference continue to resolve to light.Implementation notes for reviewers
darkclass, the dark stylesheet, and the OS listener). The React menu applies changes through the state bridge (applyAdminThemePreference/preloadAdminThemeStylesheet) for instant feedback, and the existing user-update bridge re-applies the theme after the save round-trips, so editor and UI consumers stay in sync.nightShiftis optional (not eagerly defaulted) so an absent preference isn't written back on unrelated preference saves; the display fallback to light lives at the read site.colorSchemeis pinned tolightat the Playwright config level so theme state is deterministic across the suite under the newsystemdefault.Tests
add(invited staff, importing skip, explicit preference) andsetup(owner) default-injection paths.nightShiftmigration (legacy booleans, strings, absent, invalid) and non-eager persistence.Checklist