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
Copy Markdown
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
Copy Markdown

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 dvdksn added agent/generated status/review Pull requests that are ready for review labels Mar 25, 2026
@dvdksn dvdksn requested a review from a team March 25, 2026 10:03
@dvdksn
Copy link
Copy Markdown
Contributor Author

dvdksn commented Mar 25, 2026

/review

Copy link
Copy Markdown
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
Copy Markdown
Contributor Author

dvdksn commented Mar 25, 2026

/review

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
Contributor Author

dvdksn commented Mar 25, 2026

/review

Copy link
Copy Markdown
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

Labels

agent/generated status/review Pull requests that are ready for review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

dockerdocs page use auto light/dark theme?

1 participant