Skip to content

Tailwind CSS

This content is not available in your language yet.

Tailwind CSS est un framework CSS utility-first qui révolutionne la façon de styliser les interfaces web. Au lieu d’écrire du CSS personnalisé, vous composez vos designs directement en HTML avec des classes utilitaires.

CSS classique :

<!-- HTML -->
<button class="btn btn-primary">Ajouter au panier</button>
<!-- CSS séparé -->
.btn {
padding: 0.5rem 1rem;
border-radius: 0.375rem;
font-weight: 600;
}
.btn-primary {
background-color: #0073e6;
color: white;
}
.btn-primary:hover {
background-color: #005bb5;
}

Avec Tailwind :

<!-- Tout dans le HTML -->
<button class="px-4 py-2 rounded-md font-semibold bg-blue-600 text-white hover:bg-blue-700">
Ajouter au panier
</button>
AvantageImpact sur TailStore
Développement rapidePrototyper des composants produit en minutes
Design système intégréCohérence couleurs/espacements automatique
Responsive natifMobile-first avec préfixes sm:, md:, lg:
Purge CSSFichier final < 10 KB (vs 200 KB avec Bootstrap)
Pas de namingFini les débats sur .product-card vs .item-card
Dark modeSupport natif avec dark:
PerformanceAucun CSS inutilisé chargé
┌─────────────────────────────────────────┐
│ Templates Twig (.html.twig) │
│ Classes Tailwind dans le HTML │
└──────────────┬──────────────────────────┘
│ Vite watch
┌─────────────────────────────────────────┐
│ assets/style.css │
│ @import "tailwindcss" │
│ @theme { custom vars } │
│ @layer components { .btn } │
└──────────────┬──────────────────────────┘
│ npm run build
┌─────────────────────────────────────────┐
│ dist/assets/main.css (minified) │
│ Seulement les classes utilisées │
└──────────────┬──────────────────────────┘
│ Déclaré dans .libraries.yml
┌─────────────────────────────────────────┐
│ Chargé dans <head> par Drupal │
└─────────────────────────────────────────┘
  • 🛒 Grilles de produits : grid grid-cols-4 gap-6
  • 🏷️ Badges promo : badge badge-danger (-30%)
  • 🔍 Filtres : Formulaires stylés avec form-input
  • 📱 Navigation : Menu responsive avec breakpoints
  • 🖼️ Galeries images : Lightbox avec transitions
  • 💳 Checkout : Formulaire multi-étapes

🆕 Approche Tailwind v4 : CSS natif au lieu de @apply

Section intitulée « 🆕 Approche Tailwind v4 : CSS natif au lieu de @apply »

❌ Tailwind v3 (ancienne approche) :

.btn {
@apply px-4 py-2 bg-blue-600 text-white rounded;
}

✅ Tailwind v4 (nouvelle approche) :

.btn {
padding-inline: --spacing(4);
padding-block: --spacing(2);
background-color: var(--color-blue-600);
color: var(--color-white);
border-radius: var(--radius);
}

Avantages :

  • 🚀 Plus rapide (pas de transformation @apply)
  • 🎨 Accès direct aux variables CSS
  • 🔧 Meilleure intégration avec les outils CSS
  • 📦 CSS plus petit et optimisé
Fenêtre de terminal
cd themes/custom/tailstore
npm init -y
Fenêtre de terminal
npm install -D vite @tailwindcss/vite tailwindcss

Créez vite.config.js :

import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [
tailwindcss(),
],
build: {
outDir: 'dist',
rollupOptions: {
input: {
main: 'assets/main.js',
},
output: {
entryFileNames: 'assets/[name].js',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name].[ext]',
},
},
},
})

Créez assets/style.css :

