tailwind2-2 Exercise 1: One Component, Four Themes ============================================================ Setup (once): a folder with Tailwind installed (npm install tailwindcss @tailwindcss/cli). Put these two files in it, then run the commands at the end. --- theme.css --- @import "tailwindcss" source(none); @source "./tpl.html"; @theme inline { --color-surface: var(--surface); --color-ink: var(--ink); --color-muted: var(--muted); --color-line: var(--line); --color-primary: var(--primary); --color-primary-ink: var(--primary-ink); } :root { --surface: oklch(0.99 0 0); --ink: oklch(0.25 0.02 260); --muted: oklch(0.5 0.02 260); --line: oklch(0.9 0.01 260); --primary: oklch(0.5 0.2 260); --primary-ink: oklch(1 0 0); } [data-mode="dark"] { --surface: oklch(0.2 0.02 260); --ink: oklch(0.95 0.01 260); --muted: oklch(0.7 0.02 260); --line: oklch(0.35 0.02 260); --primary: oklch(0.7 0.15 260); --primary-ink: oklch(0.2 0.02 260); } [data-brand="sunset"] { --primary: oklch(0.55 0.19 40); } [data-mode="dark"][data-brand="sunset"] { --primary: oklch(0.75 0.15 50); } --- tpl.html (the same button and text block in four contexts; the script reads the colours and works out WCAG contrast ratios) ---
from a page
page() { "$CH" --headless=new --disable-gpu --dump-dom "file:///$1" | sed -n '//,/<\/pre>/p' | sed 's///; s/<\/pre>//'; }
npx @tailwindcss/cli -i theme.css -o theme.out.css
sed "s/OUT/theme.out.css/" tpl.html > page.html
page "C:/path/to/your/folder/page.html"
Output:
t1 light/default surface #fcfcfc ink #1c222b (15.59) muted (5.82) primary #0559d2 on-primary (6.24)
t2 dark/default surface #11161f ink #ebeff5 (15.71) muted (6.79) primary #659dfb on-primary (6.71)
t3 light/sunset surface #fcfcfc ink #1c222b (15.59) muted (5.82) primary #c73800 on-primary (5.26)
t4 dark/sunset surface #11161f ink #ebeff5 (15.71) muted (6.79) primary #f88f4f on-primary (7.80)
PART 2: BREAK IT ON PURPOSE
---------------------------
Change one line in theme.css (the light-mode sunset primary, lightness 0.55 -> 0.7):
[data-brand="sunset"] { --primary: oklch(0.7 0.19 40); }
Build and read again. Only the light/sunset row changes:
t1 light/default surface #fcfcfc ink #1c222b (15.59) muted (5.82) primary #0559d2 on-primary (6.24)
t2 dark/default surface #11161f ink #ebeff5 (15.71) muted (6.79) primary #659dfb on-primary (6.71)
t3 light/sunset surface #fcfcfc ink #1c222b (15.59) muted (5.82) primary #fc6b33 on-primary (2.88)
t4 dark/sunset surface #11161f ink #ebeff5 (15.71) muted (6.79) primary #f88f4f on-primary (7.80)
The text on the primary button now has a contrast of 2.88, well below the 4.5 AA minimum for normal text, and the script reports it without anyone having to look at the page. A lighter
orange looks pleasant but white text on it is hard to read; the numbers make that visible. In a real project you would turn the last column into a pass/fail check.
WHY THIS WORKS AS AN ANSWER
---------------------------
The markup has no dark: classes and no brand names. Every element uses meaning (bg-surface, text-ink, bg-primary, text-primary-ink), and the four contexts differ only in two attributes. The tokens in
@theme inline point at plain variables (--surface, --ink, --primary), and each attribute selector re-defines those variables, so the same class gives a different colour in each context. The two axes are
independent: data-mode picks light or dark, data-brand picks the accent. The one combined rule [data-mode="dark"][data-brand="sunset"] exists because the sunset orange that is readable on a pale page
(oklch 0.55) is too dark on a dark page, so the dark version is lighter (oklch 0.75). That is exactly what the contrast column proves: every pair is above 4.5, the WCAG AA minimum for normal text
(lowest: the muted text on the light surface at 5.82 and the sunset button at 5.26). Check the numbers yourself by changing --primary in one block and re-running the page.