Skip to content

feat: simplify theme toggle to light/dark with current-view icon#83

Merged
jmeridth merged 1 commit into
mainfrom
feat/theme-toggle-light-dark-only
Jun 5, 2026
Merged

feat: simplify theme toggle to light/dark with current-view icon#83
jmeridth merged 1 commit into
mainfrom
feat/theme-toggle-light-dark-only

Conversation

@jmeridth

@jmeridth jmeridth commented Jun 5, 2026

Copy link
Copy Markdown
Member

What

Remove the three-state lightdarksystem cycle from the theme toggle. The button now flips between light and dark only, and the icon reflects the currently displayed view (moon when dark, sun when light) rather than the stored preference. Touched files: _includes/head.html (boot script no longer special-cases the string "system"), assets/js/theme.js (cycle dropped, updateButtons driven by current view, accessible labels describe the next action), and _includes/nav.html / _layouts/aggregate_default.html / _layouts/author_default.html (static fa-desktop fallback replaced with fa-moon-o).

Why

The system state was a third click in a cycle that most users would never need, and the desktop icon did not convey anything meaningful about the current page appearance. Showing the icon of the current view (and labelling the button with the action it performs) is more discoverable. The OS preference is still honored on first visit and whenever no explicit choice has been stored, so the "follow system" behavior is preserved without occupying a slot in the cycle.

Notes

  • Users with a legacy localStorage.theme === "system" value are treated as having no preference. Their next click cleanly overwrites the slot with "light" or "dark" — no migration script needed, no data wipe.
  • The static fa-moon-o in nav/layout markup is a JS-disabled fallback only. Once theme.js runs (synchronously after DOM ready), the icon is corrected to match the actual displayed theme on every page.
  • aria-label / title now describe the action ("Switch to light theme") rather than the state, since the icon already communicates state. Screen reader users get the verb; sighted users get the glyph.
  • _layouts/aggregate_default.html and _layouts/author_default.html still inline their own nav rather than using _includes/nav.html. Pre-existing duplication carried forward; unchanged by this PR.

Testing

Verified locally with bundle exec jekyll serve --livereload:

  • Icon matches current view on load: with OS in dark mode and no stored preference, page renders dark and the toggle shows the moon. Switch OS to light, refresh, page renders light and the toggle shows the sun.
  • Toggle flips and icon swaps: clicking the button flips data-theme on <html> and swaps the icon in the same render.
  • Persistence: after a click, localStorage.getItem('theme') returns "light" or "dark"; refresh keeps the chosen theme.
  • System default with no preference: localStorage.removeItem('theme') + refresh follows OS prefers-color-scheme and shows the matching icon.
  • Live OS change with no preference: with no stored preference, flipping OS dark/light updates the page and icon without a refresh (via the matchMedia change listener).
  • Legacy "system" value: localStorage.setItem('theme','system') + refresh behaves as if no preference is stored; next click cleanly overwrites with "light" or "dark".
  • Built output is clean: bundle exec jekyll build succeeds; grep of _site/ for fa-desktop, cycleTheme, and the literal string "system" in _site/assets/js/theme.js and _site/index.html returns nothing.

Screenshots

Dark

Screenshot 2026-06-05 at 16 35 15

Light

Screenshot 2026-06-05 at 16 35 20

## What

Remove the three-state `light` → `dark` → `system` cycle from the theme toggle. The button now flips between `light` and `dark` only, and the icon reflects the currently displayed view (moon when dark, sun when light) rather than the stored preference. Touched files: `_includes/head.html` (boot script no longer special-cases the string `"system"`), `assets/js/theme.js` (cycle dropped, `updateButtons` driven by current view, accessible labels describe the next action), and `_includes/nav.html` / `_layouts/aggregate_default.html` / `_layouts/author_default.html` (static `fa-desktop` fallback replaced with `fa-moon-o`).

## Why

The `system` state was a third click in a cycle that most users would never need, and the desktop icon did not convey anything meaningful about the current page appearance. Showing the icon of the current view (and labelling the button with the action it performs) is more discoverable. The OS preference is still honored on first visit and whenever no explicit choice has been stored, so the "follow system" behavior is preserved without occupying a slot in the cycle.

## Notes

- Users with a legacy `localStorage.theme === "system"` value are treated as having no preference. Their next click cleanly overwrites the slot with `"light"` or `"dark"` — no migration script needed, no data wipe.
- The static `fa-moon-o` in nav/layout markup is a JS-disabled fallback only. Once `theme.js` runs (synchronously after DOM ready), the icon is corrected to match the actual displayed theme on every page.
- `aria-label` / `title` now describe the action ("Switch to light theme") rather than the state, since the icon already communicates state. Screen reader users get the verb; sighted users get the glyph.
- `_layouts/aggregate_default.html` and `_layouts/author_default.html` still inline their own nav rather than using `_includes/nav.html`. Pre-existing duplication carried forward; unchanged by this PR.

Signed-off-by: jmeridth <jmeridth@gmail.com>
@jmeridth jmeridth self-assigned this Jun 5, 2026
@jmeridth jmeridth marked this pull request as ready for review June 5, 2026 15:01
@jmeridth jmeridth merged commit f473841 into main Jun 5, 2026
7 checks passed
@jmeridth jmeridth deleted the feat/theme-toggle-light-dark-only branch June 5, 2026 21:36
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.

1 participant