Thématisation
Sur cette page
Tous les composants lisent les mêmes propriétés personnalisées : un thème n’est donc qu’un jeu de surcharges sur :root — pas d’étape de build, pas de fichier de configuration, et aucun composant à mettre au courant.
Mettre les styles en place
styles() renvoie un <link> vers un seul fichier, que le navigateur met en cache sur toutes les pages du site. Rien à configurer, rien à copier : le plugin de sitelo le sert en dev et l’écrit dans le build, sur la même base que le runtime des composants.
import { styles } from 'sitelo/ui'
head(
title('Mon site'),
styles(),
)
// <link rel="stylesheet" href="/su/ui-c9428b65.css">Le nom porte une empreinte du contenu : vous pouvez donc le servir en immutable et livrer quand même une modification. Passez { hash: false } pour un simple /su/ui.css, ou base pour pointer le lien vers une copie que vous hébergez.
{ inline: true } met plutôt la feuille entière dans un <style> : environ 11 ko gzippés dans chaque page, mais aucune requête supplémentaire et rien qui puisse manquer dans dist/. C’est le meilleur compromis pour une page unique ; le lien rembourse sa requête dès la deuxième page lue.
head(
title('Mon site'),
styles({ inline: true }),
)Le reste de la famille vous donne les pièces. stylesheet() renvoie le CSS brut sous forme de chaîne — pour héberger la feuille là où sitelo n’a pas la main, ou l’écrire vous-même quelque part — et stylesUrl() l’URL seule, pour un élément link à vous.
Préréglages
Un préréglage change tout le rendu d’un coup. styles({ preset: 'neumorphism' }) lie une seconde feuille juste après la feuille principale — servie, hachée et mise en cache aux mêmes conditions — et chaque composant de la page la suit, sans rien changer au balisage.
import { styles } from 'sitelo/ui'
head(
title('Mon site'),
styles({ preset: 'neumorphism' }),
)
// <link rel="stylesheet" href="/su/ui-c9428b65.css">
// <link rel="stylesheet" href="/su/neumorphism-5d0e7b91.css">Chaque préréglage a sa propre page, où tous les composants sont restylés en direct : Neumorphisme, Néo-brutalisme, Superneon.
inline intègre les deux feuilles, stylesheet({ preset }) les renvoie en une seule chaîne, et un nom qui n’est pas un préréglage lève une erreur qui liste ceux qui existent.
Surcharger des jetons
theme() écrit les surcharges. Les clés sont des noms de jetons en camelCase, des objets de palette, ou des propriétés personnalisées littérales — et cela vient après styles(), donc cela l’emporte.
import { styles, theme } from 'sitelo/ui'
head(
styles(),
theme({
primary: { base: '#5b5bd6', hover: '#4a4ac4', active: '#3f3fb0', fg: '#ffffff' },
radiusMd: '2px',
fontSans: '"Inter", system-ui, sans-serif',
}),
)Thèmes délimités
Un selector limite les surcharges à un sous-arbre plutôt qu’à toute la page. C’est ce que font les trois panneaux ci-dessous — mêmes composants, trois palettes différentes, une seule page.
fragment(
theme({ primary: { base: '#5b5bd6', hover: '#4a4ac4', fg: '#ffffff', soft: '#e6e6fa', softFg: '#33338f', border: '#b9b9ee' } }, { selector: '.theme-indigo' }),
theme({ primary: { base: '#b0357a', hover: '#962e68', fg: '#ffffff', soft: '#fbe4f0', softFg: '#7d1f53', border: '#f0a9ce' } }, { selector: '.theme-pink' }),
theme({ radiusMd: '999px', radiusLg: '1.5rem' }, { selector: '.theme-round' }),
grid({ min: '11rem' },
div({ class: 'theme-indigo' },
card(cardBody(stack({ gap: 'sm' },
text({ variant: 'caption', tone: 'muted' }, 'indigo'),
button({ block: true }, 'Primary'),
button({ variant: 'soft', block: true }, 'Soft'),
))),
),
div({ class: 'theme-pink' },
card(cardBody(stack({ gap: 'sm' },
text({ variant: 'caption', tone: 'muted' }, 'rose'),
button({ block: true }, 'Primary'),
button({ variant: 'soft', block: true }, 'Soft'),
))),
),
div({ class: 'theme-round' },
card(cardBody(stack({ gap: 'sm' },
text({ variant: 'caption', tone: 'muted' }, 'arrondi'),
button({ block: true }, 'Primary'),
button({ variant: 'soft', block: true }, 'Soft'),
))),
),
),
)Mode sombre
Le sombre se résout tout seul depuis prefers-color-scheme. Un data-theme ou data-su-theme explicite valant light ou dark sur n’importe quel ancêtre l’emporte — c’est ainsi que les démos de ce site suivent la bascule de la barre du haut.
Passez dark pour des surcharges qui ne doivent s’appliquer que là. Cela couvre l’attribut et la media query d’un coup.
theme({
primary: { base: '#5b5bd6' },
}, {
dark: { primary: { base: '#8f8ff0' } },
})Ce qu’il y a à surcharger
Cinq palettes de neuf emplacements chacune, une échelle d’espacement, la typographie, les rayons, les ombres et les couleurs de surface. Chacun est une propriété personnalisée — ouvrez la feuille de style, ou l’inspecteur de votre navigateur, elles sont toutes sur :root.
stack({ gap: 'md' },
stack({ direction: 'row', gap: 'sm', wrap: true },
...['primary', 'neutral', 'success', 'warning', 'danger'].map((color) =>
stack({ gap: 'xs', align: 'center' },
div({ style: 'width: 3.5rem; height: 2rem; border-radius: 0.4rem; background: var(--su-' + color + ')' }),
text({ variant: 'caption', tone: 'muted' }, color),
),
),
),
stack({ direction: 'row', gap: 'sm', wrap: true, align: 'flex-end' },
...['xs', 'sm', 'md', 'lg', 'xl'].map((step) =>
stack({ gap: 'xs', align: 'center' },
div({ style: 'width: var(--su-space-' + step + '); height: 2rem; border-radius: 0.2rem; background: var(--su-neutral)' }),
text({ variant: 'caption', tone: 'muted' }, step),
),
),
),
)Nommage
Une clé en camelCase devient une propriété en kebab-case : radiusMd donne --su-radius-md, fontSans donne --su-font-sans. Un objet imbriqué se développe de la même façon — { primary: { softFg: … } } pose --su-primary-soft-fg — et une clé commençant déjà par -- est utilisée telle quelle, ce qui est la porte de sortie pour tout ce que la correspondance ne couvre pas.
Une palette a neuf emplacements : base, hover, active, fg, soft, softHover, softFg, border et ring. Ne définissez que ceux que vous changez.
Contraste
Les palettes livrées passent le AA des WCAG face aux surfaces sur lesquelles elles se posent, dans les deux thèmes, et un test du dépôt fait échouer le build si cela cesse d’être vrai. Un thème à vous n’est pas couvert par ce test — vérifiez votre fg face à votre base avant de le publier.
Props
styles() :
| Prop | Type | Défaut | Description |
|---|---|---|---|
preset | 'neumorphism' | 'neubrutalism' | 'superneon' | — | Restyle chaque composant avec un préréglage, lié ou intégré après la feuille principale. |
inline | boolean | false | Émet le CSS lui-même plutôt qu’un lien vers lui. |
hash | boolean | true | Ajoute au nom de fichier une empreinte du contenu. Lien uniquement. |
base | string | '/su/' | Pointe l’URL ailleurs ; cette copie est à vous d’héberger. Lien uniquement. |
minify | boolean | true | Retire commentaires et espaces. En ligne uniquement. |
nonce | string | — | Nonce CSP pour l’élément émis. |
stylesUrl() prend base et hash ; stylesheet() prend minify et preset.
theme(tokens, options) :
| Prop | Type | Défaut | Description |
|---|---|---|---|
selector | string | ':root' | Limite les surcharges à un sous-arbre. |
dark | object | — | Surcharges appliquées uniquement en mode sombre. |
nonce | string | — | Nonce CSP. |