Skip to content

docs: add auto/system theme option to theme toggle#24519

Open
dvdksn wants to merge 6 commits intodocker:mainfrom
dvdksn:fix/23177-auto-theme-toggle
Open

docs: add auto/system theme option to theme toggle#24519
dvdksn wants to merge 6 commits intodocker:mainfrom
dvdksn:fix/23177-auto-theme-toggle

Conversation

@dvdksn
Copy link
Contributor

@dvdksn dvdksn commented Mar 25, 2026

🤖 Generated with Claude Code

Summary

theme.js unconditionally wrote the resolved preference to localStorage on every page load, permanently locking in a concrete "light" or "dark" value even for first-time visitors — making it impossible to track OS preference changes after any site visit. Removed the unconditional localStorage.setItem from theme.js so the early script only reads (never writes); the Alpine.js toggle in header.html now cycles through three states (light → dark → auto), with "auto" removing the theme-preference key and resolving from prefers-color-scheme with a live matchMedia change listener so the theme updates immediately when the OS preference changes. A contrast icon indicates auto mode; all three icon spans are driven by Alpine x-show directives rather than pure CSS dark-mode classes.

Verified: logic matches the standard three-state pattern; prefers-color-scheme media query and matchMedia change listener are the canonical browser APIs for system-preference tracking.
Checked: no other files reference theme-preference or the theme toggle; no CSS changes required.

Closes #23177

On every page load, theme.js was unconditionally writing the resolved
preference to localStorage, permanently overriding the OS dark-mode
setting after the first visit. Removed the unconditional write and
dead commented-out code from theme.js; the script now only reads and
applies the class. The header Alpine component now manages the full
lifecycle with a three-state cycle (light -> dark -> auto): in auto
mode it removes the localStorage key, resolves from prefers-color-scheme,
and listens for live system preference changes via matchMedia.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@netlify
Copy link

netlify bot commented Mar 25, 2026

Deploy Preview for docsdocker ready!

Name Link
🔨 Latest commit 382160d
🔍 Latest deploy log https://app.netlify.com/projects/docsdocker/deploys/69c3cceecef2c800089ba1e4
😎 Deploy Preview https://deploy-preview-24519--docsdocker.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@dvdksn
Copy link
Contributor Author

dvdksn commented Mar 25, 2026

/review

Copy link
Contributor

@docker-agent docker-agent bot left a comment

Choose a reason for hiding this comment

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

Assessment: 🔴 CRITICAL

This PR adds a three-state theme toggle (light/dark/auto) but has a critical build-blocking issue and several robustness concerns.

- Use icons/contrast.svg (not "contrast") to avoid build failure — the
  short name resolves to contrast-fill.svg which doesn't exist
- Remove eager applyTheme(theme) call on init to prevent double-apply
  and potential flicker (theme.js already set the class on page load)
- Store mql change listener in variable and return cleanup function from
  x-init so Alpine can remove it if the component is destroyed
- Add hidden attribute to moon and contrast spans as no-JS fallback so
  only the sun icon shows when Alpine fails to initialise

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@dvdksn
Copy link
Contributor Author

dvdksn commented Mar 25, 2026

/review

Copy link
Contributor

@docker-agent docker-agent bot left a comment

Choose a reason for hiding this comment

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

Assessment: 🟢 APPROVE

This PR successfully implements a three-state theme toggle (light → dark → auto) for the Docker documentation site. The changes properly handle:

  • Theme initialization: The early theme.js script correctly reads from localStorage and applies the theme before page render, preventing flash of unstyled content
  • Three-state cycling: The Alpine.js toggle cycles through light → dark → auto as intended
  • System preference tracking: The matchMedia listener properly updates the theme when the OS preference changes while in auto mode
  • Icon display: Uses Alpine.js x-show directives to display the appropriate icon (sun/moon/contrast) for each state

The implementation follows standard patterns for theme management and properly integrates with Alpine.js reactivity. No bugs or issues were found in the changed code.

@docker-agent
Copy link
Contributor

docker-agent bot commented Mar 25, 2026

⏭️ Review skipped — another review for this PR is already in progress (started 1182s ago). Only one review runs at a time to avoid duplicate comments. The in-progress review will post its results when complete.

dvdksn and others added 2 commits March 25, 2026 11:30
Reverts the icon path change from the previous commit. contrast-fill.svg
does exist at node_modules/@material-symbols/svg-400/rounded/contrast-fill.svg
and is correctly mounted to assets/icons/ by Hugo. The short name "contrast"
resolves to icons/contrast-fill.svg as intended. The docker-agent review
comment was incorrect.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Alpine x-show toggles inline style="display:none" but does not remove
the HTML hidden attribute. With hidden present, the browser stylesheet's
display:none from [hidden] persists even when x-show evaluates to true,
so the moon and contrast icons never become visible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@dvdksn
Copy link
Contributor Author

dvdksn commented Mar 25, 2026

/review

Copy link
Contributor

@docker-agent docker-agent bot left a comment

Choose a reason for hiding this comment

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

Assessment: 🔴 CRITICAL

Found 2 issues in the theme toggle implementation:

  • 1 HIGH severity (build-breaking)
  • 1 MEDIUM severity (initialization timing)

dvdksn and others added 2 commits March 25, 2026 12:34
Before Alpine initializes, all three icon spans (sun/moon/contrast) were
visible simultaneously because x-show only manages style="display:none"
and has no effect before Alpine runs.

The site already defines [x-cloak=""] { display: none !important } in
global.css and uses x-cloak throughout (sidebar, gordon-chat, dropdowns).
Adding x-cloak to all three spans hides them via CSS before Alpine loads;
Alpine removes x-cloak during initialization after evaluating x-show, so
only the correct icon becomes visible — no flash.

This replaces the earlier attempt to use the HTML hidden attribute, which
is incompatible with x-show: Alpine removes style="display:none" when
x-show is true, but never removes the hidden attribute, so the UA
stylesheet's [hidden]{display:none} kept the icons permanently hidden.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
x-cloak hid all three icons until Alpine initialized, causing a visible
delay before the correct icon appeared. The original two-state toggle had
no such issue because icon visibility was pure CSS (dark:hidden /
dark:block) — theme.js sets the root class synchronously in <head> before
first paint, so the right icon was always visible immediately.

Fix: extend that same synchronous mechanism to three states. theme.js now
also sets data-theme-preference on <html> (alongside the existing
className set). CSS rules in global.css use that attribute to hide the
two wrong icons before Alpine loads. x-show still handles all post-click
updates (Alpine is already running by then, so no delay). applyTheme()
keeps the data attribute in sync on every state change.

Remove x-cloak from all three spans — it is no longer needed, and was
the source of the jank.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

dockerdocs page use auto light/dark theme?

1 participant