Le filtre tailwind_merge résout un problème spécifique : quand vous construisez des composants Twig réutilisables avec Tailwind CSS, les classes entrent en conflit. Ce filtre, fourni par le bundle tales-from-a-dev/twig-tailwind-extra, détermine automatiquement quelle classe doit l'emporter.
Le problème des classes en double
Voici ce qui se passe sans tailwind_merge. Vous créez un composant bouton avec des styles par défaut :
<button class="p-4 text-gray-500 {{ attributes.render('class') }}">
Click me
</button>
Imaginez que vous repassez quatre semaines plus tard sur le composant pour faire une modification :
{% embed 'Button.html.twig' with { attributes: { class: 'p-2 text-blue-600' } } %}
Le HTML généré contient maintenant class="p-4 text-gray-500 p-2 text-blue-600". Quelle classe gagne ? Ça dépend de l'ordre dans lequel Tailwind génère le CSS, pas de l'ordre dans le HTML. Résultat : vous ne pouvez pas prédire si le bouton aura padding: 1rem ou padding: 0.5rem.
Ce n'est pas une erreur JavaScript qui plante. C'est pire : c'est un bug visuel qui passe inaperçu jusqu'à ce qu'un utilisateur le remarque en production.
Comment fonctionne tailwind_merge
Le filtre analyse les classes Tailwind et garde la dernière classe de chaque "groupe". Un groupe correspond à une propriété CSS : toutes les classes p-* contrôlent le padding, toutes les classes text-*-500 contrôlent la couleur du texte.
{{ ('p-4 text-gray-500 ' ~ attributes.render('class'))|tailwind_merge }}
Avec attributes.class = 'p-2 text-blue-600', vous obtenez p-2 text-blue-600. La classe p-2 écrase p-4, et text-blue-600 écrase text-gray-500. C'est tout.
La règle est simple : dernière classe du groupe = classe appliquée.
Application dans mes composants
Dans Button.html.twig, le composant combine les classes générées par un système de variants avec les classes personnalisées :
<{{ as }} data-slot="button"
class="{{ style.apply({ variant: variant, size: size }, attributes.render('class'))|tailwind_merge }}"
{{ attributes }}>
{%- block content %}{% endblock -%}
</{{ as }}>
style.apply() génère des classes selon la variante (primary, secondary) et la taille (sm, lg). attributes.render('class') ajoute ce que l'appelant a passé. Le filtre élimine les conflits.
Pour un composant plus simple comme Card.html.twig :
<div class="{{ ('rounded-lg border bg-card text-card-foreground shadow-sm ' ~ attributes.render('class'))|tailwind_merge }}" {{ attributes }}>
{%- block content %}{% endblock -%}
</div>
Les classes de base définissent l'apparence standard. Si quelqu'un passe border-2, ça écrase border. Si quelqu'un passe shadow-lg, ça écrase shadow-sm.
Le pattern à retenir
Chaque fois qu'un composant accepte des classes via attributes, utilisez cette structure :
{{ ('classes-de-base ' ~ attributes.render('class'))|tailwind_merge }}
Les classes de base viennent en premier. Les classes personnalisées viennent après. Le filtre gère les conflits.
Ce que ça change concrètement
Avant tailwind_merge, vous deviez créer des props pour chaque variation possible. Un composant Card avait besoin de borderWidth, shadowSize, rounded, etc. Ou alors vous acceptiez que les overrides de classes soient aléatoires.
Avec tailwind_merge, vous écrivez un seul composant avec des classes de base raisonnables. Les développeurs passent les classes qui diffèrent. Le filtre fait le tri.
Moins de props à maintenir. Moins de variantes à documenter. Moins de bugs visuels mystérieux.
Installation et utilisation
Le filtre vient avec tales-from-a-dev/twig-tailwind-extra, qui encapsule tailwind-merge-php. Une fois le bundle installé, tailwind_merge est disponible partout dans vos templates Twig.
tailwind_merge repose sur une connaissance interne de la structure de Tailwind. Il sait que p-* et px-* appartiennent au même système de propriétés (padding). Il sait que text-gray-500 et text-blue-600 contrôlent la couleur du texte. Il sait résoudre les conflits entre rounded-lg et rounded-none.
Cette connaissance est encodée dans la bibliothèque. Vous n'avez pas à lui expliquer quelles classes entrent en conflit. Elle le sait déjà.
Le mot de la fin
tailwind_merge prend un comportement imprévisible (plusieurs classes qui se contredisent) et applique une règle claire (la dernière gagne). Pour les projets qui combinent Twig et Tailwind, c'est un outil qui réduit les frictions et rend les composants plus fiables. Pas révolutionnaire, mais utile tous les jours.