Développement Web

Composants réutilisables avec @apply en Tailwind CSS

13 min de lecture

Au troisième bouton identique copié-collé dans votre interface, une gêne s’installe : vous répétez exactement la même longue liste de classes, et le jour où le design du bouton change, il faudra la modifier à dix endroits. C’est le moment où la question de la réutilisation devient sérieuse. Tailwind y répond de deux façons très différentes, et choisir la bonne sépare les projets maintenables des autres. À la fin de ce tutoriel, vous saurez quand factoriser avec la directive @apply, quand préférer un vrai composant, et comment créer vos propres utilitaires avec @utility — le tout en bâtissant la bibliothèque de composants de StockLab.

🎯 Ce que vous allez apprendre

  • Factoriser des classes répétées en une classe de composant avec @apply.
  • Comprendre pourquoi l’extraction de composant (React, Vue, partial) est souvent préférable à @apply.
  • Utiliser @reference pour faire fonctionner @apply dans un bloc de style isolé.
  • Créer un utilitaire personnalisé réutilisable avec la directive @utility.

🛠️ Ce que vous allez construire

Nous dotons StockLab d’une petite bibliothèque de composants : un bouton primaire, un bouton secondaire, une carte standard et un badge d’état. Chacun existera en une seule définition, réutilisable partout, et modifiable en un point unique. Vous verrez aussi comment créer un utilitaire maison pour un effet récurrent.

Prérequis

  • Un projet Tailwind v4 fonctionnel avec les composants des leçons précédentes.
  • À l’aise avec les classes utilitaires de base et la structure d’une feuille CSS.
  • Test express : si vous savez ce que produit rounded-md bg-indigo-600 px-4 py-2, vous êtes prêt.
  • ⏱️ Temps estimé : environ 30 minutes.

Le réflexe à acquérir : composant d’abord, @apply ensuite

Avant de toucher à @apply, il faut comprendre une recommandation qui vient de l’équipe de Tailwind elle-même : dans un projet avec un framework, la meilleure façon de réutiliser un ensemble de classes n’est pas @apply, c’est d’extraire un composant. Un composant React, un composant Vue, un partial de template : vous écrivez la liste de classes une fois, dans le composant, et vous le réutilisez. Cette approche garde tout au même endroit — markup et style — et évite de recréer une couche d’abstraction CSS que Tailwind cherchait justement à supprimer.

Alors, à quoi sert @apply ? À deux situations précises. D’abord, surcharger les styles d’une bibliothèque tierce dont vous ne contrôlez pas le HTML : vous écrivez du CSS classique mais avec vos tokens. Ensuite, créer une primitive vraiment universelle réutilisée dans un projet sans framework — un site statique, un thème WordPress — où il n’y a pas de système de composants. En dehors de ces cas, demandez-vous toujours d’abord : « est-ce que je ne devrais pas plutôt en faire un composant ? » Cette discipline vous évitera de reconstruire involontairement les fichiers CSS monolithiques que l’approche utilitaire voulait éliminer.

Étape 1 — Un bouton primaire avec @apply

Plaçons-nous dans le cas légitime d’un projet sans framework. Le bouton « Ajouter un article » revient partout ; on en fait une classe .btn-primary qui rassemble ses utilitaires. La directive @apply inline les classes Tailwind dans une règle CSS classique, en gardant l’accès à votre thème.

@import "tailwindcss";

.btn-primary {
  @apply rounded-md bg-indigo-600 px-4 py-2 font-medium text-white transition;
}
.btn-primary:hover {
  @apply bg-indigo-700;
}

Désormais, <button class="btn-primary">Ajouter</button> produit exactement le même rendu qu’avant, mais en un seul mot. Notez qu’on a séparé l’état de survol dans une règle :hover distincte : c’est la manière propre de gérer les variantes quand on passe par @apply. Le grand avantage : changez la couleur ici, et tous les boutons du projet suivent. L’inconvénient : vous venez de recréer une indirection — il faut ouvrir le CSS pour savoir ce que fait .btn-primary. C’est précisément ce compromis qu’il faut peser.

Point d’étape — Vos boutons portant la classe btn-primary s’affichent identiquement et réagissent au survol. Si la classe reste sans style, vérifiez que la définition se trouve bien après @import "tailwindcss"; dans le même fichier.

Étape 2 — Décliner avec un bouton secondaire et une carte

On enrichit la bibliothèque. Un bouton secondaire moins appuyé, et une carte standard pour uniformiser toutes les fiches de StockLab. La logique est la même : une classe, une intention de design.

.btn-secondary {
  @apply rounded-md border border-gray-300 px-4 py-2 font-medium text-gray-700;
}
.card {
  @apply rounded-lg bg-white p-4 shadow;
}

Ces deux classes condensent des intentions claires : « bouton discret » et « surface de carte ». Dans le HTML, <div class="card"> remplace désormais la répétition de rounded-lg bg-white p-4 shadow. Tant que ces primitives restent peu nombreuses et stables, le gain de lisibilité est réel. Le danger guette quand on multiplie les variantes — .card-small, .card-highlighted, .card-with-image — car on reconstruit alors le problème de prolifération que l’utility-first résolvait. La règle de sagesse : peu de primitives, et le reste en utilitaires directs.