/* assets/style.css */
/* Import Tailwind */
@import "tailwindcss";
/* Personnalisation du thème */
@theme {
/* Couleurs */
--color-primary: #0073e6;
--color-primary-dark: #005bb5;
--color-secondary: #6c757d;
--color-success: #28a745;
--color-warning: #ffc107;
--color-danger: #dc3545;
--color-dark: #1a1a2e;
--color-light: #f8f9fa;
/* Fonts */
--font-sans: 'Inter', system-ui, sans-serif;
/* Spacing custom */
--spacing-18: 4.5rem;
--spacing-128: 32rem;
/* Border radius */
--radius-btn: 0.375rem;
--radius-card: 0.5rem;
}
/* Composants personnalisés avec CSS natif (Tailwind v4) */
@layer components {
/* Boutons */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding-inline: --spacing(4);
padding-block: --spacing(2);
font-weight: var(--font-weight-semibold);
border-radius: var(--radius-btn);
transition: all 0.2s;
}
.btn-primary {
background-color: var(--color-primary);
color: var(--color-white);
&:hover {
@media (hover: hover) {
background-color: var(--color-primary-dark);
}
}
}
.btn-secondary {
background-color: var(--color-secondary);
color: var(--color-white);
&:hover {
@media (hover: hover) {
background-color: color-mix(in srgb, var(--color-secondary) 90%, black);
}
}
}
.btn-outline {
border: 1px solid currentColor;
background-color: transparent;
&:hover {
@media (hover: hover) {
background-color: var(--color-primary);
color: var(--color-white);
border-color: var(--color-primary);
}
}
}
/* Cards */
.card {
background-color: var(--color-white);
border-radius: var(--radius-card);
box-shadow: var(--shadow-md);
overflow: hidden;
}
.card-body {
padding: --spacing(4);
}
/* Badges */
.badge {
display: inline-flex;
align-items: center;
padding-inline: --spacing(2);
padding-block: --spacing(0.5);
font-size: var(--text-xs);
font-weight: var(--font-weight-medium);
border-radius: calc(infinity * 1px);
}
.badge-primary {
background-color: color-mix(in srgb, var(--color-primary) 10%, transparent);
color: var(--color-primary);
}
.badge-danger {
background-color: color-mix(in srgb, var(--color-danger) 10%, transparent);
color: var(--color-danger);
}
.badge-success {
background-color: color-mix(in srgb, var(--color-success) 10%, transparent);
color: var(--color-success);
}
/* Formulaires */
.form-input {
width: 100%;
padding-inline: --spacing(3);
padding-block: --spacing(2);
border: 1px solid var(--color-gray-300);
border-radius: var(--radius-btn);
outline: none;
transition: colors 0.2s;
&:focus {
border-color: var(--color-primary);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-primary) 20%, transparent);
}
}
.form-label {
display: block;
font-size: var(--text-sm);
font-weight: var(--font-weight-medium);
color: var(--color-gray-700);
margin-bottom: --spacing(1);
}
/* Container */
.container {
margin-inline: auto;
padding-inline: --spacing(4);
max-width: 1280px;
@media (width >= 640px) {
padding-inline: --spacing(6);
}
@media (width >= 1024px) {
padding-inline: --spacing(8);
}
}
}
/* Utilitaires personnalisés */
@layer utilities {
.text-balance {
text-wrap: balance;
}
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
}

Créez package.json avec les scripts :

{
"name": "tailstore-theme",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"@tailwindcss/vite": "^4.0.0",
"tailwindcss": "^4.0.0",
"vite": "^5.0.0"
}
}

Créez assets/main.js :

// assets/main.js
import './style.css'
// Votre code JavaScript ici
console.log('TailStore theme loaded!')
Fenêtre de terminal
# Démarrer le serveur de développement
npm run dev
# Build production
npm run build
# tailstore.libraries.yml
global:
version: 1.0
css:
theme:
dist/assets/main-*.css: { minified: true }
dependencies:
- core/drupal

