Dark mode
A single class on <html> switches the whole site to dark mode, and the components never need to know.
shadwire.css ties Tailwind's dark variant to a class:
The dark variant applies when an ancestor has the
.dark class, so you switch themes by adding or removing that class on
<html>. The components only use tokens, and every token has both a
light and a dark value, so dark mode needs no code in the components.
The toggle
This five-line Stimulus controller is what switches the theme on this site:
And the button. The dark variant decides which of its two icons is shown:
dark:hidden and hidden dark:block choose which
element is visible, not its colour. That is a fine use of the dark:
prefix; using it for colours is not, as explained below.
Avoiding the flash
If the class is only added after the app's JavaScript loads, people who chose the dark
theme see a white flash on every page load. To avoid it, put an inline script in
<head>, before any stylesheet, so it runs before the page is drawn:
It checks the saved preference first and only then falls back to the system setting. That way someone can pick the light theme on a system set to dark, and the choice is remembered.
Content Security Policy
nonce: true on javascript_tag) or by moving it into a file
loaded in <head> ahead of the stylesheets.
Don't use dark: for colours
A hand-written dark colour repeats a decision the tokens already make, and it stops matching the theme as soon as someone changes the tokens:
If you need a colour the tokens don't cover, add a token. You give it a light and a dark value, and it keeps working when the theme changes.
Testing both modes
Check your screens in both modes. When something looks wrong in dark mode, the colour value itself is rarely the problem. More often two utilities with the same specificity are competing, or one of a pair of utilities is missing, so the class list looks right while the compiled CSS is not. Look at the generated CSS.