Étape 3 — Le badge d’état et la composition mesurée

Les fiches affichent un statut : en stock, stock faible, rupture. Trois badges proches mais distincts. Plutôt qu’une classe par couleur, on définit une base commune et on laisse la couleur en utilitaire dans le HTML : la composition reste visible et flexible.

.badge {
  @apply inline-flex items-center rounded-full px-2 py-1 text-xs font-medium;
}

Dans le markup : <span class="badge bg-green-100 text-green-700">En stock</span>. La classe .badge porte la forme commune ; les couleurs varient en utilitaires. C’est l’équilibre idéal : on factorise ce qui ne change jamais (la forme) et on garde flexible ce qui varie (la couleur). Cette approche hybride est souvent supérieure à une explosion de classes .badge-green, .badge-red, etc., car elle reste lisible et n’enferme rien.

Étape 4 — Faire fonctionner @apply dans un composant isolé

Si vous travaillez avec Vue, Svelte ou des CSS Modules, vous rencontrerez vite un piège : le bloc <style> d’un composant est compilé isolément et n’a pas connaissance de votre thème, donc @apply échoue avec une erreur de classe inconnue. La solution officielle est la directive @reference, qui importe le contexte de votre feuille principale sans en dupliquer le contenu.

<style>
  @reference "../app.css";

  .stat {
    @apply text-3xl font-bold text-indigo-600;
  }
</style>

@reference pointe vers votre CSS principal (celui qui contient @import "tailwindcss"; et votre @theme). Il rend les tokens et utilitaires disponibles pour @apply dans ce bloc isolé, mais sans réinjecter tout le CSS de Tailwind dans le composant — ce qui gonflerait inutilement la sortie. Sans cette ligne, vous verriez une erreur du type « cannot apply unknown utility class ». C’est l’un des changements de la v4 que les développeurs venant de la v3 découvrent souvent à leurs dépens.

Point d’étape — Dans un composant à bloc de style isolé, @apply fonctionne dès lors que @reference pointe vers votre CSS principal. Si l’erreur « unknown utility » persiste, vérifiez le chemin relatif du fichier référencé.

Étape 5 — Créer un utilitaire maison avec @utility

Parfois, vous voulez non pas un composant mais un vrai utilitaire réutilisable — une classe atomique qui accepte les variantes hover:, md:, etc., comme les utilitaires natifs. C’est le rôle de @utility. Donnons à StockLab un utilitaire de troncature de texte sur deux lignes, fréquent pour les descriptions de produit.

