Le blog

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.

" class="wp-image-7046" width="768" height="509

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 :

" class="wp-image-7050" width="768" height="233

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.

#
Une productivité accrue

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 !

#
Une lecture étonnamment facilitée

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”.

#
Une personnalisation ultra simplifiée

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.

#
Un framework en constante évolution

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).

#
Un CSS léger et performant

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 ?

#
Des débuts laborieux

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 :

" class="wp-image-7069" width="768" height="437

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”.

#
Le mobile-first

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” />
#
Trop de liberté ?

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.

#
Les limitations de Tailwind CSS

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.
#
Le problème des classes dynamiques (et mes solutions un peu moches pour y pallier)

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 :

#
Mettre un gros “switch” dans le composant qui écrira en dur les classes à utiliser :
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.

#
La safelist

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

#
Utilisez un plugin d'auto complétion

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.

" class="wp-image-7077" width="768" height="345#
Organisez vos 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',
 )}
/>
#
Évitez les duplications de style

N’hésitez pas à créer un maximum de “petits” composants pour éviter de dupliquer sans arrêt les mêmes classes.

#
N’utilisez pas “space-x“ et “space-y“

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.

#
N'abusez pas des classes arbitraires

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. 

#
N'abusez pas des “apply”

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 : 

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.

Laury Sorriaux

Laury Sorriaux

Lead developer & designer

Mots-clésCSS, framework, Tailwind CSS

Le blog

Pour aller plus loin