00:00:00Arrêtez d'utiliser les composants Radix et passez à autre chose : Base UI. D'ailleurs, si vous êtes fan de shadcn,
00:00:06il est déjà possible de faire la transition. Si vous n'avez jamais entendu parler de Base UI,
00:00:10sachez que c'est une bibliothèque UI "headless" créée par les auteurs originaux de Radix, Floating UI et Material UI.
00:00:15Elle apporte les fonctionnalités et l'accessibilité des composants, tout en vous laissant la main sur le design,
00:00:20ce qui est crucial aujourd'hui, car les LLM ont encore du mal avec les cas particuliers et l'accessibilité. Mais ce que je
00:00:24viens de décrire, c'est exactement la définition de Radix.
00:00:30Alors pourquoi en changer ? Voyons ça tout de suite avec les principales différences.
00:00:35Je vais d'abord vous montrer rapidement la documentation de Base UI pour que vous voyiez tous les
00:00:44composants disponibles sur la gauche. Ils proposent quasiment tout ce dont vous auriez besoin
00:00:48dans votre bibliothèque, et même des composants avancés comme le "Combo Box"
00:00:52qui n'existe pas dans Radix. Pour chacun, vous avez un bel exemple de mise en œuvre et de
00:00:57personnalisation avec des modules CSS ou même Tailwind si vous préférez.
00:01:01La doc est excellente, mais on n'est pas là pour comparer la documentation. Passons à la première
00:01:06différence majeure, celle dont vous entendrez le plus parler :
00:01:10Base UI est activement maintenu, alors que Radix est un peu à l'état de zombie. Si l'on regarde
00:01:15les graphiques de contribution sur GitHub, on voit que Base UI ne cesse de croître, tandis que Radix
00:01:20ne reçoit que de rares commits sporadiques. C'est encore plus flagrant si l'on regarde
00:01:25les PR et les tickets fermés le mois dernier : Base UI a résolu 58 problèmes et
00:01:29fusionné 154 pull requests, contre zéro pour Radix. Pour résumer l'histoire,
00:01:36WorkOS a racheté l'entreprise derrière Radix mais n'a pas vraiment investi dedans.
00:01:42L'équipe de Radix est donc partie en grande partie, et nous voilà dans cette situation. Le co-créateur
00:01:47de Radix a même déclaré qu'il ne l'utiliserait qu'en dernier recours... et il travaille désormais
00:01:53sur Base UI. Il s'agit donc de s'assurer que vos applications dépendent de projets actifs pour éviter
00:01:58des maux de tête futurs face à des bugs qui ne seront jamais corrigés. Mieux encore,
00:02:02Base UI a été conçu pour ressembler à Radix, donc la migration ne devrait pas être trop pénible.
00:02:08Cela ne les empêche pas de glisser quelques améliorations, et l'une de mes préférées
00:02:13concerne la gestion de la propriété "asChild" que l'on trouve dans Radix. Si vous ne connaissez pas,
00:02:17j'ai ici un composant Select de Radix. Pour rendre un composant personnalisé
00:02:22en tant que déclencheur (trigger), si je l'entoure simplement avec le composant "Select.Trigger",
00:02:27j'obtiens un composant conteneur en plus du bouton "S'abonner" (cliquez dessus d'ailleurs !).
00:02:31Mais si je veux que le bouton serve directement de déclencheur,
00:02:36je dois ajouter la propriété "asChild" sur le déclencheur. Cela dit à
00:02:41Radix de fusionner toutes les propriétés et fonctionnalités du déclencheur avec son composant enfant.
00:02:46On le voit ici : le nom de la classe est fusionné avec le bouton et prend même le dessus.
00:02:50Si je retire cette propriété et que je sauvegarde, on a bien un bouton, mais il
00:02:55fait maintenant office de déclencheur pour mon Select.
00:03:00C'est une fonctionnalité pratique, mais on lui reprochait souvent son manque de clarté.
00:03:04J'avoue qu'en parcourant du code rapidement, il m'arrivait de rater cette propriété et
00:03:09de ne pas comprendre d'où venait un problème. Voyons comment Base UI gère cela :
00:03:13on obtient le même résultat — un bouton qui sert de déclencheur —
00:03:18mais si vous regardez le code, Base UI utilise la propriété "render" au lieu de "asChild".
00:03:24Avec la propriété "render", vous spécifiez directement le composant que vous voulez afficher
00:03:29comme déclencheur. C'est un changement mineur, mais je trouve cela beaucoup plus
00:03:34explicite. On voit littéralement ce qui est rendu. Pas besoin de vérifier
00:03:39s'il y a un composant enfant ou de chercher la propriété "asChild".
00:03:43C'est un petit détail, mais très pertinent. Et ce n'est pas tout le potentiel de la propriété "render".
00:03:48Ici, sur un composant Switch, on peut passer une fonction à la propriété "render".
00:03:52Cela permet d'accéder aux propriétés et à l'état du composant que l'on
00:03:56construit, comme ici pour le curseur (thumb). Cela nous permet de choisir sur quel composant
00:04:01appliquer les propriétés transmises, mais aussi d'ajouter une logique de rendu
00:04:05ou de style personnalisée basée sur l'état. Avec ce curseur de switch,
00:04:10on peut savoir s'il est coché, modifié, désactivé, rempli, etc. Dans ce cas,
00:04:14on vérifie simplement s'il est coché pour afficher
00:04:18une icône différente. Il existe même un hook pour intégrer cette propriété "render"
00:04:22dans vos propres composants personnalisés. On rentre dans le complexe, mais vous voyez
00:04:27que Base UI peut tout gérer. Revenons à mon composant Select :
00:04:31l'autre différence est que Base UI permet une approche basée sur les données.
00:04:35On peut le voir ici avec le Select. Voici d'abord le code Radix :
00:04:40nous avons un tableau avec nos libellés et valeurs, et pour les injecter dans le
00:04:44Select, on doit itérer (map) sur le tableau "apples" et
00:04:49rendre un composant "SelectItem" pour chaque valeur. Voyons maintenant
00:04:54comment Base UI procède. C'est très similaire : on a toujours notre tableau
00:04:59et on itère toujours dessus pour rendre les éléments, mais on l'inclut
00:05:03aussi à un autre endroit : sur le composant racine "Select.Root".
00:05:08On lui passe le tableau, ce qui a un impact subtil : le composant
00:05:13connaît les données avant même le rendu. Cela améliore légèrement les performances,
00:05:17surtout pour le rendu côté serveur (SSR). Je pense toutefois qu'on pourrait
00:05:22améliorer un point : là, je passe "apples" dans la propriété "items",
00:05:26mais j'itère aussi dessus plus bas. On l'utilise donc à deux endroits.
00:05:32Je préférerais l'approche de React Aria, une autre bibliothèque headless. Regardez :
00:05:36on passe le tableau "animals" aux "items", puis pour les
00:05:41enfants, on utilise simplement une fonction. Celle-ci connaît déjà tous les éléments
00:05:45transmis au parent. On n'utilise pas le tableau deux fois et l'élément parent
00:05:50sert de fournisseur de données. C'est un point qui pourrait encore être peaufiné.
00:05:55Mais revenons à notre Select pour une autre différence, cette fois spécifique
00:05:59à ce composant. Si la propriété "render" et l'approche par données
00:06:03se retrouvent partout, cette fonctionnalité-ci est propre au Select :
00:06:08la sélection multiple. C'était cruellement absent de Radix.
00:06:13Dans Base UI, il suffit d'ajouter la propriété "multiple" (true ou false) sur la racine du Select
00:06:17pour qu'il devienne multi-sélection. C'est aussi simple que ça. Et pour
00:06:22aller plus loin, Base UI propose des composants absents de Radix comme le "Combo Box"
00:06:27ou l'Auto-complétion. C'est l'avantage d'une maintenance active qui répond aux demandes.
00:06:33Il reste deux différences que je veux vous montrer entre Radix et Base UI.
00:06:38On va passer au composant Checkbox parce que je commence à me lasser
00:06:41du Select. La première concerne le style. Base UI offre
00:06:45une option de stylisation vraiment cool. Ici, j'utilise Tailwind.
00:06:50On peut utiliser les approches classiques (CSS normal, modules CSS, etc.),
00:06:55mais avec Tailwind, on style généralement un composant
00:06:59en utilisant des attributs de données pour l'état. Par exemple, avec "data-checked",
00:07:04on applique une couleur de fond. On se retrouve vite avec une très longue chaîne
00:07:08de caractères dans Tailwind, ce qui peut devenir problématique.
00:07:13Base UI permet donc d'utiliser une fonction pour définir le nom de la classe (className).
00:07:17Cela donne accès à l'état du composant. Pour cette case à cocher,
00:07:22j'applique des styles selon qu'elle est cochée ou désactivée via une simple condition.
00:07:26Je trouve que c'est plus lisible : on voit d'un coup d'œil
00:07:31d'où viennent les styles d'état, au lieu de devoir scanner
00:07:35une ligne interminable à la recherche des attributs "data". C'est encore
00:07:40plus utile si vous utilisez Vanilla CSS. Ça fonctionne aussi
00:07:45très bien avec une bibliothèque que j'adore : Tailwind Variants. Regardez,
00:07:49je passe l'état à ma fonction checkbox, et dans ma variante Tailwind,
00:07:53on a nos styles de base, puis nos variantes. Je peux dire : "si checked est vrai,
00:07:58applique ces styles ; si disabled est vrai, applique ceux-là".
00:08:02C'est parfois bien plus clair que les attributs de données, mais c'est une affaire de goût.
00:08:06C'est génial que Base UI nous laisse le choix. J'ajoute que j'avais
00:08:11adoré cette utilisation de fonction pour les classes dans React Aria.
00:08:14Pour moi, on a d'un côté React Aria, très puissant mais avec une courbe d'apprentissage raide,
00:08:19et de l'autre Radix, assez simple. Base UI se situe pile au milieu, prenant le
00:08:23meilleur des deux pour créer la bibliothèque headless ultime. Voilà pour les
00:08:28principales différences, mais il y en a d'autres : Base UI supporte très bien
00:08:33React Hook Form et TanStack Form, facilite les animations des composants
00:08:38et propose même des fonctions comme le réglage numérique par glissement (input scrubbing),
00:08:42les dialogues imbriqués ou les menus au survol. Et comme le projet est actif,
00:08:47ils répondront sûrement à vos suggestions sur GitHub. Cela dit,
00:08:52je ne migrerais pas forcément une application Radix existante tout de suite,
00:08:57car Radix n'est pas totalement hors service. Mais pour un nouveau projet, je choisirais Base UI,
00:09:02ou si j'avais besoin d'un Combo Box ou d'une auto-complétion. Dites-moi
00:09:07ce que vous en pensez en commentaire, abonnez-vous et, comme toujours,
00:09:11à la prochaine !