Objectif : Configurer Tailwind v4 et vérifier que les classes fonctionnent.

  1. Suivez toutes les étapes d’installation ci-dessus

  2. Créez un template de test test-tailwind.html.twig :

    Fenêtre de terminal
    touch themes/custom/tailstore/templates/test-tailwind.html.twig
  3. Ajoutez du HTML avec des classes Tailwind :

    <div class="container mx-auto p-8">
    <h1 class="text-4xl font-bold text-blue-600 mb-4">
    Tailwind fonctionne ! 🎉
    </h1>
    <div class="grid grid-cols-3 gap-4">
    <div class="bg-red-500 text-white p-4 rounded-lg">Rouge</div>
    <div class="bg-green-500 text-white p-4 rounded-lg">Vert</div>
    <div class="bg-blue-500 text-white p-4 rounded-lg">Bleu</div>
    </div>
    </div>
  4. Lancez le build :

    Fenêtre de terminal
    cd themes/custom/tailstore
    npm run dev
  5. Videz le cache Drupal :

    Fenêtre de terminal
    drush cr
  6. Visitez une page et inspectez (F12) : les classes doivent être appliquées

Validation : ✅ Le texte est bleu, les 3 boîtes colorées sont visibles


Objectif : Utiliser @layer components pour créer des boutons réutilisables.

  1. Dans assets/style.css, ajoutez dans @layer components :

    @layer components {
    .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding-inline: --spacing(6);
    padding-block: --spacing(3);
    font-weight: var(--font-weight-semibold);
    border-radius: var(--radius-lg);
    transition: all 0.2s;
    outline: none;
    &:focus {
    box-shadow: 0 0 0 2px var(--color-white), 0 0 0 4px currentColor;
    }
    }
    .btn-primary {
    background-color: var(--color-blue-600);
    color: var(--color-white);
    &:hover {
    @media (hover: hover) {
    background-color: var(--color-blue-700);
    }
    }
    }
    .btn-secondary {
    background-color: var(--color-gray-600);
    color: var(--color-white);
    &:hover {
    @media (hover: hover) {
    background-color: var(--color-gray-700);
    }
    }
    }
    .btn-outline {
    border: 2px solid var(--color-blue-600);
    color: var(--color-blue-600);
    background-color: transparent;
    &:hover {
    @media (hover: hover) {
    background-color: var(--color-blue-600);
    color: var(--color-white);
    }
    }
    }
    .btn-lg {
    padding-inline: --spacing(8);
    padding-block: --spacing(4);
    font-size: var(--text-lg);
    }
    .btn-sm {
    padding-inline: --spacing(3);
    padding-block: --spacing(1.5);
    font-size: var(--text-sm);
    }
    }
  2. Testez dans un template :

    <div class="flex gap-4 p-8">
    <button class="btn btn-primary">Primary</button>
    <button class="btn btn-secondary">Secondary</button>
    <button class="btn btn-outline">Outline</button>
    <button class="btn btn-primary btn-lg">Large</button>
    <button class="btn btn-primary btn-sm">Small</button>
    </div>
  3. Rebuilder le CSS :

    Fenêtre de terminal
    npm run build
    drush cr

Validation : ✅ 5 boutons avec styles différents, hover/focus fonctionnent


Objectif : Créer une grille qui s’adapte à la taille d’écran.

  1. Créez un template Views views-view-unformatted--products.html.twig :

    <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
    {% for row in rows %}
    {{ row.content }}
    {% endfor %}
    </div>
  2. Testez la responsivité :

    • Mobile (< 640px) : 1 colonne
    • Tablette (640px+) : 2 colonnes
    • Desktop (1024px+) : 3 colonnes
    • Large (1280px+) : 4 colonnes
  3. Ajoutez des espacements adaptés :

    <div class="container mx-auto px-4 sm:px-6 lg:px-8 py-8 lg:py-12">
    <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 lg:gap-6">
    <!-- produits -->
    </div>
    </div>
  4. Testez en redimensionnant la fenêtre du navigateur

Validation : ✅ La grille change de colonnes selon la taille d’écran


