Journal de bord · Phase 0 · 6/7
Verre, le design system d'Aubia
Verre remplace l'ancien design d'aubia.dev et servira aussi à aubia.cloud et à l'application desktop : deux thèmes aux gris neutres, un verre gris, Geist et des composants copiés d'un registre shadcn.
par Mike, fondateur d'Aubia Publié le Mis à jour le 10 min de lecture
- design-system
- verre
- tailwind
- motion



Le fond sombre d'aubia.dev est un gris presque noir, #0E0F12, sans la teinte bleue qu'il avait à la première parution de cet article. Le thème clair, « aube », a pour fond un gris très pâle, #EDEFF2. Les deux viennent de Verre, la nouvelle identité visuelle d'Aubia, dont j'ai validé la maquette le 5 octobre 2026.
En août, cet article décrivait un autre site : deux thèmes Catppuccin complets, un verre que traversaient les nappes de couleur du fond, et Chakra Petch, une police anguleuse réservée aux titres d'aubia.dev. Chaque app d'Aubia définissait ses propres couleurs et ses propres composants, et l'application desktop prévoyait quatre variantes de thème quand le site en proposait deux. Verre donne aux trois apps une seule identité, et le site est le premier à l'appliquer.
De Luma, le style de composants de shadcn/ui, j'ai repris les arrondis, l'espacement, l'élévation et les animations des changements d'état. La matière, les couleurs, la typographie et les icônes sont propres à Verre.
Un registre shadcn fournit les composants
Les composants de Verre se trouvent dans un registre shadcn privé, hébergé sur GitHub. Une app ne les installe pas comme une dépendance : la commande shadcn add copie leur code source dans son dépôt, à côté du reste de son code. J'ai écarté un paquet npm versionné : il aurait livré un code compilé à la place de ces sources, et il aurait fallu publier et protéger un registre npm privé de plus.
J'ai installé 91 éléments du registre dans le site. Un manifeste note l'empreinte SHA-256 de chaque fichier copié, et un test vérifie que la copie reste identique à la source. Pour que ce contrôle tienne, une copie ne se modifie jamais sur place : une adaptation de style passe par une feuille de style à part, importée après celles du registre.
Ces composants s'appuient sur Base UI 1.9.0, que shadcn recommande désormais à la place de Radix. Base UI gère le focus, la navigation au clavier et les rôles ARIA. Un lien habillé en bouton passe par un composant ButtonLink à part, parce que la primitive Button imposerait le rôle de bouton à un lien.
Les icônes viennent de Hugeicons, en style Stroke Rounded, sous licence MIT. Je les ai choisies à la place de Font Awesome Pro : sa licence est payante, et la maquette validée est dessinée avec Hugeicons.
Deux thèmes, et le choix du système par défaut
Verre définit un thème clair, « aube », et un thème sombre, « Nuit ». Le sombre est fait de gris neutres, pour les surfaces, les lignes et les textes. De Catppuccin, la palette de l'ancien site, Verre garde les accents : la pêche de Latte en clair, #FE640B, celle de Mocha en sombre, #FAB387, puis, sur l'accueil, la sarcelle et la lavande des intertitres et le dégradé du grand titre. La pêche sert aux liens, aux puces et au contour du focus clavier.
Les couleurs ne forment plus une palette complète déclinée en classes. Elles sont nommées par rôle, fond, verre, texte ou accent, et chaque rôle reçoit sa valeur dans chacun des deux thèmes. Un bloc @theme inline relie ces noms à ceux de shadcn et aux utilitaires de Tailwind : un composant emploie bg-background ou text-muted-foreground, et la valeur suit le thème affiché.
J'ai gardé les couleurs de la maquette telles quelles, même là où des textes passent sous les seuils de contraste WCAG AA, dans les deux thèmes : le design system signale ces contrastes faibles sans les corriger. Les autres exigences d'accessibilité restent : navigation au clavier, focus visible, cibles tactiles d'au moins 44 px sur les écrans de moins de 768 px de large, lien d'évitement.
L'ancien design ouvrait le site en sombre, quelle que soit la préférence du visiteur. J'ai choisi le thème du système par défaut : vous avez déjà indiqué votre préférence à votre OS, et le même choix vaudra dans les trois apps. Le sélecteur propose toujours « Clair », « Sombre » et « Système ».
Le thème est connu avant le premier rendu
Un script placé dans le <head>, avant les feuilles de style, lit le choix enregistré sous la clé aubia-theme et la préférence du système. Il définit data-theme pour un choix explicite et data-scheme pour la préférence : le CSS applique le bon thème sans attendre React. Sans JavaScript, la media query prefers-color-scheme applique le sombre quand le système le demande. Si le stockage local est bloqué, votre choix ne vaut que pour la visite en cours.
Le changement de thème lui-même est animé. Après un clic sur le sélecteur, la page attend 200 ms, le temps que l'indicateur du sélecteur glisse sur le nouveau choix, puis le nouveau thème apparaît en 650 ms dans un cercle parti du bouton, par l'API View Transition. En mode « Système », un changement de la préférence produit un fondu de toute la page. Quand le système demande un mouvement réduit, le thème bascule d'un coup.
Le CSS du mode « Système » lit data-scheme plutôt que la media query. Chromium affiche déjà les couleurs de la nouvelle préférence quand il déclenche l'événement change : si la page suivait la media query, elle aurait changé avant que le script capture l'état de départ de la transition.
Les cartes reprennent le verre gris de la barre
L'effet de verre vient de la propriété backdrop-filter : elle floute ce qui passe derrière un élément, et un fond semi-opaque teinte le résultat. Dans l'ancien design, ce filtre saturait aussi les couleurs, et celles des nappes du fond traversaient les cartes. Le verre ordinaire du design system, celui des listes de choix et des notifications, sature encore, à 160 %, avec un flou de 24 px. Celui de la barre de navigation floute autant mais retire toute couleur :
@utility surface-bar {
background: var(--bar-glass);
border: 1px solid var(--glass-line);
backdrop-filter: var(--bar-blur);
}
--bar-blur vaut blur(24px) grayscale(100%), et --bar-glass un blanc à 80 % en clair, un gris à 70 % en sombre : les halos de lumière du fond ne teintent plus ce verre. Sur le site, les cartes, les questions de la FAQ, la couverture et les tableaux des articles le reprennent, sans reflet au passage du pointeur.
Le menu de langue s'affiche hors de la barre
Quand un ancêtre possède déjà un backdrop-filter, il définit une backdrop root : le filtre d'un descendant ne voit plus ce qui se trouve derrière cet ancêtre. Le menu de langue de la barre, un menu de Base UI, est rendu dans un portail : la barre n'est plus son ancêtre, et il applique son propre flou. Le verre de la barre est défini sur la racine du document pour que ce menu le retrouve.
Ce menu ne se monte que côté client : ses liens sont absents du HTML initial. Le pied de page reprend donc ces liens dans un élément <details> natif : ils figurent dans le HTML produit par le rendu serveur, et l'ouverture fonctionne sans JavaScript. React y ajoute la navigation par les flèches, la fermeture par Échap ou par un clic à l'extérieur, et place le focus sur la langue courante à l'ouverture.
Des halos et une trame d'étoiles sous le contenu
Le fond des pages tient en deux calques fixes. Le premier dessine dans chaque coin une ellipse de lumière, de la couleur que prend le symbole du logo dans le même coin : violet en haut à gauche, rose en haut à droite, corail en bas à gauche, pêche clair en bas à droite, plus discrètes en sombre qu'en clair. Le second est une trame de formes de 7 px, des soleils-étoiles en clair et des étoiles en sombre, que la lumière des halos révèle. Son pas, proche de 32 px, divise exactement la largeur du contenu : une colonne de formes tombe sur chacun de ses bords.
Partout sur le site, toutes les 0,3 à 0,9 seconde, une forme grandit et prend une teinte du thème, six au plus à la fois, et une loupe éclaire les formes proches du pointeur. La boucle d'animation s'arrête quand l'onglet est masqué.
Geist pour le texte comme pour le code
J'ai retiré Chakra Petch. Geist compose désormais tout le texte, grand titre de l'accueil compris, et Geist Mono le code, les chemins et les commandes. Les deux polices sont variables : un seul fichier WOFF2 latin par police couvre les graisses de 100 à 900.
Elles sont auto-hébergées. Le plugin de polices de laravel-vite-plugin lit les fichiers des paquets Fontsource installés et génère les déclarations @font-face au build ; la directive Blade @fonts les insère, avec leur préchargement, dans toutes les pages. Aucune requête ne part vers un fournisseur de polices.
Avec display: 'swap', le texte s'affiche d'abord dans une police de repli. Pour Geist, le plugin ajuste les métriques de ce repli, et le texte se recompose à peine à l'arrivée de la police. Geist Mono garde un repli monospace ordinaire : le plugin cherche dans le fichier la catégorie de la police, monospace ou sans empattement, n'en trouve aucune et prendrait Arial comme modèle.
Le mouvement, et une seule règle pour le réduire
Les durées et les courbes viennent de variables CSS, --d-* et --ease-*, lues par les feuilles de style comme par les scripts. La courbe des micro-interactions, --ease-out, vaut cubic-bezier(.2, .8, .2, 1).
Au chargement, aucun texte visible n'apparaît en fondu, parce que Chromium exclut du LCP un élément d'opacité nulle. Le grand titre de l'accueil entre par un glissement de 14 px. Le fondu reste réservé aux textes révélés au défilement, plus bas dans la page, et aux décors.
Avant Verre, les règles de mouvement réduit visaient certaines animations et laissaient jouer les autres transitions. Verre les remplace par une seule, pour tout le site :
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
transition: none !important;
animation: none !important;
}
}
Chaque état s'affiche alors d'emblée. Les couleurs changent encore, d'un coup ; les déplacements au survol et à l'appui passent par la variante motion-safe: de Tailwind, qui ne s'applique plus.
Les animations pilotées par script échappent à cette règle, comme l'accroche de l'accueil, dont les phrases apparaissent caractère par caractère. Elles lisent la préférence par le hook useReducedMotion du registre, qui s'appuie sur useSyncExternalStore : il s'abonne à l'événement change de matchMedia, et un changement de réglage pendant la visite arrête aussitôt l'animation en cours. Le serveur ne connaît pas cette préférence. Le hook renvoie donc false au rendu serveur et pendant l'hydratation, puis lit la valeur réelle, comme le décrit la documentation de React pour useSyncExternalStore. Le HTML serveur contient déjà la dernière phrase, et l'accroche la garde en mouvement réduit.
Le code des articles garde les couleurs de Catppuccin
Les blocs de code de ce blog sont colorés côté serveur par Phiki 2.2, au build, avec les thèmes Catppuccin Latte en clair et Mocha en sombre. Une classe PHP copie ces deux thèmes et remplace leurs neutres, le fond, le texte, les commentaires et la ponctuation, par des variables CSS de Verre ; elle échoue si une montée de version de Phiki supprime l'une des règles de couleur visées, plutôt que de livrer un thème incomplet. Un bloc de code prend donc le fond du thème affiché, et seules ses couleurs de syntaxe viennent encore de Catppuccin.
La bêta d'Aubia est gratuite et sans limite de temps. Inscrivez-vous pour recevoir votre invitation.
Rejoindre la liste d'attente