Themes
Iris CSS follows the system light or dark setting. data-theme forces either one, on the page or on any element.
Every theme color is a light-dark() pair, and :root
sets color-scheme: light dark. The browser picks the side that
matches the user's setting, so dark mode needs no extra CSS. Add the
color-scheme meta tag too, so the page background is right
before the stylesheet loads.
<meta name="color-scheme" content="light dark">
Force a theme
Set data-theme on <html> to choose the theme for the whole page.
<html lang="en" data-theme="dark">
Theme one element
data-theme works on any element. The element repaints its text and
background, and everything inside follows, nested themes included. The
.light and .dark classes do the same.
Light
Always light.
Dark
Always dark.
Theme toggle
A toggle only has to set data-theme and remember it. Read the saved
value in the <head>, before the page paints, so it does not
flash the wrong theme. This site uses the same script.
<script>
try {
const theme = localStorage.getItem("theme");
if (theme) document.documentElement.dataset.theme = theme;
} catch {}
</script>
<button type="button" id="theme-toggle">Toggle theme</button>
<script>
document.getElementById("theme-toggle").addEventListener("click", () => {
const root = document.documentElement;
const dark = root.dataset.theme
? root.dataset.theme === "dark"
: matchMedia("(prefers-color-scheme: dark)").matches;
root.dataset.theme = dark ? "light" : "dark";
localStorage.setItem("theme", root.dataset.theme);
});
</script>
Your own theme colors
Override a theme color with your own light-dark() pair. A single
color, without light-dark(), is used in both themes. A color set
on :root reaches nested themes too. Browsers without
light-dark() (Chrome before 123, Firefox before 120, Safari before
17.5) recompute colors on each nested theme, so there set it on
:root, [data-theme], .light, .dark to cover them.
:root {
--i-color-background: light-dark(#fffdf7, #101418);
--i-color-primary: light-dark(#6d28d9, #a78bfa);
--i-color-primary-inverse: light-dark(#fff, #1e1b4b);
}
Each color family is a color and an -inverse color, the text that
sits on top of it. Hover, border and focus colors are mixed from these two, so
you never set them one by one. See colors
for the classes that use them.
Variables
Theme colors
| Variable | Default |
|---|---|
--i-color-textDefault colors | light-dark(#373c44, #c2c7d0) |
--i-color-background | light-dark(var(--i-color-white), #13171f) |
--i-color-lightLight & dark colors | var(--i-color-white) |
--i-color-dark | var(--i-color-black) |
--i-color-brandBrand colors. The text ones reach 4.5:1 on the page and on tinted bands, such as the footer, in both themes. | light-dark(#b31fc6, #e12afb) |
--i-color-brand-dark | #a800b7 |
--i-color-brand-light | #f4a8ff |
--i-color-brand-name | light-dark(var(--i-color-brand-dark), #e47ef0) |
--i-color-brand-accent | light-dark(#b31fc6, var(--i-color-brand-light)) |
--i-color-inkMix targets. Hovers and colored text mix toward ink, tints mix toward paper | light-dark(var(--i-color-black), var(--i-color-white)) |
--i-color-paper | light-dark(var(--i-color-white), #13171f) |
--i-color-baseNeutral color of buttons and links without a color class | light-dark(#8e9fbc, #c2c7d0) |
--i-color-focus | color-mix(in oklab, var(--i-color-base), var(--i-color-ink) 40%) |
--i-color-primaryEach family is a color and the text color used on top of it. Hover, border, focus and tint colors are derived where they are used. | light-dark(#00d5be, #05cc47) |
--i-color-primary-inverse | var(--i-color-slate-900) |
--i-color-secondary | light-dark(#5d6b89, #525f7a) |
--i-color-secondary-inverse | var(--i-color-white) |
--i-color-contrast | light-dark(#1c2840, #eff1f4) |
--i-color-contrast-inverse | light-dark(var(--i-color-white), var(--i-color-black)) |
--i-color-accent | #ffb900 |
--i-color-accent-inverse | var(--i-color-slate-900) |
--i-color-info | var(--i-color-cyan) |
--i-color-info-inverse | var(--i-color-slate-900) |
--i-color-success | var(--i-color-green) |
--i-color-success-inverse | var(--i-color-slate-900) |
--i-color-warning | var(--i-color-yellow) |
--i-color-warning-inverse | var(--i-color-slate-900) |
--i-color-danger | var(--i-color-red-600) |
--i-color-danger-inverse | var(--i-color-white) |
--i-color-link | var(--i-color-blue-600) |
--i-color-link-inverse | var(--i-color-white) |
--i-variantCurrent color family. The color classes set it, buttons and links read it | var(--i-color-base) |
--i-variant-inverse | var(--i-color-paper) |
--i-color-subtleSubtle colors | light-dark(#646b79, #848d9e) |
--i-color-subtle-border | light-dark(#e7eaf0, #202632) |
--i-color-shadowBox shadow, built from one color so it follows the theme | light-dark(#8191b5, #07090c) |
--i-shadow | 0.0145rem 0.029rem 0.174rem color-mix(in srgb, var(--i-color-shadow) 1.6%, transparent), 0.0335rem 0.067rem 0.402rem color-mix(in srgb, var(--i-color-shadow) 2.4%, transparent), 0.0625rem 0.125rem 0.75rem color-mix(in srgb, var(--i-color-shadow) 3.1%, transparent), 0.1125rem 0.225rem 1.35rem color-mix(in srgb, var(--i-color-shadow) 3.5%, transparent), 0.2085rem 0.417rem 2.502rem color-mix(in srgb, var(--i-color-shadow) 4.3%, transparent), 0.5rem 1rem 6rem color-mix(in srgb, var(--i-color-shadow) 5.9%, transparent), 0 0 0 0.0625rem color-mix(in srgb, var(--i-color-shadow) 1.6%, transparent) |
Selection and scrollbar
| Variable | Default |
|---|---|
--i-selection-color-background | light-dark(#05cc4740, #05cc4730) |
--i-scrollbar-color-thumbScrollbar | color-mix(in oklab, var(--i-color-subtle) 50%, transparent) |
--i-scrollbar-color-track | transparent |