Objectif : Adapter les couleurs Tailwind aux couleurs de la marque.

  1. Dans assets/style.css, modifiez @theme :

    @theme {
    /* Couleurs de la marque TailStore */
    --color-brand-primary: #ff6b6b;
    --color-brand-secondary: #4ecdc4;
    --color-brand-accent: #ffe66d;
    --color-brand-dark: #2d3436;
    --color-brand-light: #f8f9fa;
    /* Remplacer les couleurs Tailwind par défaut */
    --color-primary: var(--color-brand-primary);
    --color-secondary: var(--color-brand-secondary);
    }
  2. Utilisez ces couleurs :

    <button class="bg-primary text-white px-6 py-3 rounded-lg hover:bg-primary/90">
    Acheter maintenant
    </button>
    <div class="bg-secondary/10 border border-secondary text-secondary p-4 rounded">
    Livraison gratuite dès 50€
    </div>
  3. Testez avec différentes opacités :

    <div class="bg-primary/10">10%</div>
    <div class="bg-primary/50">50%</div>
    <div class="bg-primary/90">90%</div>
  4. Rebuilder et vérifier

Validation : ✅ Les couleurs personnalisées sont appliquées partout

1. Purge automatique - Vérifier la configuration

Tailwind v4 purge automatiquement, mais vérifiez que tous vos templates sont scannés :

// vite.config.js
export default defineConfig({
plugins: [
tailwindcss({
// Spécifier où chercher les classes
content: [
'./templates/**/*.twig',
'./templates/**/*.html.twig',
'./src/**/*.php',
],
}),
],
})

2. Éviter les classes dynamiques

