Skip to content

✨(frontend) add dark / light / system colour-mode switcher - #2519

Open
MashdorDev wants to merge 6 commits into
suitenumerique:mainfrom
MashdorDev:feat/theme-color-mode
Open

✨(frontend) add dark / light / system colour-mode switcher#2519
MashdorDev wants to merge 6 commits into
suitenumerique:mainfrom
MashdorDev:feat/theme-color-mode

Conversation

@MashdorDev

Copy link
Copy Markdown

Purpose

Docs already ships a dark Cunningham token set, but there is no way for a user to select it and no following of the OS colour scheme. This adds an in-app dark / light / system colour-mode switcher, which is one of the recurring asks around theming.

Proposal

  • Add a themeMode (light / dark / system) to the theme store, persisted to localStorage
  • In system mode, resolve and follow the OS prefers-color-scheme live
  • Pre-hydration inline script in _document sets the theme class on <html> so the correct scheme paints on first frame (no flash), with suppressHydrationWarning
  • A user's explicit mode preference takes precedence over the plain light/dark FRONTEND_THEME backend default (non-default brand themes like dsfr still apply)
  • A ThemeSwitch control in the left-panel footer (app) and the home header (landing page)
  • Unit tests for the theme-mode store logic

The store maps the mode onto the existing Cunningham themes (light -> default, dark -> dark), so no new tokens are introduced.

External contributions

Thank you for your contribution! 🎉

General requirements

CI requirements

  • I made sure that all existing tests are passing
  • I have signed off my commits with git commit --signoff (DCO compliance)
  • I have signed my commits with my SSH or GPG key (git commit -S)
  • My commit messages follow the required format: <gitmoji>(type) title description
  • I have added a changelog entry under ## [Unreleased] section (if noticeable change)

AI requirements

  • I used AI assistance to produce part or all of this contribution
  • I have read, reviewed, understood and can explain the code I am submitting
  • I can jump in a call or a chat to explain my work to a maintainer

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds persisted light, dark, and system color-mode state with system preference resolution. Theme providers hydrate stored preferences, synchronize operating-system changes, and preserve configured brand themes. A pre-hydration document script applies the initial theme class. The new translated ThemeSwitch is exported and rendered in the home header and left-panel footer, while editors and related styling respond to the active theme. Tests cover persistence, mode resolution, and invalid stored values.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested reviewers: AntoLC, qbey, sampaccoud

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: adding a light/dark/system colour-mode switcher.
Description check ✅ Passed The description directly describes the theme-mode switcher, persistence, system sync, hydration fix, and related UI changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

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

Inline comments:
In `@src/frontend/apps/impress/src/components/ThemeSwitch.tsx`:
- Around line 30-42: Update the ThemeSwitch component’s DropdownMenu usage to
remove selectedValues, allowing the single-choice theme options to use radio
semantics via isSelected. Also remove the redundant inner aria-label while
retaining the DropButton label.

In `@src/frontend/apps/impress/src/core/config/ThemeProvider.tsx`:
- Around line 22-35: Prevent the OS color-scheme listener in ThemeProvider from
overriding brand themes: when onChange checks the store, only call
setThemeMode('system') if themeMode is 'system' and the current theme is the
default theme; alternatively update setTheme to synchronize themeMode when
applying a brand theme. Use useCunninghamTheme, onChange, setThemeMode, and
setTheme as references.
- Around line 2-20: ThemeProvider’s mount reconciliation persists the fallback
“system” value and prevents backend brand themes from being applied. Update the
theme handling around useIsomorphicLayoutEffect and useCunninghamTheme so a
missing stored preference applies “system” only in memory without localStorage
persistence, using an internal non-persisting applyThemeMode path; continue
using the persisting setter when getStoredThemeMode() returns an explicit value.

