Teme și design tokens
@theme, tokens semantici, light/dark cu variabile CSS și toggle manual.
Actualizat
Ce este o temă
O temă e setul de decizii vizuale ale unui produs: culori, fonturi, raze, umbre, spațieri. Într-un proiect bine construit, aceste decizii sunt centralizate în design tokens — variabile cu nume — iar componentele folosesc doar token-urile.
Rezultatul: schimbi brandul, adaugi dark mode sau un al doilea brand dintr-un singur fișier, fără să atingi componentele.
Tokens, nu culori hardcodate
Greșeala clasică: bg-blue-600 împrăștiat prin 200 de fișiere. Schimbi brandul → schimbi 200 de fișiere.
Corect: definești design tokens semantice (ce rol are culoarea, nu ce nuanță e) și le folosești peste tot.
primitive: blue-600, stone-100 → paleta brută
semantic: accent, bg, surface, muted → ce folosește UI-ul
@theme — tokens care devin clase
@import "tailwindcss";
@theme {
--color-accent: #1f5fbf;
--font-display: "IBM Plex Sans", sans-serif;
--radius-card: 0.75rem;
}Fiecare variabilă din namespace-ul potrivit generează clase: --color-accent → bg-accent, text-accent, border-accent/50; --font-display → font-display; --radius-card → rounded-card.
| Namespace | Clase |
|---|---|
--color-* |
bg-*, text-*, border-*, fill-*… |
--font-* |
font-* |
--text-* |
text-* (mărimi) |
--spacing |
baza pentru p-*, m-*, gap-* |
--radius-* |
rounded-* |
--shadow-* |
shadow-* |
--breakpoint-* |
sm:, md:… |
Light / dark cu variabile CSS
Pattern-ul folosit chiar în aplicația asta:
:root {
--bg: #f7f6f3;
--fg: #1f1e1c;
--accent: #1f5fbf;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #141414;
--fg: #e9e7e2;
--accent: #6ea4f0;
}
}
@theme inline {
--color-bg: var(--bg);
--color-fg: var(--fg);
--color-accent: var(--accent);
}@theme inline = clasa folosește direct var(--bg), deci se schimbă când variabila se schimbă. Componentele scriu doar bg-bg text-fg — nu au nevoie de dark: deloc.
Toggle manual (buton de temă)
Implicit, dark: urmează sistemul. Pentru un buton care forțează tema:
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));
[data-theme=dark] {
--bg: #141414;
--fg: #e9e7e2;
}Pui data-theme="dark" pe <html> (salvat în cookie/localStorage, aplicat înainte de hidratare ca să nu clipească).
Light / dark / sistem, fără „flash”
Un toggle real are trei opțiuni: luminos, întunecat și „ca sistemul”. Problema clasică: pagina se randează pe server fără să știe alegerea userului → apare o fracțiune de secundă în tema greșită (flash), apoi JS-ul o corectează.
| Unde salvezi alegerea | Cum eviți flash-ul |
|---|---|
| cookie | serverul citește cookie-ul și pune direct data-theme pe <html> — zero flash (pagina devine dinamică) |
localStorage |
un script mic, inline în <head>, setează data-theme înainte să se deseneze pagina — ce face librăria next-themes |
/* „sistem” = fără atribut → urmează prefers-color-scheme */
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));
@media (prefers-color-scheme: dark) {
:root:not([data-theme=light]) { --bg: #141414; --fg: #e9e7e2; }
}
[data-theme=dark] { --bg: #141414; --fg: #e9e7e2; }Adaugă și color-scheme: light dark pe :root — scrollbar-urile și controalele native (input, select) se adaptează automat.
Mai multe teme / branduri
Același mecanism: [data-theme=ocean] { --accent: ... }. Componentele nu se schimbă deloc.
Reguli
- Componentele folosesc doar tokens semantici (
bg-surface,text-muted), nubg-stone-100. - Puține tokens, bine numite:
bg,surface,fg,muted,line,accent,success,danger. - Verifică contrastul în ambele teme.
Pe scurt
- Tokens semantici (
bg,surface,accent), nu culori brute în componente. @themecreează clase;@theme inline+ variabile CSS = teme care se schimbă la runtime.- Dark mode și branduri multiple = alte valori pentru aceleași variabile.