Skip to content

Add user-controlled dark/light mode toggle#65

Merged
calebl merged 11 commits intomainfrom
calebl/dark-light-mode-toggle
Feb 25, 2026
Merged

Add user-controlled dark/light mode toggle#65
calebl merged 11 commits intomainfrom
calebl/dark-light-mode-toggle

Conversation

@calebl
Copy link
Owner

@calebl calebl commented Feb 23, 2026

Summary

Adds a theme preference system allowing users to choose between light mode, dark mode, or following their system (OS) preference. Users can control this setting on their account settings page, and the theme is persisted in the database.

Changes

  • New theme column on users table (defaults to "system") with validation for light/dark/system values
  • Theme selector (radio buttons) added to account edit page
  • Stimulus controller manages dynamic theme switching, including real-time OS preference detection for system mode
  • All CSS media queries replaced with data-theme attribute selectors for consistent control

Testing

  1. Run migrations: bin/rails db:migrate
  2. Log in and navigate to Account settings
  3. Test Light mode toggle—page should use light theme regardless of OS setting
  4. Test Dark mode toggle—page should use dark theme regardless of OS setting
  5. Test System mode—page should follow OS preference and update in real-time when OS setting changes

🤖 Generated with Claude Code

calebl and others added 11 commits February 23, 2026 11:08
Adds a theme preference system allowing users to choose between light mode, dark mode, or system (OS) preference. New `theme` column on users table stores the preference, theme selector appears on account settings page, and a Stimulus controller manages dynamic theme switching. CSS media queries replaced with data-theme attribute selectors for consistent control.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Devise requires current_password for all updates, which causes a
re-render with 200 status that Turbo rejects. Custom registrations
controller skips password validation when only non-sensitive fields
like theme are being changed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The inline flash message script expects toastr as a global, but it
was only pinned in the importmap without being imported. This causes
a ReferenceError during Turbo page navigations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Turbo replaces the body on navigation but does not update html
element attributes. Moving data-theme and the Stimulus controller
to body ensures theme is applied after Turbo page transitions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous approach used <%= with a raw string containing quotes,
which ERB HTML-escaped to &quot;, causing the attribute value to
include literal quote characters. Now uses a simple attribute with
ERB value interpolation which avoids the escaping issue.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously the controller only set data-theme when the preference
was "system", relying on the server-rendered attribute for explicit
dark/light. Now it always applies the correct theme on connect,
making it resilient to Turbo body replacements or reconnections.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Stimulus controller on <body> wasn't connecting reliably. Replace
it with a small inline script that runs immediately when the body is
parsed, resolving "system" to the OS preference and listening for
real-time OS preference changes. For explicit dark/light, the
server-rendered data-theme attribute is used directly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The app has three layout files. The theme data-attribute and inline
script were only in application.html.erb. Add the same theme logic
to home.html.erb and devise.html.erb so dark/light mode works on
all pages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move the inline theme script to shared/_theme.html.erb and add a
theme_preference helper method. All three layouts now use the
partial and helper instead of duplicating the theme code.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a cycling theme toggle button (light/dark/system) to the home page
header, theme API endpoint, and supporting styles. Move theme preference
to a shared mutable variable (window.__themePref) so the matchMedia
listener stays in sync after client-side toggles without a page reload.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@calebl calebl merged commit 9647733 into main Feb 25, 2026
1 check passed
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