@utility line-clamp-2-custom {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

La différence avec @apply est fondamentale : un utilitaire défini avec @utility se comporte comme un citoyen de première classe du système. Vous pouvez écrire md:line-clamp-2-custom ou hover:line-clamp-2-custom, ce qu’une simple classe .line-clamp ne permettrait pas. C’est l’outil à choisir quand votre besoin est atomique et doit composer avec les variantes, plutôt que d’être un assemblage de composant. Tailwind propose d’ailleurs déjà line-clamp-* en natif ; cet exemple illustre simplement le mécanisme pour vos propres besoins réels.

Trois outils, trois intentions

Récapitulons la carte de décision, car c’est elle qui compte vraiment. L’extraction de composant est votre choix par défaut dès qu’un framework est présent : markup et style restent ensemble. La directive @apply sert à factoriser une primitive dans un projet sans composants, ou à surcharger une bibliothèque tierce. La directive @utility crée un utilitaire atomique qui doit accepter les variantes. Se tromper d’outil n’empêche pas le code de marcher, mais alourdit la maintenance : trop de @apply, et vous revoilà avec un gros fichier CSS opaque ; pas assez de composants, et vous copiez-collez. Le bon réflexe se construit en gardant ces trois intentions distinctes à l’esprit.

Spécificité et couches : pourquoi vos composants restent surchargeables

Une question légitime se pose dès qu’on crée des classes de composant : si .btn-primary définit un fond indigo, que se passe-t-il quand on veut ponctuellement un bouton vert en ajoutant bg-green-600 dans le HTML ? Avec le CSS traditionnel, le résultat dépend de l’ordre de déclaration et de la spécificité, et tourne souvent au conflit qu’on résout à coups de !important. Tailwind v4 évite ce piège grâce aux couches en cascade natives (cascade layers), une fonctionnalité moderne du CSS sur laquelle le framework s’appuie en profondeur.

Concrètement, Tailwind range ses utilitaires dans une couche prioritaire, de sorte qu’un utilitaire posé dans le HTML l’emporte sur une déclaration équivalente venue d’ailleurs, indépendamment de la spécificité du sélecteur. Pour StockLab, cela signifie que vos classes de composant restent surchargeables : vous pouvez écrire <button class="btn-primary bg-green-600"> et obtenir bien un bouton vert, sans bataille de spécificité. C’est un confort considérable au quotidien et l’une des raisons pour lesquelles l’approche utilitaire compose si bien avec quelques classes de composant. Si vous voulez explicitement ranger vos propres règles dans une couche, la directive @layer reste disponible, mais dans la plupart des cas le comportement par défaut suffit.

Organiser sa feuille de styles quand le projet grandit

Au début, tout tient dans un seul fichier : l’@import, le bloc @theme, quelques classes de composant. Mais StockLab va grossir, et un fichier CSS qui enfle redevient difficile à parcourir. La bonne pratique consiste à séparer les responsabilités. Gardez d’un côté le thème — toutes vos variables dans @theme — et de l’autre vos rares primitives de composant. Beaucoup d’équipes éclatent même le CSS en plusieurs fichiers importés : un pour le thème, un pour les composants, que l’on rassemble avec des @import classiques que Lightning CSS regroupe à la compilation.

Une règle d’or accompagne cette organisation : ne créez une classe de composant que lorsque la répétition est avérée, jamais par anticipation. Il est tentant de bâtir une bibliothèque complète de composants dès le premier jour ; c’est presque toujours une erreur, car on abstrait des choses qui finiront par diverger. Attendez de copier-coller la même liste de classes une troisième fois avant de la factoriser. Cette patience garde votre CSS mince et honnête : il ne contient que des abstractions justifiées par l’usage réel, et reste le reflet fidèle de votre interface plutôt qu’une couche spéculative qui vit sa propre vie. C’est cette sobriété, plus que n’importe quelle directive, qui distingue une base de code Tailwind durable.

🐞 Pièges fréquents

Symptôme / erreur Cause probable Correctif
Cannot apply unknown utility class dans un composant Vue/Svelte Le bloc de style isolé n’a pas accès au thème Ajouter @reference "votre-app.css"; en tête du bloc <style>
Le hover ne marche pas sur une classe @apply État appliqué dans la même règle au lieu d’une règle :hover Créer une règle .btn-primary:hover { @apply … } séparée
Le fichier CSS regonfle de manière inattendue Multiplication de classes @apply par variantes Revenir à l’extraction de composant pour la réutilisation
Une classe @apply personnalisée n’accepte pas md: Une classe CSS n’est pas un utilitaire composable La définir avec @utility si elle doit accepter des variantes

✅ Récapitulatif

Vous disposez maintenant d’une vraie boîte à outils de réutilisation. Vous savez factoriser avec @apply dans les bons cas, et surtout reconnaître quand l’extraction de composant est meilleure ; vous avez construit les boutons, la carte et le badge de StockLab ; vous savez débloquer @apply dans un composant isolé avec @reference ; et vous créez vos propres utilitaires composables avec @utility. La leçon centrale : ces outils ne s’opposent pas, ils répondent à des intentions différentes, et le bon développeur choisit en conscience.

🧾 Aide-mémoire

Directive Usage
@apply Inliner des utilitaires dans une règle CSS (primitive, surcharge tierce)
@reference "app.css" Donner le contexte du thème à un bloc de style isolé
@utility nom { … } Créer un utilitaire atomique qui accepte les variantes
Composant (React/Vue/partial) Choix par défaut pour réutiliser dans un framework

💪 À vous de jouer

Créez une classe .btn-danger pour les actions destructrices (supprimer un article), en rouge, avec un état de survol plus foncé. Puis demandez-vous honnêtement : dans un projet React, en feriez-vous une classe @apply ou un composant ?

Voir une solution
.btn-danger {
  @apply rounded-md bg-red-600 px-4 py-2 font-medium text-white transition;
}
.btn-danger:hover {
  @apply bg-red-700;
}

Dans un projet React, on en ferait plutôt un composant <Button variant="danger"> : markup et style restent ensemble, et la variante se gère par une prop plutôt que par une classe CSS supplémentaire.

Tutoriels frères

Ressources et références

FAQ

Q : @apply est-il déconseillé ?
R : Pas déconseillé, mais à employer à bon escient. L’équipe de Tailwind recommande de privilégier l’extraction de composant quand un framework est disponible, et de réserver @apply aux primitives sans framework et aux surcharges de bibliothèques tierces.

Q : Pourquoi mon @apply échoue-t-il dans un fichier Vue ?
R : Le bloc <style> est compilé isolément et ne connaît pas votre thème. Ajoutez @reference pointant vers votre CSS principal en tête du bloc pour lui donner ce contexte.

Q : Quelle différence entre @apply et @utility ?
R : @apply assemble des utilitaires dans une règle CSS classique non composable ; @utility crée un véritable utilitaire qui accepte les variantes comme hover: ou md:.

Q : Combien de classes @apply est-il raisonnable d’avoir ?
R : Peu. Quelques primitives stables (bouton, carte, badge) sont saines. Si vous commencez à créer des variantes en cascade, c’est le signal de revenir à des composants ou à des utilitaires directs.

Partager