Design system
Palette, typographie, espace, formes, composants et thème sombre, la référence visuelle du produit.
Référence visuelle du produit. Décrit l'apparence, le ressenti et les règles visuelles, indépendamment de la technologie qui les implémente.
Philosophie
LRVO est un outil de travail, pas un produit marketing. L'interface disparaît pour laisser place au contenu.
Un seul principe directeur : ne jamais ajouter un élément sans raison. Chaque couleur, bordure, espace ou texte supplémentaire doit justifier sa présence. Le doute se résout toujours en faveur de la suppression.
Palette
Base
Par défaut, le produit est noir et blanc. Pas de teinte bleue ou violette dans les fonds. Le fond est blanc pur, le texte presque noir.
Un projet peut poser sa marque : une couleur d'action, une teinte de gris, une barre foncée. Elle passe par le thème du projet, jamais par une couleur écrite dans un composant. Les règles de sobriété qui suivent valent quel que soit le thème.
Jetons
Un composant n'écrit jamais une nuance de palette, seulement un rôle. text-fg-muted, pas text-neutral-500 dark:text-neutral-400. Le jeton porte à la fois le clair, le sombre et la marque du projet : une nuance écrite en dur reste noire quand le projet passe au bleu, et oublie le sombre une fois sur deux.
| Rôle | Jetons | Usage |
|---|---|---|
| Fonds | canvas, surface, surface-muted, surface-hover | Fond de page, cartes et champs, puces, survol d'une ligne ou d'une carte. |
| Texte | fg, fg-secondary, fg-muted, fg-subtle | Principal, secondaire, discret, très discret. |
| Lignes | line, line-strong | Séparations, bordures appuyées. |
| Contrôles | control, control-hover, control-line | Bouton secondaire, champ de saisie, ligne cliquable dans une carte. |
| Action | primary, primary-hover, primary-fg | Bouton principal, élément sélectionné, barre de progression. |
| Accent | accent, accent-soft, accent-line, accent-fg | Liens, cases cochées, sélection, cible de dépôt. |
| Barre et sidebar | chrome, chrome-fg, chrome-fg-muted, chrome-line, chrome-hover | Coquille de l'admin uniquement. |
| Statuts | danger, success, warning, info, chacun avec -soft, -line, -fg | Voir ci-dessous. |
Les jetons control existent parce qu'un contrôle secondaire ne se comporte pas pareil dans les deux modes. En clair, il est transparent et sa bordure suffit à le dessiner. En sombre, une bordure seule ne le détache pas d'une carte presque noire : il prend un fond gris translucide. Aucun jeton de surface n'a ce comportement, d'où ce trio à part.
primary et fg se ressemblent en noir et blanc, et c'est le piège. fg est l'encre : du texte, un filet noir. primary est l'action : ce qui devient bleu quand la marque est bleue. Un titre en primary passerait à la couleur de marque, un bouton en fg resterait noir.
Le garde-fou npm run check:colors fait échouer le build du core dès qu'une nuance de palette, une couleur dark: ou une couleur arbitraire [#…] apparaît. Un module lance le même script sur son code : node ../core/scripts/check-colors.js dist. Les rares exceptions (couleurs catégorielles, couleurs choisies par l'utilisateur) sont déclarées une par une dans le script, avec leur raison.
Accents sémantiques
Quatre statuts, utilisés avec une grande parcimonie : pour les états, les alertes, les variations. Jamais comme couleur décorative, jamais en fond de section.
| Jeton | Couleur par défaut | Usage |
|---|---|---|
info | Bleu | Information |
success | Vert | Succès, actif, variation positive |
warning | Ambre | Avertissement, en attente, brouillon |
danger | Rouge | Erreur, danger, suppression, variation négative |
Chaque statut existe en trois déclinaisons : la couleur elle-même pour le texte, un fond pâle (-soft), une bordure légère (-line). Ces trois valeurs forment toujours un ensemble. On ne mélange pas les déclinaisons de statuts différents. Sur la couleur pleine, le texte prend -fg.
Données et catégories
Deux sortes de couleurs échappent aux jetons, et c'est voulu. Une couleur choisie par l'utilisateur est une donnée : la couleur d'un rôle, d'un service de réservation, un texte coloré dans l'éditeur. Elle s'affiche telle qu'elle a été saisie, en style inline. Une couleur catégorielle distingue des types entre eux, pas un état : les variantes purple, pink, orange de Badge, les icônes par type de fichier de Drive. La lier à la marque ferait se confondre deux catégories.
Ce qu'on n'utilise pas
- Dégradés.
- Couleurs de fond dans les sections, sauf survol et état actif.
- Une couleur de marque écrite en dur dans un composant.
- Transparence et flou, sauf l'overlay des modales.
Typographie
Police unique : IBM Plex Sans. Pour les codes, identifiants, montants et dates : IBM Plex Mono. Un projet peut changer ces deux familles avec l'option font du thème : les composants écrivent font-ui et font-code, jamais un nom de police.
| Rôle | Taille | Graisse | Notes |
|---|---|---|---|
| Titre de page | 20 px | 600 | |
| Titre de section | 14 à 20 px | 600 | |
| Corps de texte | 13 px | 400 | |
| Texte secondaire | 12 px | 400 | Tableaux, listes |
| Labels et légendes | 12 px | 400 à 500 | |
| En-têtes de colonnes | 11 px | 500 | Majuscules, interlettrage +0.04em |
| Codes, identifiants, montants | 11 à 13 px | 400 | IBM Plex Mono |
Taille minimale : 11 px. En dessous, on ne descend pas.
Règles d'écriture
- Sentence case partout. « Tableau de bord », pas « TABLEAU DE BORD ». Seule exception : les en-têtes de colonnes de tableau, en petites majuscules.
- Texte court et direct. Pas de phrase là où un mot suffit.
- Les montants, les dates ISO et les identifiants sont toujours en monospace.
Voir Rédaction pour la voix des textes.
Espace
Base : 4 px. Tout espacement est un multiple de 4.
| Valeur | Usage |
|---|---|
| 4 px | Espacement interne minimal, entre icône et texte |
| 8 px | Padding compact, écart entre éléments proches |
| 12 px | Padding interne des petits composants |
| 16 px | Padding standard des cartes et panneaux |
| 24 px | Espacement entre sections |
| 32 px | Padding latéral du contenu principal |
| 48 à 64 px | Espacement entre blocs majeurs |
La densité est moyenne : ni étouffante, ni aérée. Un admin s'utilise plusieurs heures par jour, la densité confortable prime sur l'esthétique aérée.
Formes et bordures
Rayons
| Contexte | Rayon |
|---|---|
| Badges, étiquettes, petits éléments | Complet |
| Boutons, champs de saisie | 8 px |
| Modales, panneaux flottants, tableaux, cartes | 12 px |
| Avatars, indicateurs ronds | Cercle |
12 px est le rayon standard. On ne mélange pas les rayons dans un même composant.
Les deux rayons principaux sont des jetons : rounded-control pour les boutons et les champs, rounded-card pour les cartes, modales et panneaux. L'option radius du thème les fait varier ensemble.
Bordures
Toutes les séparations sont des bordures de 1 px solid. Pas d'ombre sur les cartes et surfaces statiques, pas de ligne de 2 px ou plus.
Les ombres existent uniquement pour ce qui flotte au-dessus de l'interface : menus déroulants, modales, toasts. Elles restent discrètes, juste assez pour indiquer la profondeur.
Structure de l'admin
┌─────────────────────────────────────────────┐
│ Sidebar 230px │ Barre supérieure 48px │
│ ├─────────────────────────── │
│ Logo │ │
│ │ Zone de contenu │
│ Nav principale │ padding 28px / 32px │
│ │ │
│ ────────────── │ │
│ Paramètres │ │
└─────────────────────────────────────────────┘- Sidebar : fixe, 230 px, fond
chrome, séparée par une bordure droite. - Barre supérieure : fixe, 48 px, fond
chrome. Fil d'Ariane à gauche, emplacements des modules puis utilisateur à droite. - Contenu :
canvas, gris très pâle. Les cartes et tableaux sont ensurface, ce qui crée la séparation visuelle sans bordure de section. En thème sombre, le fond devient noir et les boîtes grises.
La barre et la sidebar portent les jetons chrome-*, distincts des surfaces. Par défaut elles prennent la surface en clair et le fond de page en sombre : en noir, la coquille se fond dans la page et seules les cartes se détachent. Avec chrome: 'dark', elles restent foncées quel que soit le mode, et le contenu garde ses surfaces claires.
Coquille topbar
Avec shell: 'topbar', la navigation quitte la sidebar pour la barre du haut, qui passe à 56 px. De gauche à droite : la marque, les sections de navigation, puis les emplacements des modules et le menu utilisateur. Le contenu prend toute la largeur.
┌───────────────────────────────────────────────────────────┐
│ Logo Tableau de bord Utilisateurs ▾ Projets 🔔 (JT) │
├───────────────────────────────────────────────────────────┤
│ │
│ Zone de contenu, padding 28px / 32px │
│ │
└───────────────────────────────────────────────────────────┘Une section à un seul item est un lien, une section à plusieurs items un menu déroulant : c'est la règle de l'accordéon de la sidebar, posée à l'horizontale. L'item actif prend le fond chrome-hover. Sous 1024 px, le bouton de menu ouvre le tiroir de la sidebar. Il n'y a pas de fil d'Ariane : le titre de la page, dans AdminHeader, suffit à situer l'utilisateur quand la navigation est visible en permanence.
Cette coquille convient à une application aux sections peu nombreuses, un portail client par exemple. Au-delà de six ou sept sections, la barre déborde : la sidebar reste le bon choix.
En-tête de page
AdminHeader sépare deux zones : à gauche ce que la page est, à droite ce qu'on fait avec.
- À gauche : le titre, le badge d'état, la description. Le badge se passe en prop
badgeet se pose à côté du titre. Un état n'est pas un contrôle : glissé dans le groupe d'actions, il casse l'alignement de la rangée et se lit comme un bouton mort. - À droite : le bouton retour puis les actions, toutes à la taille par défaut
md. Une seule taille par rangée. Mélangersmetmdcôte à côte donne trois hauteurs de boutons et c'est ce qui se voit en premier.
Trois contrôles maximum à droite, bouton retour compris : une action destructive, une action principale. Au-delà, les actions se regroupent dans un bouton split. Un bouton icône seule n'a pas sa place dans un en-tête : sans libellé à côté de voisins qui en ont, il ne se lit pas. Sa place est dans une ligne de tableau ou dans le menu du bouton split.
Pages publiques
Les pages servies aux visiteurs hors de l'admin, partage de fichiers, prise de rendez-vous, facture à payer, formulaire, passent toutes par le même cadre : PublicShell, exposé par @lrvo/core/shared/public. Une personne qui reçoit deux liens du même site ne doit pas avoir l'impression de changer de site en cours de route.
Deux dispositions. Un état qui tient en une phrase et au plus un champ, lien expiré, mot de passe demandé, lien fermé, prend une boîte centrée (layout="centered"), comme la page de connexion. Une vraie interface prend la page (layout="page").
Deux silhouettes d'en-tête
comfortable | bar | |
|---|---|---|
| Logo | 64 px, au-dessus du titre | 24 px, à gauche dans la barre |
| Titre | 30 à 36 px, 600 | 14 px, 600, centré dans la barre |
| Position | défile avec la page | collant en haut, bordure basse |
| Respiration au-dessus du contenu | 64 à 96 px | 32 à 48 px |
La règle : une page qu'on ouvre pour regarder prend comfortable, une page qu'on ouvre pour faire quelque chose prend bar. Un partage de fichiers et une facture sont des documents : le grand titre les annonce comme une page d'accueil annonce un site. Un assistant de réservation et un formulaire sont des outils : l'en-tête y est de la hauteur prise à la tâche, et un visiteur qui a déjà lu le titre une fois n'a pas besoin qu'il occupe le tiers de son écran.
En silhouette bar, le titre de la barre est le titre de la page : il n'y a pas de second titre en dessous. Un sous-titre, s'il y en a un, devient la première ligne de la colonne de contenu.
Une page en silhouette bar qui déplace elle même le défilement doit poser une marge de défilement d'au moins 80 px (scroll-mt-20) sur sa cible, sans quoi celle ci se gare sous la barre.
Marque et pied de page
Le logo vient de la configuration du site, décliné par thème. Sans logo configuré, rien n'est rendu : un nom posé là où l'œil attend une image se lit comme un logo qui n'a pas chargé. Le nom du site reste dans le pied de page, où il est à sa place, suivi des mentions propres au module : lien privé, numéro de facture, numéros de taxes.
Navigation de la sidebar
Trois zones verticales :
- Logo, hauteur 48 px, aligné sur la barre supérieure, séparé par une bordure basse.
- Navigation principale, défilante si nécessaire, padding 8 px autour des items.
- Items épinglés en bas, optionnels, séparés par une bordure haute, jamais dans la zone défilante.
Deux types d'items
- Lien direct : une section qui ne contient qu'un item s'affiche comme un lien simple portant le titre de la section, sans chevron. Un post type « Actualités » sans taxonomie se résume donc à un bouton « Actualités ».
- Section accordéon : une section à plusieurs items. Un bouton parent déploie ou replie les sous-items.
États
| État | Apparence |
|---|---|
| Inactif | Texte chrome-fg-muted. Fond transparent. |
| Survol | Fond chrome-hover. Texte chrome-fg. |
| Actif replié | Fond chrome-hover. Texte chrome-fg. Graisse 500. |
| Actif ouvert, parent | Pas de fond, texte chrome-fg, graisse 500. Le fond actif est sur le sous-item. |
| Sous-item actif | Fond chrome-hover. Texte chrome-fg. Graisse 500. |
Gabarit d'un item
Padding 7 px vertical et 10 px horizontal, rayon 8 px, icône 15 × 15 px à gauche avec un écart de 8 px, texte 13 px, transition 120 ms ease-out. Un badge numérique optionnel se place à droite : fond danger, texte danger-fg, 10 px, rayon 8 px.
Les sous-items n'ont pas d'icône et sont indentés de 25 px.
Accordéon
Le chevron de 12 × 12 px est à l'extrême droite du bouton parent. Il pivote de -90deg quand la section est repliée. L'animation du conteneur combine max-height et opacity, sur 120 ms ease-out.
Une section s'ouvre automatiquement au chargement si elle contient l'item actif. Toutes les autres sont repliées.
Mobile
Sous 1024 px, la sidebar sort du flux et se positionne en fixe sur toute la hauteur. Masquée par défaut, elle s'ouvre par un bouton externe, avec un overlay semi-transparent. Cliquer l'overlay ou naviguer la ferme. Au-delà de 1024 px, elle se referme automatiquement.
Boutons
| Variante | Apparence | Usage |
|---|---|---|
| Primaire | Fond primary, texte primary-fg | Action principale de la page |
| Secondaire | Fond control, bordure control-line, texte fg-secondary | Actions secondaires, annulation |
| Fantôme | Transparent, sans bordure, texte fg-secondary | Actions tertiaires |
| Fantôme plein | Identique, sans anneau de focus | Boutons très discrets dans une zone déjà interactive |
| Danger | Fond danger, texte danger-fg | Suppression, action destructive |
| Succès | Fond success, texte success-fg | Confirmation, validation |
| Avertissement | Fond warning, texte warning-fg | Alerte, action risquée |
dangerSoft | Fond danger-soft, bordure danger-line, texte danger | Suppression dans une liste ou un contexte secondaire |
successSoft | Fond success-soft, bordure success-line, texte success | Validation discrète |
warningSoft | Fond warning-soft, bordure warning-line, texte warning | Alerte secondaire |
Le bouton primaire est le premier endroit où la marque du projet apparaît : avec une couleur primary, il la prend, et le texte posé dessus passe au blanc ou au noir selon le contraste.
Les variantes pleines sont la règle pour les actions principales : un bouton doit dominer visuellement le badge à côté. Les variantes *Soft servent quand la même action est répétée, par exemple une icône de suppression sur chaque ligne d'un tableau, ou quand le bouton est secondaire à côté d'un autre déjà plein. Les badges de statut gardent leur traitement teinté propre : ne pas les confondre avec ces variantes.
Il ne doit jamais y avoir plus d'un bouton primaire par section ou en-tête de page.
Tailles
| Taille | Padding horizontal | Padding vertical | Texte |
|---|---|---|---|
sm | 10 px | 5 px | 12 px |
md | 12 px | 7 px | 13 px |
lg | 14 px | 9 px | 14 px |
La taille par défaut est md.
Règles communes
Rayon 8 px, transition 120 ms ease-out, anneau de focus fin de la couleur de la variante. Une icône peut se placer à gauche ou à droite du texte. Un bouton icône seule a un padding uniforme. L'état de chargement remplace le contenu par un indicateur et désactive l'interaction. L'état désactivé passe à 50 % d'opacité, curseur interdit.
Bouton split
Quand plusieurs actions partagent la même intention avec des intensités différentes (Déployer, Démarrer sans rebuild, Redéployer sans cache), on n'aligne pas trois boutons primaires : visuellement ils se ressemblent et l'utilisateur ne sait plus lequel choisir. On utilise SplitButton : un bouton principal à gauche, un chevron à droite qui ouvre un menu de variantes. Chaque item peut porter une description qui explicite la nuance. Son panneau est ancré dans un portail, donc il reste lisible dans une modale ou un tableau défilant.
Une action d'intention opposée (Arrêter face à Démarrer) ne va pas dans le menu. Elle reste un bouton séparé, généralement en variante danger. Le SplitButton regroupe des variantes du même verbe, pas son inverse.
C'est aussi la sortie quand un en-tête déborde : plusieurs façons de remettre la même ressource au client (Renvoyer, Copier le lien, Télécharger le PDF) tiennent dans un seul bouton au lieu d'occuper trois places. Il accepte une icon comme Button, ce qui lui permet de remplacer une action principale sans que la rangée change d'allure.
Quand l'action principale disparaît selon l'état de la ressource, c'est l'item suivant du menu qui prend sa place. On ne laisse pas un chevron au-dessus d'un bouton mort.
Cartes et panneaux
Fond blanc, bordure 1 px, rayon 12 px, padding interne de 16 à 20 px, pas d'ombre. L'en-tête interne est séparé du contenu par une bordure horizontale, et le pied d'actions de formulaire également.
Les pages d'authentification font exception sur deux points : carte isolée au centre de l'écran, elle prend un padding de 32 px, et son pied est une bande séparée qui touche les deux bords. Leurs champs ne portent pas d'astérisque, parce que tous sont requis. Voir Apparence des pages d'authentification.
Champs de sélection
Select:<select>HTML natif. À utiliser quand la liste tient à l'écran et ne dépasse pas une dizaine d'entrées, statuts, types, langues. Pas de recherche.Combobox: équivalent avec recherche. Trigger identique, champ de filtrage en tête, navigation au clavier. À utiliser dès que la liste devient longue, par exemple pour assigner un utilisateur à une ressource. Le filtre couvrelabeletvalue, et chaque option peut afficher unhintsecondaire grisé. Son panneau est ancré dans un portail, donc la liste passe par-dessus une modale au lieu d'être rognée par son corps défilant.
Les deux respectent les mêmes conventions de label, description et erreur qu'Input. Pas de composant enveloppant.
Boutons d'action des formulaires
Le placement dépend du contexte.
Formulaire pleine page : utiliser UnsavedChangesBar.
- La barre apparaît dès qu'une modification est détectée et disparaît au retour à l'état initial.
- Annuler est secondaire et ramène le formulaire à son état initial sans naviguer.
- Enregistrer est primaire et désactivé pendant l'enregistrement.
- Pas de bouton de fermeture : tant qu'il y a des modifications, l'utilisateur choisit explicitement.
- Le hook sous-jacent bloque toute navigation par lien tant qu'il reste des modifications, et fait se secouer la barre à chaque tentative.
- Après un enregistrement réussi, rester sur la page. Pour une création, rediriger vers la page d'édition de la ressource créée : l'utilisateur reste avec la ressource, pas dans le listing.
- Aucun bouton inline en sidebar ou en pied de carte.
Le détail de l'API et la gestion de la navigation interne non liée sont dans Composants et Hooks.
FloatingActionBar reste réservée aux actions d'une sélection. Elle apparaît dès le premier élément sélectionné quand la sélection est explicite, cases à cocher à l'appui : l'utilisateur qui vient de cocher attend une suite. Elle ne porte que les actions qui ont un sens sur un lot, le reste vivant dans le menu de la ligne.
Formulaire en modale : garder les boutons dans le pied de la modale. Une modale est déjà contextualisée, l'utilisateur ne peut pas la perdre, et sa croix sert d'annulation explicite.
Tableaux
- En-têtes de colonnes en 11 px, majuscules, couleur secondaire.
- Lignes à fond blanc, survol légèrement grisé.
- Bordure horizontale entre les lignes, pas sur la dernière.
- Données numériques, identifiants et dates en IBM Plex Mono.
- Actions à l'extrême droite, en boutons icône discrets.
- Au-delà de deux actions, ou dès qu'une est destructive, elles passent dans un
DropdownMenuplutôt que de s'aligner. Six icônes dans une cellule ne se lisent pas, et une suppression alignée avec les autres se clique par erreur. - Une liste où le clic droit est le geste attendu, un explorateur de fichiers par exemple, peut offrir le même menu au pointeur avec
ContextMenu. En plus du bouton à trois points, jamais à sa place : une action qui n'existe qu'au clic droit ne se découvre pas et ne s'atteint pas au toucher. Voir Composants. - Pas de fond alterné.
- Un rechargement ne vide pas le tableau. Les squelettes sont réservés au premier affichage.
- Une liste pleine page pose une hauteur plancher,
minHeightsurTable. Un tableau dont la hauteur suit le nombre de lignes se réduit à un bandeau dès que la liste est courte, et saute d'une page à l'autre. Les squelettes remplissent cette hauteur, l'état vide s'y centre, et les lignes gardent la leur. Voir Composants. - Une sélection se fait à la case à cocher, en tête de ligne, avec une case dans l'en-tête pour la page affichée. Le clic simple sert à ouvrir : une sélection qui ne s'annonce pas ne se découvre pas, et la case est ce qui l'annonce.
Ctrl/Cmd+ clic etMaj+ clic sélectionnent aussi, mais comme un raccourci pour qui le connaît déjà, jamais comme le seul chemin. Voir Composants. - Un glisser-déposer double une action du menu, il ne la remplace pas. Déplacer une ligne à la souris est le geste attendu d'un explorateur de fichiers, mais il ne se voit pas avant d'être tenté, il n'existe pas au toucher, et il ne s'atteint pas au clavier. L'action doit donc rester dans le menu de la ligne et dans la
FloatingActionBar. La cible sous le curseur se surligne, et le pointeur emporte une pastille qui dit ce qu'il tient.
Style par défaut des cellules. Une colonne sans render est automatiquement enrobée dans le style monospace canonique. Réserver render aux cas qui demandent une composition. Voir Composants.
Badges de statut
Petits éléments inline indiquant un état. Toujours trois éléments ensemble : fond pâle de la couleur sémantique, bordure légère, texte de la couleur sémantique. Rayon complet, texte 11 px.
États interactifs
| État | Apparence |
|---|---|
| Survol | Fond surface-hover : 4 à 5 % plus sombre en clair, un cran plus clair en sombre, jamais vers le noir du fond de page. |
| Actif ou focus | La bordure de 1 px prend la couleur du texte principal |
| Désactivé | 50 % d'opacité, curseur interdit |
| Chargement, premier affichage | Squelette de la forme du contenu attendu |
| Chargement, revalidation | Le contenu reste en place, atténué à 60 %, contrôles verrouillés |
Toutes les transitions durent 120 ms, courbe ease-out. Aucune animation décorative.
Les widgets ARIA marquent l'état désactivé avec aria-disabled, pas avec disabled. Checkbox et Switch sont des <button> porteurs d'un rôle. L'attribut disabled fait partie de l'état de formulaire que le navigateur restaure au rechargement d'une page : une case rendue désactivée par le serveur, parce que le tableau est encore vide, peut ainsi revenir active avant que React ne prenne la main, et l'hydratation signale alors un écart qu'elle ne répare pas. aria-disabled porte la même information sans passer par cet état, et laisse le contrôle atteignable au clavier. Le gestionnaire de clic reste responsable de bloquer l'action.
Un bouton ordinaire garde disabled : c'est l'attribut juste pour un contrôle de formulaire, et son état ne dépend pas d'une donnée arrivée après le rendu serveur.
Un contenu déjà affiché ne disparaît jamais pour être rechargé. Le squelette répond à l'absence de donnée, pas à l'attente d'une donnée fraîche. Vider une vue le temps d'un aller-retour réseau se lit comme un clignotement, et fait sauter la mise en page à chaque tri ou changement de page.
Ce qu'on n'utilise jamais
- Dégradés sur les fonds ou les boutons.
- Cartes à bordure colorée uniquement sur le côté gauche.
- Ombres internes.
- Gras utilisé pour une emphase décorative.
- Icônes sans fonction claire.
- Statistiques ou chiffres inventés pour remplir l'espace.
- Couleurs de fond dans les sections de contenu.
Thème sombre
Le thème sombre est une inversion calibrée : les fonds passent au noir complet, les zones par-dessus au noir-gris, les textes s'éclaircissent, et les accents gagnent légèrement en luminosité pour rester lisibles. La structure, les espaces et les proportions restent identiques.
Le sombre est porté par les jetons, pas par les classes. Chaque jeton a une valeur claire et une valeur sombre, et la classe dark sur <html> fait basculer les deux ensemble. Un composant écrit bg-surface text-fg, jamais bg-white dark:bg-neutral-900. C'est ce qui rend l'oubli impossible : sans variante dark: à écrire, il n'y en a pas à oublier. Le variant dark: ne sert plus qu'à ce qui n'est pas une couleur, comme afficher un logo propre au sombre.
Un projet peut n'offrir qu'un mode avec l'option modes du thème. Toutes les bascules disparaissent alors, dans l'admin comme sur les pages publiques.
L'implémentation est décrite dans Thèmes.