Tailwind CSS : retour d'expérience après deux ans d'utilisation
Publié le 07 juin 2023
Introduction
Il y a maintenant presque trois ans, Grégoire vous donnait son avis (mitigé) de développeur PHP sur la version 2 de Tailwind CSS. Depuis, de l’eau a coulé sous les ponts, la France n’a pas gagné la coupe du monde, la v3 de Tailwind CSS est sortie, et une bonne partie de nos développeurs front-end ont eu l’occasion de se faire leur propre opinion sur le framework. Et si certains sont restés sur leur première impression :
Mais qu’est ce que c’est que cette merde ?
Grégory Copin, 2 février 2021
Mais c'est vraiment de la merde !
Grégory Copin, 17 mars 2023
d’autres, dont je fais partie, sont devenus progressivement des “utility-first” addicts. Il était donc temps d’écrire une “réponse” à ce premier article et de vous donner cette fois le point de vue d’une développeuse front-end sur Tailwind CSS.
Tailwind CSS, c'est quoi ?
Une petite présentation s’impose pour les deux du fond qui n’auraient pas lu l’article de Grégoire : Tailwind, c’est un framework CSS qui a fortement gagné en popularité au fil des années. Contrairement aux frameworks plus classiques, il est basé sur des classes “utilitaires” de bas niveau plutôt que sur des classes sémantiques pré-construites. Cette approche permet de réduire drastiquement la quantité de code CSS nécessaire pour styliser une page, tout en offrant une grande flexibilité et une personnalisation aisée.
Malgré sa relative jeunesse dans le monde sans pitié des frameworks CSS, le petit nouveau a su s’imposer en seulement quelques années, et commence même à faire concurrence à l’ancêtre longtemps indétrôné Bootstrap.
Si l’on se fie aux statistiques de “npm trends“ pour l'année 2023, on constate que Bootstrap et Tailwind sont quasiment similaires en nombre de téléchargements, et que depuis février ce dernier a même tendance à dépasser Bootstrap.
Selon l'édition 2022 du site “State of CSS“, Tailwind est non seulement largement utilisé, mais surtout très apprécié par les développeurs, qui lui attribuent un score de rétention de près de 80% depuis 2019.
Et cerise sur le gâteau, Next.js (que nous utilisons beaucoup pour nos projets front chez Les-Tilleuls.coop) est désormais livré par défaut avec la configuration de base de Tailwind :
Plus besoin d’installer PostCSS ou d’initialiser un fichier de config, Next.js se charge de le faire pour vous !
Tailwind CSS, ça ressemble à quoi ?
C’est super moche. Je ne vais pas vous mentir, la première fois qu’on voit du code utilisant Tailwind ça pique les yeux.
<div class="ring-cyan-500 dark:ring-cyan-400 ring-2 absolute left-1/2 top-1/2 w-4 h-4 -mt-2 -ml-2 flex items-center justify-center bg-white rounded-full shadow">
<div class="w-1.5 h-1.5 bg-cyan-500 dark:bg-cyan-400 rounded-full ring-1 ring-inset ring-slate-900/5"></div>
</div>
(vrai bout de code extrait de la page d’accueil de la doc de Tailwind)
Pour résumer grossièrement, Tailwind possède une classe pour quasiment chaque propriété CSS qui existe.
Ces classes utilitaires peuvent être associées à des “modificateurs” correspondant à des états, à des media queries, à des sélecteurs, au dark mode…
<div class="grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-3">
<div class="bg-slate-200 p-4 hover:bg-slate-500 hover:text-white">Item 1</div>
<div class="bg-slate-200 p-4 hover:bg-slate-500 hover:text-white">Item 2</div>
<div class="bg-slate-200 p-4 hover:bg-slate-500 hover:text-white">Item 3</div>
</div>
Dans ce mini exemple, la div principale est une grille d’une seule colonne en mobile, de deux colonnes sur petit écran et de trois colonnes sur les résolutions supérieures à 1024px. Au hover, les éléments de la grille deviennent plus sombres et la couleur du texte change.
Cet exemple n’est pas très vendeur pour Tailwind, parce qu’on a l’impression de devoir dupliquer des blocs entiers de style, mais sur un vrai projet on créerait ces trois items via une boucle, et on pourrait en faire un composant à part entière.
Mais du coup, est-ce que c'est bien ?
Si j’étais plus que sceptique la première fois que j’ai posé les yeux sur la documentation du framework, en deux ans d’utilisation mon avis a complètement changé et je suis devenue accro à la simplicité et l’efficacité de Tailwind. C’est bien simple, pour mes projets persos, il me paraît maintenant inconcevable d’utiliser autre chose ou de faire du CSS custom.
Ce qui impressionne la première fois qu’on utilise Tailwind, c’est la rapidité avec laquelle on peut commencer à rentrer dans le vif de l’intégration. Pas besoin de créer des classes CSS personnalisées, pas besoin de reset, pas besoin de “tordre” des styles du framework pour l’adapter à notre maquette (coucou Bootstrap) : tout est là, prêt à être utilisé. Le temps de mise en place est ridiculement court comparé à ses concurrents.
Il suffit juste de personnaliser un peu le fichier de configuration pour customiser les couleurs, les typos, et ajouter ce qui donnera à notre projet sa patte graphique, et c’est parti !
Mélanger la logique du style et la structure HTML, ça va à l’encontre de tout ce qu’on nous apprend à l’école sur la séparation des préoccupations. Et pourtant, étonnamment, à l’utilisation… je trouve que ça fonctionne. En ayant toutes les classes définies directement dans le code HTML, il est plus facile de voir comment les différents éléments sont stylisés et de comprendre les choix de design. Si le code a besoin d’être modifié, pas besoin de farfouiller dans des fichiers CSS, il suffit d’aller modifier directement le composant concerné. Et vu que les changements se font directement dans le HTML, aucun risque de régression visuelle, contrairement à ce qui pourrait se produire en cas de modifications d’une classe CSS plus “générique”.
La flexibilité de ce framework est un autre avantage de taille. Contrairement à ses concurrents qui imposent des choix de design et de mise en page, Tailwind CSS offre une liberté totale de personnalisation. Les classes utilitaires peuvent être combinées pour créer des styles entièrement nouveaux, et le fichier de configuration peut être totalement modifié pour remplacer les styles “de base” du framework comme les tailles de texte ou les espacements par des valeurs personnalisées. Contrairement à Bootstrap ou Material, deux sites réalisés avec Tailwind ne se ressembleront pas vu que le framework est totalement “neutre” en termes de design.
Depuis que j’ai commencé à l’utiliser en 2021, Tailwind n’a cessé d’évoluer. La v2 a permis de tester en beta ce qui est pour moi l’un des plus gros atouts du framework et qui allait devenir le coeur de la v3 : le mode JIT (Just In Time) qui permet de compiler uniquement les classes utilisées dans le projet, là où auparavant il fallait faire la mécanique inverse et “purger” les classes non utilisées.
Autre grosse nouveauté de la v3 : la possibilité de créer des classes totalement arbitraires. Par exemple, si vous avez besoin à un endroit précis de votre application d’avoir une div qui fait exactement 321px de large, plus besoin d’ajouter une valeur “width” dans le fichier de config de Tailwind, vous pouvez créer votre propre classe de cette façon :
<div class="w-[321px]" />
Ces classes arbitraires fonctionnent même avec des calculs CSS :
<div class="w-[calc(100%-321px)]" />
Si au premier abord cette écriture peut vous faire dire “là ça va trop loin”, cette liberté permet d’éviter l’ajout de dizaines de valeurs dans votre fichier de configuration Tailwind qui ne vous serviront au final qu’une seule fois.
La v3 a également intégré la classe “aspect-ratio”.
(fun fact : c’est Tailwind qui du coup m’a fait découvrir que cette incroyable propriété existait en CSS, je faisais partie de ces intégrateurs qui utilisaient encore le hack “height: 0px / padding-bottom: 100%” pour créer des divs responsives carrées…).
La v3.3, quant à elle, a vu s’ajouter la classe “line-clamp” qui permet de gérer automatiquement la troncation d’un texte multiligne (fonctionnalité bien pratique qui n’était auparavant rendue possible que via l’ajout d’un plugin officiel Tailwind).
L’un des plus gros avantages du framework, c’est la légèreté de son CSS final. Contrairement aux frameworks CSS traditionnels qui incluent des styles prédéfinis pour chaque composant, Tailwind ne charge que les classes spécifiques RÉELLEMENT utilisées dans votre projet. Cela signifie que vous n'avez pas à vous soucier du poids excessif de “restes” de code CSS inutilisés, ce qui peut vite arriver quand un projet existe depuis longtemps.
Dans le dernier projet sur lequel j’ai utilisé Tailwind, j’ai généré tout un ensemble de contenus via des markdowns. Je me suis donc retrouvée à devoir “extraire” des classes Tailwind en utilisant css-modules pour pouvoir les appliquer au conteneur dans lequel j’injectais mon Markdown, histoire de pouvoir styliser les titres, les paragraphes, etc. Si cette méthode avait le mérite de fonctionner, je la trouvais un peu laborieuse.
Je suis donc allée voir dans la doc de Tailwind s’il n’y avait pas une autre solution… Et il y en avait une : le plugin Typography. Ce plugin permet d’ajouter un style par défaut élégant et efficace à tout type de contenu HTML sur lequel vous n’avez pas la main. C’est propre, facile à mettre en place, customisable, compatible avec le dark mode, et ça peut faire gagner énormément de temps sur l’intégration d’articles de blogs ou de pages de documentation puisque que le plugin solutionne quantité de cas ”gênants”, comme par exemple la gestion des marges lors de l’enchaînement de certains éléments (paragraphe qui suit un titre, h3 qui suit un h2, etc.) ou l’imbrication de listes. Je vous conseille de jeter un œil à cette démo live du plugin.
Et des défauts, il y en a ?
La première fois que vous utiliserez Tailwind, vous passerez sans doute l’essentiel de votre temps à switcher entre votre IDE et la doc du framework. Les classes utilitaires c’est fantastique, encore faut-il les connaître… Et si certains noms sont évidents et correspondent plutôt bien à leur équivalent CSS (p-XXX pour du padding, m-XXX pour de la marge, bg-COLOR pour un background) d’autres sont un peu plus tirés par les cheveux :
Ces noms qui ne coïncident pas avec leurs attributs CSS sont d’ailleurs un autre défaut de Tailwind : si vous comptiez sur ce framework pour apprendre le CSS, vous risquez de partir sur de mauvaises bases. Un exemple tout bête : pour définir l’attribut “flex-direction” d’un élément, les classes proposées par Tailwind sont “flex-col” et “flex-row”. Quand on repasse sur du CSS classique après avoir utilisé Tailwind pendant une longue période, le premier réflexe est d’écrire “flex-direction: col” là où vous devriez mettre “flex-direction: column”.
C’est un argument totalement subjectif mais voilà : le mobile- first, ça me gonfle. Surtout pour une intégration destinée à une audience qui naviguera essentiellement sur desktop. Sauf que Tailwind a pris le parti pris d’être mobile-first, ce qui oblige parfois à devoir “annuler” des classes, là où en CSS classique il aurait juste suffit d’une media query pour appliquer du style destiné uniquement au mobile.
Un exemple tout bête : sur un élément qui n’a des marges et du padding qu’en version mobile, il faudra donc mettre :
<div class=”mx-2 my-1 px-4 py-2 md:mx-0 md:my-0 md:px-0 md:py-0”></div>
Devoir annuler des attributs pour les remettre à 0 ou à “none” en desktop peut vite devenir frustrant.
ERRATUM / TIP
En rédigeant cet article j’ai découvert qu’il est possible de spécifier des “max” dans les paramètres de screen de la config de Tailwind, il existe donc une solution toute simple pour cibler uniquement les mobiles :
extend: {
screen: {
'xs-only': { max: '639px' },
}
Qui permettrait donc dans l’exemple précédent de juste mettre :
<div class=”xs-only:mx-2 xs-only:my-1 xs-only:py-2 xs-only:px-4” />
Je l’ai dit dans les arguments en faveur de Tailwind, la possibilité de pouvoir créer des classes arbitraires quand on ne souhaite pas ajouter une valeur dans le fichier de config pour un cas isolé, c’est super et ça donne une grande liberté à l’intégrateur. Mais comme le disait un grand philosophe, un grand pouvoir implique de grandes responsabilités : on peut vite tomber dans le travers de TOUT déclarer en classes arbitraires, et le code déjà bien moche de Tailwind peut vite devenir complètement illisible.
D’une manière générale, si vous utilisez plus de trois fois une même classe arbitraire, c’est sans doute que ça vaudrait le coup d’en faire une nouvelle valeur dans la config de Tailwind. Et si vous vous rendez compte que vous multipliez les classes arbitraires, c’est peut-être qu’il y a un souci dans votre intégration et qu’il faudrait sans doute uniformiser vos styles.
Quand on utilise Tailwind depuis un moment, on se fait vite à l’idée qu’il existe une classe utilitaire pour à peu près tout. Le problème, c’est que à peu près, ce n’est pas tout. Et il peut être très frustrant de découvrir que des pans entiers de CSS ont été “laissés de côté” par le framework. On peut citer par exemple :
- les rotations 3D : Tailwind ne connaît pas la propriété “perspective”, et il n’est pas possible d’utiliser les rotations 3D sans utiliser un plugin custom. Une discussion est ouverte depuis deux ans sur le dépôt du projet et une PR est actuellement en cours.
- les propriétés SVG : si le “stroke” et le “fill” sont couverts par le framework, ce n’est pas le cas d’autres attributs comme le “dash-array” ou le “dash-offset” qui sont bien pratiques pour animer des SVG. Une discussion est également ouverte à ce sujet depuis près d’un an et demi.
La façon dont Tailwind parse le CSS pour ne générer que les classes utilisées est très basique : vous lui fournissez des répertoires à scanner, il parcourt les fichiers présents dans ces dossiers et dès qu’il rencontre une chaîne de caractère qui correspond à une classe Tailwind, il l’ajoute au CSS final. C’est une méthode simpliste, très efficace mais qui a une limitation qui peut être vraiment pénible : elle implique qu’aucune classe créée “dynamiquement” ne sera prise en compte par le framework. Il faut donc trouver des astuces détournées pour qu’elles soient quand même interprétées.
Exemple tout simple : un composant “Button” qui peut être vide ou plein, et qui peut être de trois couleurs différentes, définies dans la config de Tailwind : “error”, “primary” et “success”.
Le code pourrait être écrit comme tel :
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
color: "primary" | "error" | "success";
empty?: boolean;
}
function Button({ color, empty, ...props }: ButtonProps) {
return (
<button
className={classNames(
"uppercase text-sm px-2 py-1",
empty
? `border-${color} border-2 text-${color}`
: `bg-${color} text-white`
)}
{...props}
/>
);
}
Le souci, c’est que si les classes “bg-primary”, “text-primary”, etc. n’ont pas été générées ailleurs dans votre code, elles ne seront pas présentes dans le CSScss final. Pour qu’elles soient générées, ces classes doivent être spécifiées EN DUR quelque part dans le code.
Pour pallier à ce souci j’ai trouvé deux solutions, mais qui ne me conviennent pas totalement :
function Button({ color, empty, ...props }: ButtonProps) {
const getBgColorClassName = () => {
switch (color) {
case "primary":
return "bg-primary";
case "error":
return "bg-error";
case "success":
return "bg-success";
}
};
const getBorderColorClassName = () => {
switch (color) {
case "primary":
return "border-primary";
case "error":
return "border-error";
case "success":
return "border-success";
}
};
const getTextColorClassName = () => {
switch (color) {
case "primary":
return "text-primary";
case "error":
return "text-error";
case "success":
return "text-success";
}
};
return (
<button
className={classNames(
"uppercase text-sm px-2 py-1",
empty
? `${getBorderColorClassName()} border-2 ${getTextColorClassName()}`
: `${getBgColorClassName()} text-white`
)}
{...props}
/>
);
}
Cette solution fonctionne, mais n’est pas très élégante, et alourdit beaucoup le code.
L’autre solution, c’est d’utiliser la “safelist” de la config de Tailwind. Les pattern peuvent aider à exclure de nombreuses classes de la purge en une seule condition :
safelist: [
{
pattern:
/bg-(primary|error|success)|text-(primary|error|success)|border-(primary|error|success)/,
variants: ["sm", "md", "lg", "xl"],
},
],
Cette solution paraît moins lourde que la première mais elle n’est pas idéale non plus puisqu’elle oblige à avoir bien en tête les classes à sauvegarder. Elle implique aussi le risque de laisser des classes inutilisées dans le CSS final.
Quelques conseils pour finir
Je ne m’imagine pas utiliser Tailwind sans le super plugin VScode “tailwind-css-intellisense” qui se charge de m’afficher toutes les variantes possibles pour mes classes.
(c’est un conseil que je ne respecte pas toujours, mais c’est important).
Comme vous le feriez avec du CSS classique, essayez d’organiser vos classes par type d’attributs (tailles, marges, couleurs…) et par type de modificateurs (media-queries, dark mode…) histoire de rendre le code plus lisible. Un bon moyen “visuel” de séparer les classes dans un projet React, c’est d’utiliser une librairie comme “classnames” ou “clsx”.
<div
classNames={clsx(
'bg-white text-center w-full',
'sm:w-1/2',
'md:w-1/3',
'dark:bg-gray-800',
)}
/>
N’hésitez pas à créer un maximum de “petits” composants pour éviter de dupliquer sans arrêt les mêmes classes.
C’est très subjectif comme conseil, mais la façon dont ces classes fonctionnent risque de causer des soucis graphiques bien compliqués à débugger, si par exemple vous avez une liste affichée en “flex-col“ en mobile et “flex-row“ sur les résolutions plus grandes. Exemple :
<div class=”flex flex-col space-y-2 sm:flex-row sm:space-y-0 sm:space-x-2 />
Il serait facile ici d’oublier la classe “sm:space-y-0”, elle est pourtant nécessaire afin d’éviter l’apparition d’une marge basse non désirée sur certains éléments de votre liste.
En utilisant la classe “gap”, vous pouvez obtenir le même résultat en écrivant juste :
<div class=”flex flex-col gap-2 sm:flex-row />
La propriété “gap“ est plus intuitive, moins source d’erreur, et elle est maintenant bien supportée par la plupart des navigateurs, donc je vous recommande son utilisation.
Je l’ai déjà dit quelques paragraphes plus haut, mais je pense qu’il n’est pas inutile de le répéter : si vous utilisez trop de classes arbitraires, c’est probablement que votre intégration n’a pas été conçue correctement ou qu’il vous manque des valeurs dans votre config Tailwind.
Comme l’expliquait Grégoire dans son article, la directive “@apply” permet d'agréger un ensemble de classes Tailwind dans une feuille de style “classique”. Ça peut être très tentant quand on utilise Tailwind pour la première fois de recourir à ce système pour rendre le HTML plus “propre”... Mais ne le faites pas. C’est le créateur de Tailwind lui même qui le dit :
Confession: The `apply` feature in Tailwind basically only exists to trick people who are put off by long lists of classes into trying the framework.
— Adam Wathan (@adamwathan) February 9, 2020
You should almost never use it 😬
Reuse your utility-littered HTML instead.https://t.co/x6y4ksDwrt
Il y a des cas où le “apply“ peut avoir son utilité, pour du code issu de Markdown ou d’un CMS par exemple. Il m’est également arrivé de l’utiliser pour des composants dans lesquels du contenu est injecté via “dangerouslySetInnerHTML”, et sur lesquels je n’ai donc pas directement la main sur les éventuelles balises. Mais ne cédez pas à la tentation d’utiliser du “apply” partout : c’est une stratégie qui va à l’encontre des principes de Tailwind, et c’est sans doute que vous n’avez finalement pas besoin du framework mais que vous pourriez écrire votre CSS de façon classique.
Pour conclure
Si vous ne connaissiez pas Tailwind ou si vous aviez des a priori sur ce framework, j’espère que cet argumentaire aura au moins su vous convaincre d’y jeter un œil et de lui donner sa chance.
Et bien entendu, pour toutes questions, besoins d'accompagnement personnalisé ou de formation approfondie, n’hésitez pas à contacter Les-Tilleuls.coop : nous sommes là pour échanger avec vous et voir ensemble comment répondre à vos besoins !
Notre équipe est à votre disposition pour tirer le meilleur parti du potentiel de Tailwind ou vous aider à renforcer vos compétences en CSS.