Point "system" at the themes actually named light and dark

2852f94 · Gabriel Morell · 2026-09-14 03:49

10 files +51 -20
Message
{commit_body(@commit)}

Files changed

modified assets/css/app.css
+22 −14
@@ -33,12 +33,20 @@
33 33 --color-base-200: oklch(25.26% 0.014 253.1);
34 34 --color-base-300: oklch(20.15% 0.012 254.09);
35 35 --color-base-content: oklch(97.807% 0.029 256.847);
36 − --color-primary: oklch(58% 0.233 277.117);
37 − --color-primary-content: oklch(96% 0.018 272.314);
38 − --color-secondary: oklch(58% 0.233 277.117);
39 − --color-secondary-content: oklch(96% 0.018 272.314);
40 − --color-accent: oklch(60% 0.25 292.717);
41 − --color-accent-content: oklch(96% 0.016 293.756);
36 + --color-primary: oklch(64% 0.076 19);
37 + --color-primary-content: oklch(10% 0.010 19);
38 + --color-secondary: oklch(58% 0.090 19);
39 + --color-secondary-content: oklch(98% 0.010 19);
40 + /* Dark rose, in the same hue family as primary/secondary (~15-19) rather
41 + than the violet daisyUI ships. This is what `text-accent` paints: the
42 + eyebrow over the headline, the feature icons and the step numbers. It
43 + has to stay light enough to read against base-100 at 30% lightness, so
44 + "dark rose" here is depth of hue and restraint of chroma, not a low
45 + lightness -- a genuinely dark rose would be illegible on this ground.
46 + Chroma sits above primary's 0.076 so an accent still reads as one.
47 + The content colour follows primary, which is the same lightness. */
48 + --color-accent: oklch(64% 0.135 15);
49 + --color-accent-content: oklch(10% 0.012 15);
42 50 --color-neutral: oklch(37% 0.044 257.287);
43 51 --color-neutral-content: oklch(98% 0.003 247.858);
44 52 --color-info: oklch(58% 0.158 241.966);
@@ -64,14 +72,14 @@
64 72 default: true;
65 73 prefersdark: false;
66 74 color-scheme: "light";
67 − --color-base-100: oklch(98% 0 0);
68 − --color-base-200: oklch(96% 0.001 286.375);
69 − --color-base-300: oklch(92% 0.004 286.32);
70 − --color-base-content: oklch(21% 0.006 285.885);
71 − --color-primary: oklch(70% 0.213 47.604);
72 − --color-primary-content: oklch(98% 0.016 73.684);
73 − --color-secondary: oklch(55% 0.027 264.364);
74 − --color-secondary-content: oklch(98% 0.002 247.839);
75 + --color-base-100: oklch(98% 0.008 24);
76 + --color-base-200: oklch(96% 0.014 24);
77 + --color-base-300: oklch(92% 0.022 24);
78 + --color-base-content: oklch(25% 0.050 24);
79 + --color-primary: oklch(64% 0.076 19);
80 + --color-primary-content: oklch(98% 0.010 19);
81 + --color-secondary: oklch(55% 0.060 19);
82 + --color-secondary-content: oklch(98% 0.010 19);
75 83 --color-accent: oklch(0% 0 0);
76 84 --color-accent-content: oklch(100% 0 0);
77 85 --color-neutral: oklch(44% 0.017 285.786);
modified lib/diogramos/themes.ex
+15 −0
@@ -20,6 +20,21 @@ defmodule Diogramos.Themes do
20 20 @spec default() :: theme()
21 21 def default, do: "afterdark"
22 22
23 + @doc """
24 + The pair "system" resolves to, following the OS preference.
25 +
26 + These are the two themes actually named `light` and `dark` — the ones
27 + daisyUI declares with a matching `color-scheme`, so form controls,
28 + scrollbars and the like are rendered by the browser to match. Any of the
29 + other themes can be chosen explicitly; only these two are picked
30 + automatically.
31 + """
32 + @spec system_light() :: theme()
33 + def system_light, do: "light"
34 +
35 + @spec system_dark() :: theme()
36 + def system_dark, do: "dark"
37 +
23 38 @spec valid?(theme()) :: boolean()
24 39 def valid?(theme) when is_binary(theme), do: theme in all() or theme == "system"
25 40 def valid?(_), do: false
modified lib/diogramos_web/components/layouts/root.html.heex
+14 −6
@@ -1,5 +1,9 @@
1 1 <!DOCTYPE html>
2 −<html lang="en">
2 +<html
3 + lang="en"
4 + data-system-light={Diogramos.Themes.system_light()}
5 + data-system-dark={Diogramos.Themes.system_dark()}
6 +>
3 7 <head>
4 8 <meta charset="utf-8" />
5 9 <meta name="viewport" content="width=device-width, initial-scale=1" />
@@ -12,20 +16,24 @@
12 16 </script>
13 17 <script>
14 18 (() => {
15 − const SYSTEM_DARK = "blueprint";
16 − const SYSTEM_LIGHT = "blueprint";
19 + // Read off <html> rather than written here: HEEx does not
20 + // interpolate inside a <script>, it emits the expression verbatim.
21 + // Diogramos.Themes is the one place the pair is named.
22 + const root = document.documentElement;
23 + const SYSTEM_DARK = root.dataset.systemDark;
24 + const SYSTEM_LIGHT = root.dataset.systemLight;
17 25 const mq = window.matchMedia("(prefers-color-scheme: dark)");
18 26 const systemTheme = () => mq.matches ? SYSTEM_DARK : SYSTEM_LIGHT;
19 27 const setTheme = (theme) => {
20 28 if (theme === "system") {
21 29 localStorage.removeItem("phx:theme");
22 − document.documentElement.setAttribute("data-theme", systemTheme());
30 + root.setAttribute("data-theme", systemTheme());
23 31 } else {
24 32 localStorage.setItem("phx:theme", theme);
25 − document.documentElement.setAttribute("data-theme", theme);
33 + root.setAttribute("data-theme", theme);
26 34 }
27 35 };
28 − if (!document.documentElement.hasAttribute("data-theme")) {
36 + if (!root.hasAttribute("data-theme")) {
29 37 setTheme(localStorage.getItem("phx:theme") || "system");
30 38 }
31 39 mq.addEventListener("change", () => {

Parents: 704d6eb