In `@src/frontend/apps/impress/src/pages/_document.tsx`:
- Around line 11-22: Replace the hardcoded 'doc-theme-mode' in THEME_INIT_SCRIPT
with an interpolation of the shared THEME_MODE_STORAGE_KEY constant imported
from useCunninghamTheme.tsx, ensuring the script and hook remain synchronized if
the storage key changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 62d5b6bc-37f0-43ed-8631-3b8edddd06ed

📥 Commits

Reviewing files that changed from the base of the PR and between 57ad4f4 and be17af8.

📒 Files selected for processing (10)
  • CHANGELOG.md
  • src/frontend/apps/impress/src/components/ThemeSwitch.tsx
  • src/frontend/apps/impress/src/components/index.ts
  • src/frontend/apps/impress/src/core/config/ConfigProvider.tsx
  • src/frontend/apps/impress/src/core/config/ThemeProvider.tsx
  • src/frontend/apps/impress/src/cunningham/__tests__/useCunninghamTheme.spec.tsx
  • src/frontend/apps/impress/src/cunningham/useCunninghamTheme.tsx
  • src/frontend/apps/impress/src/features/home/components/HomeHeader.tsx
  • src/frontend/apps/impress/src/features/left-panel/components/LeftPanelFooter.tsx
  • src/frontend/apps/impress/src/pages/_document.tsx

Comment thread src/frontend/apps/impress/src/components/ThemeSwitch.tsx
Comment thread src/frontend/apps/impress/src/core/config/ThemeProvider.tsx Outdated
Comment thread src/frontend/apps/impress/src/core/config/ThemeProvider.tsx Outdated
Comment thread src/frontend/apps/impress/src/pages/_document.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

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

Inline comments:
In `@src/frontend/apps/impress/src/features/docs/doc-editor/styles.tsx`:
- Around line 25-33: Reformat the explanatory comment above the `.bn-editor`
rule so the referenced selector `.bn-root.bn-editor` remains intact on one line
or is broken clearly without splitting hyphenated class names.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fa655852-54b1-44ff-b796-f73188c09476

📥 Commits

Reviewing files that changed from the base of the PR and between 80846f4 and 77dc653.

📒 Files selected for processing (2)
  • src/frontend/apps/impress/src/components/dropdown-menu/DropdownMenu.tsx
  • src/frontend/apps/impress/src/features/docs/doc-editor/styles.tsx

Comment thread src/frontend/apps/impress/src/features/docs/doc-editor/styles.tsx Outdated
@MashdorDev
MashdorDev force-pushed the feat/theme-color-mode branch from 77dc653 to 99b9fd5 Compare July 10, 2026 23:49
@lucletoffe

Copy link
Copy Markdown

In case it helps unblock this: the branch no longer merges cleanly onto main, so I rebased the four commits onto 61c2183 with @MashdorDev's authorship preserved, and pushed the result here:

https://github.com/lucletoffe/docs/tree/darkmode-rebase-main

The only conflict was CHANGELOG.md — the ### Added section had moved on since this PR was opened. Resolved by keeping both sets of entries. No code changes were needed on top of the original work. yarn vitest run is green on the rebased branch (39 files / 264 tests).

One data point that might be useful: we are running a build of these commits on v5.4.1 (which is exactly where this branch is based — git merge-base returns the v5.4.1 tag) on a self-hosted instance. Verified by headless render of an impress-served route in both modes: html.cunningham-theme--default with a #FFFFFF dominant, html.cunningham-theme--dark with a #2F3033 dominant. The switcher, the localStorage persistence and the pre-hydration inline script all behave as described.

Two cosmetic things we noticed, neither introduced by this PR:

  • the brand ramp differs between the two themes (indigo in light, blue in dark), because the dark theme in cunningham-tokens.ts carries its own brand colours;
  • the DINUM illustrations (401 page and friends) keep a white background in dark mode, since the assets are not transparent.

Feel free to take the rebase or ignore it — just wanted the conflict not to be the thing keeping this stalled.

Adds a user-facing colour-mode preference on top of the existing Cunningham
dark token set:

