Le 18 juin, Meta a publié Astryx en bêta : un design system open source sous licence MIT, 150+ composants React accessibles, du theming, un mode sombre, des templates et un CLI. Onze jours plus tard, Catherine et Cindy ont publié How Astryx works, le billet technique qui explique d'où vient le truc et de quoi il est fait. C'est celui-là qui m'a fait ouvrir un terminal.
Pourquoi ça devrait t'intéresser : depuis trois ans, la réponse par défaut à « quel design system ? » c'est Tailwind + shadcn. Astryx propose l'inverse exact du compromis shadcn — tu ne possèdes pas le code des composants, tu possèdes le thème — et il arrive avec huit ans de composants internes derrière lui. Le billet d'annonce parle de 13 000 applications internes chez Meta.
C'est une bêta, et une bêta qui bouge : le changelog empile déjà les versions mineures, breaking changes compris. J'y reviens plus bas.
Le thème à la place du fork
C'est le point central, et c'est celui qui décide si le reste t'intéresse.
Un thème Astryx, c'est un fichier de config déclaratif — couleurs, typo, motion, espacement, ajustements par composant — qui génère des surcharges de custom properties CSS et de styles de composants. Tu changes une valeur, tous les composants suivent. Sept thèmes sont livrés aujourd'hui. Un thème peut aussi introduire des variantes de composants qui n'existent pas dans le système de base.
Concrètement, à la racine de l'app :
import {Theme} from '@astryxdesign/core/theme';
import {neutralTheme} from '@astryxdesign/theme-neutral/built';
import '@astryxdesign/theme-neutral/theme.css';
<Theme theme={neutralTheme} mode="system">
<App />
</Theme>;
Et l'usage, qui n'a rien de surprenant :
import {Button} from '@astryxdesign/core/Button';
import {Card} from '@astryxdesign/core/Card';
import {Stack} from '@astryxdesign/core/Stack';
<Stack gap="md">
<Card>
<Button variant="primary">Save</Button>
</Card>
</Stack>;
Petit détail qui évite une confusion à la lecture de la doc : Button accepte aussi une prop label à la place des children — <Button label="Save" variant="primary" />. Les deux formes marchent, et les pages de doc utilisent l'une ou l'autre.
Cinq crans de contrôle, et une trappe
La doc décrit une escalade en cinq étapes : utiliser le composant tel quel → ajuster les tokens du thème → passer tes propres classes → écrire ton CSS → éjecter le code source. Chaque cran te donne plus de contrôle contre plus de responsabilité.
Le dernier cran s'appelle swizzle : il copie la source complète du composant dans ton projet, modules internes compris.
Et là il y a un piège documenté dans les notes de la 0.1.5 : un composant éjecté embarque du StyleX brut, qui a besoin d'un compilateur au build. Sans lui, le composant s'affiche sans style, et sans erreur. La CLI imprime maintenant une note après la copie. Le cas Next.js est explicitement signalé : le plugin Babel StyleX désactive SWC, ce qui casse next/font. Il faut donc une transformation basée sur SWC.
Deux distributions, un compilateur
Astryx se livre de deux façons.
La voie simple : une feuille de style pré-compilée. Trois @import, aucune configuration de build.
@import '@astryxdesign/core/reset.css';
@import '@astryxdesign/core/astryx.css';
@import '@astryxdesign/theme-neutral/theme.css';
La voie avancée : tu builds depuis la source TypeScript + StyleX, et ton bundler ne garde que les styles des composants que tu importes. Le billet donne un ordre de grandeur mesuré sur leur app de référence : environ un tiers de la feuille de style complète.
Un tiers, c'est beaucoup. Mais c'est le prix d'un compilateur StyleX dans ta chaîne de build, et la question Next.js ci-dessus revient au même endroit.
Le CLI parle aux agents
C'est la partie que je trouve la plus intéressante, et la moins vendable.
Le CLI n'est pas un accessoire de scaffolding. C'est l'interface d'opération du système : lire la doc, générer des thèmes, appliquer les migrations automatiques. Et c'est aussi, explicitement, le canal par lequel un agent apprend la bibliothèque — la même référence que celle que tu lis.
npx astryx docs migration --dense
npx astryx component Button --json
npx astryx build "on-call incident console"
npx astryx init --features agents
--dense sort une version compressée à coller dans un outil de code IA. init --features agents injecte l'index des composants dans un CLAUDE.md ou un AGENTS.md.
Ils appellent leur harnais d'évaluation les vibe tests : une mesure de la facilité avec laquelle humains et IA construisent réellement avec Astryx, utilisée pour trancher les débats d'API avec des données. Un chiffre issu des notes de la 0.1.1 : le rappel de composants est passé de 15 % à 71 % sur une éval orientée agent, après l'ajout de groupes structurels toujours présents dans le résultat de build.
Ce qui casse
Il n'y a pas de migration depuis une version précédente — le coût, ici, c'est l'adoption. Trois postes.
Les renommages de la 0.1.x. La 0.1.0 a supprimé toute la nomenclature xds d'un coup, sans fenêtre de compatibilité : scope npm @xds/* → @astryxdesign/*, xds.config.mjs → astryx.config.mjs, XDSButton → Button, classes .xds-* → .astryx-*, layers xds-base → astryx-base. La 0.1.2 a renommé color="active" en color="accent" sur Text, Heading, Link et Timestamp. La 0.1.7 déprécie tableProps sur Table. Chaque fois, un codemod :
npx astryx upgrade --apply
npx astryx upgrade --codemod migrate-table-tableprops-to-direct-props
Ça, ça ne coûte rien. Un upgrade par bump de version mineure, à mettre dans ta routine.
Les cascade layers. Voilà le vrai coût, et il est silencieux. Le guide de migration l'explique bien : un style non layered bat n'importe quel layer nommé, et un layer déclaré plus tard bat un layer déclaré plus tôt — dans les deux cas indépendamment de la spécificité. Ton vieux reset.css importé sans layer() écrase donc tous les styles de composants. Le résumé du guide tient en quatre mots : « Same CSS, opposite outcome ». Aucune erreur, aucun warning : juste des boutons sans padding.
L'ordre à déclarer une fois, avant tout @import, en Tailwind v4 :
@layer reset, theme, base, astryx-base, astryx-theme, components, utilities;
@import "tailwindcss/preflight.css" layer(base);
@import "@astryxdesign/core/reset.css";
@import "@astryxdesign/core/astryx.css";
@import "@astryxdesign/theme-neutral/theme.css";
@import "tailwindcss/utilities.css" layer(utilities);
Sur webpack — donc sur Next.js — cette déclaration doit vivre dans son propre fichier importé en premier, parce que webpack remonte le contenu des @import au-dessus du CSS inline qui suit. Le guide recommande de rendre une page jetable avec un Button, un TextInput, une Card et une Table avant de migrer quoi que ce soit, et d'asserter que le padding n'est pas à zéro.
Le remplacement, pas l'emballage. La doc est nette là-dessus : on ne wrappe pas un composant shadcn dans des styles Astryx, on remplace la primitive. Tailwind peut cohabiter pendant la migration pour les wrappers legacy et la mise en page locale.
// avant
<button className="inline-flex items-center rounded-md bg-slate-900 px-4 py-2 text-sm text-white">
Enregistrer
</button>
// après
<Button variant="primary">Enregistrer</Button>
L'ordre recommandé fait neuf étapes, route par route, en commençant par la coquille applicative. Aucune durée n'est donnée dans la source. Mon estimation, à prendre pour ce qu'elle vaut : une demi-journée pour l'installation, l'ordre des layers et le test de fondation ; le reste se compte en routes, pas en jours.
Mon avis
Le pari intéressant n'est pas les 150 composants. C'est l'ordre des crans. shadcn a répondu à la question de la personnalisation par « tu possèdes le code », ce qui veut aussi dire que tu possèdes les bugs et que tu perds le chemin de mise à jour. Astryx répond « le thème d'abord, l'éjection en dernier ». C'est le bon ordre — à une condition : que la couche thème soit assez expressive. Si je me heurte à un mur en semaine deux et que je swizzle la moitié de mes composants, je suis revenu à shadcn avec des étapes en plus.
Ce que ça ne résout pas, c'est StyleX. L'annonce insiste sur l'absence de dépendances, et c'est vrai de la feuille de style pré-compilée. Mais dès que tu veux le tree-shaking ou l'éjection, tu ajoutes un compilateur à ton build, et sur Next.js tu échanges SWC contre Babel et tu casses next/font. Sur un projet Vite ou TanStack Start, c'est un non-événement. Sur une app Next existante, c'est une décision d'architecture, pas un npm install.
Deuxième réserve : la mesure. Les vibe tests sont leur harnais, sur leur bibliothèque, avec leurs critères. Le 15 % → 71 % mesure la qualité de leur propre recherche de composants, pas le fait qu'une IA produise une meilleure UI. C'est honnête comme démarche, ce n'est pas encore une preuve externe.
De mon côté : je le monte sur un projet perso, pas sur du client. Un dashboard interne, parce que c'est exactement là que le catalogue est fort — Table, CommandPalette, SideNav, PowerSearch, et des templates de console d'incident qui sentent le vécu. Je garde Tailwind pour la mise en page et je note tout ce qui me force à écrire du CSS.
Verdict
Outil interne neuf, React, bundler qui n'est pas Next : essaie maintenant. C'est le terrain d'origine du système, la breadth est là, et le coût d'entrée se limite à l'ordre des layers. Attends-toi à du churn en 0.1.x et lance astryx upgrade à chaque bump.
App produit existante en Tailwind + shadcn : attends. Pas parce que c'est mauvais — parce qu'une bêta avec des breaking changes en version mineure et un piège de cascade silencieux ne rentre pas dans un sprint. Rouvre le dossier à la 1.0.
Site vitrine, landing pages : passe ton chemin. Ce n'est pas fait pour ça et ça ne le sera pas.