{# ❌ Mauvais - Classe générée dynamiquement #}
<div class="text-{{ color }}"> {# Ne fonctionnera pas avec la purge #}
{# ✅ Bon - Mapper vers des classes complètes #}
{% set color_classes = {
'red': 'text-red-600',
'blue': 'text-blue-600',
'green': 'text-green-600',
} %}
<div class="{{ color_classes[color] }}">

3. Utiliser les composants pour les styles complexes

/* Au lieu de répéter dans chaque template */
@layer components {
.product-card {
background-color: var(--color-white);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
overflow: hidden;
transition: all 0.2s;
&:hover {
@media (hover: hover) {
transform: translateY(-0.25rem);
box-shadow: var(--shadow-xl);
}
}
}
.product-card-image {
aspect-ratio: 1;
overflow: hidden;
}
.product-card-body {
padding: --spacing(4);
display: flex;
flex-direction: column;
gap: 0.5rem;
}
}

4. Optimiser les builds

Fenêtre de terminal
# Développement - rebuild rapide
npm run dev
# Production - minification maximale
npm run build
# Vérifier la taille finale
ls -lh dist/assets/*.css
# Attendu : < 15 KB après gzip

1. Ordre des classes (recommandé)

Utilisez Prettier avec le plugin Tailwind pour trier automatiquement :

Fenêtre de terminal
npm install -D prettier prettier-plugin-tailwindcss
// .prettierrc
{
"plugins": ["prettier-plugin-tailwindcss"]
}

Ordre logique : Layout → Spacing → Sizing → Typography → Colors → Effects

<button class="
flex items-center justify-center {# Layout #}
px-6 py-3 gap-2 {# Spacing #}
w-full {# Sizing #}
text-lg font-semibold {# Typography #}
bg-blue-600 text-white {# Colors #}
rounded-lg shadow-md {# Effects #}
hover:bg-blue-700 hover:shadow-lg {# States #}
transition-all duration-200 {# Transitions #}
">
Ajouter au panier
</button>

2. Composants vs Utilitaires

/* Créer un composant SI répété 3+ fois */
@layer components {
.badge {
@apply inline-flex items-center px-2 py-0.5;
@apply text-xs font-medium rounded-full;
}
.badge-primary {
@apply bg-blue-100 text-blue-800;
}
}
{# Utiliser directement les utilitaires pour des cas uniques #}
<div class="flex items-center justify-between p-4 bg-gray-50 rounded">
<!-- Contenu unique -->
</div>

3. Responsive - Mobile First

{# ❌ Mauvais - Desktop first #}
<div class="grid-cols-4 md:grid-cols-2 sm:grid-cols-1">
{# ✅ Bon - Mobile first #}
<div class="grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
{# Mobile: 1 col, Tablet: 2 col, Desktop: 3 col, Large: 4 col #}
</div>

1. Focus visible

<button class="
btn btn-primary
focus:ring-2 focus:ring-blue-500 focus:ring-offset-2
focus:outline-none
">
Action
</button>

2. Contraste des couleurs

/* Vérifier le contraste (WCAG AA = 4.5:1) */
@layer components {
.btn-primary {
@apply bg-blue-600 text-white; /* Contraste: 8.6:1 ✅ */
}
.text-muted {
@apply text-gray-600; /* Sur fond blanc: 5.7:1 ✅ */
}
}

3. Textes alternatifs et aria

{# Icônes avec texte caché #}
<button class="btn btn-primary">
<svg class="w-5 h-5" aria-hidden="true">...</svg>
<span class="sr-only">Ajouter au panier</span>
</button>
{# sr-only = screen reader only #}
/* Classe sr-only intégrée dans Tailwind */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
clip: rect(0, 0, 0, 0);
}

1. Documenter les composants personnalisés

/**
* Product Card Component
*
* Usage:
* <article class="product-card">
* <img class="product-card-image" />
* <div class="product-card-body">...</div>
* </article>
*
* Variants: .product-card-featured, .product-card-compact
*/
@layer components {
.product-card { /* ... */ }
}

2. Versionner les couleurs

@theme {
/* Version 1.0 - Couleurs initiales */
--color-primary-v1: #0073e6;
/* Version 2.0 - Nouveau branding (2026-01) */
--color-primary: #ff6b6b;
--color-primary-dark: #e85a5a;
--color-primary-light: #ff8787;
}
{#
/**
* @file
* TailStore page template with Tailwind CSS.
*/
#}
<div class="min-h-screen flex flex-col bg-light">
{# Header #}
<header class="bg-white shadow-sm sticky top-0 z-50">
<div class="container">
<div class="flex items-center justify-between h-16">
{# Logo #}
<div class="flex-shrink-0">
{{ page.header }}
</div>
{# Navigation principale #}
<nav class="hidden md:flex items-center space-x-8">
{{ page.primary_menu }}
</nav>
{# Actions header #}
<div class="flex items-center space-x-4">
{{ page.secondary_menu }}
{# Mobile menu button #}
<button
type="button"
class="md:hidden p-2 text-gray-600 hover:text-primary"
x-data
@click="$dispatch('toggle-mobile-menu')"
>
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
</svg>
</button>
</div>
</div>
</div>
</header>
{# Hero/Highlighted #}
{% if page.highlighted %}
<section class="relative">
{{ page.highlighted }}
</section>
{% endif %}
{# Breadcrumb #}
{% if page.breadcrumb %}
<nav class="bg-gray-50 border-b" aria-label="Breadcrumb">
<div class="container py-3">
{{ page.breadcrumb }}
</div>
</nav>
{% endif %}
{# Main content #}
<main class="flex-grow py-8" id="main-content">
<div class="container">
{{ page.help }}
<div class="{% if page.sidebar %}grid lg:grid-cols-4 gap-8{% endif %}">
{# Content #}
<div class="{% if page.sidebar %}lg:col-span-3{% endif %}">
{{ page.content }}
</div>
{# Sidebar #}
{% if page.sidebar %}
<aside class="lg:col-span-1 space-y-6">
{{ page.sidebar }}
</aside>
{% endif %}
</div>
{% if page.content_below %}
<div class="mt-12">
{{ page.content_below }}
</div>
{% endif %}
</div>
</main>
{# Footer #}
<footer class="bg-dark text-white">
{% if page.footer_top %}
<div class="container py-12">
<div class="grid md:grid-cols-4 gap-8">
{{ page.footer_top }}
</div>
</div>
{% endif %}
{% if page.footer_bottom %}
<div class="border-t border-white/10">
<div class="container py-6">
<div class="flex flex-col md:flex-row justify-between items-center gap-4 text-sm text-gray-400">
{{ page.footer_bottom }}
</div>
</div>
</div>
{% endif %}
</footer>
</div>
{#
/**
* @file
* Product card template with Tailwind CSS.
*/
#}
<article class="group card hover:-translate-y-1 transition-transform duration-200">
{# Badges #}
<div class="absolute top-2 left-2 z-10 flex gap-1">
{% if is_on_sale %}
<span class="badge badge-danger">-{{ discount_percent }}%</span>
{% endif %}
{% if node.isPromoted() %}
<span class="badge badge-primary">Nouveau</span>
{% endif %}
</div>
{# Quick actions #}
<div class="absolute top-2 right-2 z-10 flex flex-col gap-1 opacity-0 translate-x-2 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-200">
<button class="w-8 h-8 bg-white rounded-full shadow flex items-center justify-center text-gray-600 hover:text-primary hover:scale-110 transition-transform">
</button>
<button class="w-8 h-8 bg-white rounded-full shadow flex items-center justify-center text-gray-600 hover:text-primary hover:scale-110 transition-transform">
👁
</button>
</div>
{# Image #}
<a href="{{ url }}" class="block aspect-square overflow-hidden">
{% if content.field_images.0 %}
<img
src="{{ file_url(node.field_images.0.entity.fileuri) }}"
alt="{{ node.field_images.0.alt }}"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
>
{% else %}
<div class="w-full h-full bg-gray-100 flex items-center justify-center text-gray-400">
No image
</div>
{% endif %}
</a>
{# Info #}
<div class="card-body flex flex-col">
{# Brand #}
{% if node.field_brand.entity %}
<span class="text-xs uppercase tracking-wider text-gray-500">
{{ node.field_brand.entity.label }}
</span>
{% endif %}
{# Title #}
<h3 class="font-semibold text-gray-900 line-clamp-2 mt-1">
<a href="{{ url }}" class="hover:text-primary transition-colors">
{{ label }}
</a>
</h3>
{# Price #}
<div class="mt-auto pt-2">
{% if is_on_sale and old_price %}
<span class="text-sm text-gray-500 line-through mr-2">{{ old_price }}</span>
<span class="text-lg font-bold text-danger">{{ formatted_price }}</span>
{% else %}
<span class="text-lg font-bold text-gray-900">{{ formatted_price }}</span>
{% endif %}
</div>
{# Colors #}
{% if node.field_colors|length > 0 %}
<div class="flex gap-1 mt-2">
{% for color in node.field_colors|slice(0, 5) %}
<span
class="w-4 h-4 rounded-full border border-gray-200"
style="background-color: {{ color.entity.field_color_code.value }}"
title="{{ color.entity.label }}"
></span>
{% endfor %}
{% if node.field_colors|length > 5 %}
<span class="text-xs text-gray-500 self-center">+{{ node.field_colors|length - 5 }}</span>
{% endif %}
</div>
{% endif %}
</div>
{# Add to cart button #}
<div class="p-4 pt-0">
<button
class="btn btn-primary w-full"
data-product-id="{{ node.id }}"
{% if not in_stock %}disabled{% endif %}
>
{% if in_stock %}
Ajouter au panier
{% else %}
Indisponible
{% endif %}
</button>
</div>
</article>
Fenêtre de terminal
cd themes/custom/tailstore
npm run watch
Fenêtre de terminal
# Build minifié
npm run build
# Vérifier la taille
ls -lh dist/assets/

🔧 Intégration avec le formulaire exposé Views

Section intitulée « 🔧 Intégration avec le formulaire exposé Views »
{# views-exposed-form.html.twig #}
<form{{ attributes.addClass('flex flex-wrap gap-4 p-4 bg-gray-50 rounded-lg mb-8') }}>
{% for element in form %}
{% if element['#type'] is defined and element['#type'] != 'hidden' %}
<div class="flex-1 min-w-[150px]">
{% if element['#title'] %}
<label class="form-label">{{ element['#title'] }}</label>
{% endif %}
{{ element|merge({'#attributes': {'class': ['form-input']}}) }}
</div>
{% endif %}
{% endfor %}
<div class="flex gap-2 items-end">
<button type="submit" class="btn btn-primary">
Filtrer
</button>
{% if form['#info'] is defined and form['#info']['filter'] is defined %}
<a href="{{ path('<current>') }}" class="btn btn-outline">
Réinitialiser
</a>
{% endif %}
</div>
</form>

Tailwind v4 purge automatiquement les classes non utilisées. Assurez-vous que tous les templates sont listés dans content.

Si vous générez des classes dynamiquement, utilisez la syntaxe complète :

{# ❌ Ne fonctionne pas - classe coupée #}
<div class="bg-{{ color }}">
{# ✅ Fonctionne - classe complète dans le code #}
{% set color_classes = {
'red': 'bg-red-500',
'blue': 'bg-blue-500',
'green': 'bg-green-500',
} %}
<div class="{{ color_classes[color] }}">

Pour les classes générées dynamiquement, utilisez des noms de classes complets plutôt que des concaténations. Si vous avez besoin de classes vraiment dynamiques, considérez l’approche des variants safelist dans votre fichier CSS avec @theme.

  1. Vérifier l’installation des dépendances

    Fenêtre de terminal
    cd themes/custom/tailstore
    ls node_modules/@tailwindcss/vite node_modules/vite node_modules/tailwindcss

    Attendu : Les 3 dossiers existent

  2. Vérifier vite.config.js

    Fenêtre de terminal
    cat vite.config.js | grep "@tailwindcss/vite"

    Attendu : import tailwindcss from '@tailwindcss/vite'

  3. Vérifier assets/style.css

    Fenêtre de terminal
    cat assets/style.css | head -5

    Attendu : @import "tailwindcss"; présent

  4. Tester le build

    Fenêtre de terminal
    npm run build
    ls -lh dist/assets/

    Attendu : Fichier main-*.css créé (< 20 KB)

  5. Vérifier la librairie Drupal

    Fenêtre de terminal
    cat tailstore.libraries.yml | grep -A 5 "global:"

    Attendu : Référence à dist/assets/main-*.css

  6. Tester dans le navigateur

    Fenêtre de terminal
    drush cr
    # Ouvrir une page, F12 → Network → Filtrer "css"

    Attendu : Le fichier CSS Tailwind est chargé (200 OK)

  7. Vérifier les classes appliquées

    • Inspecter un élément (F12)
    • Chercher une classe Tailwind (ex: bg-blue-600) Attendu : Les styles sont appliqués
  • Les classes Tailwind sont appliquées (couleurs, espacements)
  • Les composants personnalisés fonctionnent (.btn, .card)
  • Le responsive fonctionne (tester mobile, tablette, desktop)
  • Les variables @theme sont accessibles
  • Le mode watch fonctionne (npm run dev)
  • Le build production est optimisé (< 15 KB après gzip)
  • Pas de classes inutilisées dans le CSS final
  • Les templates Twig utilisent les classes Tailwind
  • Taille CSS production < 15 KB
  • Temps de build < 2 secondes
  • Hot Module Replacement (HMR) fonctionne en dev
  • Aucune erreur console liée aux styles
#!/bin/bash
echo "=== Validation Tailwind CSS ==="
cd themes/custom/tailstore
# 1. Vérifier l'installation
echo "\n📦 Dépendances :"
npm list @tailwindcss/vite tailwindcss vite 2>&1 | grep -E "@tailwindcss|tailwindcss@|vite@"
# 2. Vérifier la config
echo "\n⚙️ Configuration :"
if [ -f "vite.config.js" ]; then
echo "✅ vite.config.js existe"
else
echo "❌ vite.config.js manquant"
fi
# 3. Vérifier le CSS source
echo "\n🎨 Fichier CSS source :"
if grep -q "@import.*tailwindcss" assets/style.css 2>/dev/null; then
echo "✅ @import tailwindcss trouvé"
else
echo "❌ @import tailwindcss manquant"
fi
# 4. Build et vérifier la taille
echo "\n🏗️ Build production :"
npm run build > /dev/null 2>&1
if [ -d "dist/assets" ]; then
echo "✅ Build réussi"
ls -lh dist/assets/*.css | awk '{print " Taille: " $5}'
else
echo "❌ Build échoué"
fi
# 5. Vérifier la déclaration Drupal
echo "\n📚 Librairie Drupal :"
if grep -q "dist/assets" tailstore.libraries.yml 2>/dev/null; then
echo "✅ Librairie déclarée"
else
echo "❌ Librairie non déclarée"
fi
echo "\n✅ Validation terminée"

Temps estimé total : 2-3 heures pour configuration + conversion des premiers templates

Erreur 1 : “Cannot find module ‘@tailwindcss/vite’”

Section intitulée « Erreur 1 : “Cannot find module ‘@tailwindcss/vite’” »

Symptôme : Erreur au lancement de npm run dev

Cause : Dépendances non installées

Solution :

Fenêtre de terminal
cd themes/custom/tailstore
rm -rf node_modules package-lock.json
npm install

Symptôme : Le CSS est chargé mais les classes ne fonctionnent pas

Cause : CSS non rebuilder après modification des templates

Solution :

Fenêtre de terminal
# 1. Rebuilder le CSS
npm run build
# 2. Vider le cache Drupal
drush cr
# 3. Vider le cache navigateur (Ctrl+Shift+R)

Symptôme : Le fichier dist/assets/main-*.css fait plus de 50 KB

Cause : La purge ne fonctionne pas correctement

Solution :

// vite.config.js - Spécifier les fichiers à scanner
export default defineConfig({
plugins: [
tailwindcss({
content: [
'./templates/**/*.twig',
'./templates/**/*.html.twig',
],
}),
],
})
Fenêtre de terminal
# Rebuilder
npm run build
ls -lh dist/assets/main-*.css

Erreur 4 : “Failed to load config from vite.config.js”

Section intitulée « Erreur 4 : “Failed to load config from vite.config.js” »

Symptôme : Erreur de syntaxe dans vite.config.js

Cause : Import incorrect ou syntaxe invalide

Solution :

// ❌ Mauvais
import tailwindcss from '@tailwindcss/vite'
export default {
plugins: [tailwindcss()],
}
// ✅ Bon
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
build: {
outDir: 'dist',
},
})

Symptôme : Classe générée dynamiquement ne fonctionne pas

Cause : Tailwind ne voit pas la classe complète dans le code

Solution :

{# ❌ Ne fonctionne pas - classe dynamique #}
<div class="bg-{{ color }}-500">
{# ✅ Fonctionne - mapping explicite #}
{% set color_map = {
'red': 'bg-red-500',
'blue': 'bg-blue-500',
'green': 'bg-green-500',
} %}
<div class="{{ color_map[color] }}">
{# Ou utiliser style inline pour les couleurs vraiment dynamiques #}
<div style="background-color: {{ color }};">

Symptôme : Modifications CSS non visibles en temps réel

Cause : Vite dev server non lancé

Solution :

Fenêtre de terminal
# Terminal 1 : Vite watch
cd themes/custom/tailstore
npm run dev
# Terminal 2 : DDEV (Drupal)
ddev start
# Les modifications CSS sont maintenant instantanées

Symptôme : Erreur de parsing CSS

Cause : Mauvaise syntaxe dans @theme

Solution :

/* ❌ Mauvais */
@theme {
color-primary: #0073e6; /* Manque -- */
}
/* ✅ Bon */
@theme {
--color-primary: #0073e6;
--color-secondary: #6c757d;
}
  • Tailwind CSS IntelliSense (VS Code) : Autocomplétion des classes
  • Headwind (VS Code) : Tri automatique des classes
  • Prettier + plugin Tailwind : Formatage cohérent
  • Tailwind Play : Playground en ligne pour tester

Tailwind est intégré ! Ajoutons maintenant Alpine.js pour les interactions.