- useCunninghamTheme: themeMode state (light/dark/system) + setThemeMode,
  persisted to localStorage; system resolves via prefers-color-scheme.
- ThemeProvider: applies the stored mode on mount and follows live OS
  changes while in system mode.
- _document: pre-hydration inline script sets the theme class on <html>
  to avoid a flash of the wrong theme; suppressHydrationWarning on <html>.
- ConfigProvider: user mode preference takes precedence over the plain
  light/dark FRONTEND_THEME default (brand themes still apply).
- ThemeSwitch component in the left-panel footer and the home header.
- Unit tests for the theme-mode store logic.

Signed-off-by: MashdorDev <dorzairi@ymail.com>
- Drive the BlockNote editor (editable + reader) theme from the resolved
  Cunningham theme so the document canvas is dark in dark mode instead of
  staying light with unreadable text.
- Fade the doc floating bar to the surface background token instead of solid
  white (the white blur band under the header in dark mode).
- ThemeProvider: resolve the default 'system' in memory (new non-persisting
  applyThemeMode) so an unset preference no longer masks a backend brand
  theme, and skip the OS-change listener when a brand theme is active.
- ThemeSwitch: use radio semantics (drop selectedValues), remove the
  redundant inner aria-label.
- _document: reference the shared THEME_MODE_STORAGE_KEY constant.
- Add a test for applyThemeMode not persisting.

Signed-off-by: MashdorDev <dorzairi@ymail.com>
The BlockNote editor used its own Mantine surface (distinct from the app
background) and the loading skeleton was hardcoded to gray-000 (white), so in
dark mode both stood out. Make the editor background transparent so it shares
the page background, and paint the skeleton with the surface token.

Signed-off-by: MashdorDev <dorzairi@ymail.com>
Setting the BlockNote CSS var lost to BlockNote's own
.bn-root[data-color-scheme=dark] rule, so the editor kept Mantine's dark
surface. Set the background directly on .bn-root .bn-editor (which outranks
BlockNote's .bn-editor rule) so the canvas shares the page background in any
theme. Also make the dropdown selected-check use the theme-aware neutral text
colour instead of a fixed gray that was invisible on the dark menu.

Signed-off-by: MashdorDev <dorzairi@ymail.com>
Nothing painted a background on html, body or the app wrapper, so the
browser default white showed through in dark mode while the text tokens
switched to the dark ramp. On the pre-login home page that left the
heading near-white on white.

Reproduced on a 5.4.1 build and on our own instance before this change.
@MashdorDev
MashdorDev force-pushed the feat/theme-color-mode branch from 99b9fd5 to d9be7bb Compare July 29, 2026 04:22
@MashdorDev

Copy link
Copy Markdown
Author

Thanks Luc. The branch is rebased onto 61c2183 and force-pushed, so it merges cleanly again. I resolved CHANGELOG.md the same way you did. I did the rebase independently first and diffed it against your branch: the trees came out identical.

Your check also caught something I had missed, so there is a fifth commit now. Nothing painted a background on html, body or the app wrapper, so the browser default white showed through while the text tokens switched to the dark ramp, leaving the home page heading near-white on white. body now takes --c--contextuals--background--surface--primary, which is gray-000 in default and gray-800 in dark. That gives the #2F3033 you measured.

Your other two points match what I see and neither comes from this PR: the brand ramp differs because the dark theme carries its own brand colours in cunningham-tokens.ts, and the DINUM illustrations keep a white plate because the assets are not transparent. The second one is more visible now that the canvas is actually dark, but fixing it needs new assets.

@virgile-dev the branch is current and CI is green if you want to take a look.

The chip was a hardcoded gainsboro, so in dark mode it stayed near-white
while the content tokens switched to the light ramp, leaving inline code
unreadable. Uses the gray tertiary background token instead: #d3d4e0 in
default (near-identical to the old value) and #686b6f in dark, which is
4.5:1 against the neutral primary text.
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.

2 participants