Intégrez Tailwind CSS 4 dans Angular 19+ : installation, configuration @theme en CSS natif, composants stylisés, dark mode avec @dark et bonnes pratiques.
Tailwind CSS 4 : ce qui change tout
Tailwind CSS 4, sorti en janvier 2025, est une réécriture complète du framework avec un nouveau moteur nommé Oxide, écrit en Rust. Ce changement radical transforme radicalement l'expérience d'intégration dans un projet Angular. Fini le fichier de configuration JavaScript complexe — tout se définit désormais directement dans votre CSS via des variables natives.
content à configurer), et une intégration native avec les variables CSS du navigateur.
Les changements majeurs par rapport à Tailwind CSS 3 :
| Fonctionnalité | Tailwind CSS 3 | Tailwind CSS 4 |
|---|---|---|
| Configuration | tailwind.config.js (JS) | @theme dans styles.css (CSS natif) |
| Moteur | Moteur JS (Lightning CSS) | Oxide (Rust) — 5x plus rapide |
| Détection contenu | Array content: [] requis | Automatique (scan intelligent) |
| Variables CSS | Pas d'exposition native | Design tokens en variables CSS |
| Dark mode | darkMode: 'class' en config JS | Variante @dark native en CSS |
| Import | @tailwind base/components/utilities | @import "tailwindcss" (une ligne) |
Tailwind CSS 4 dans l'écosystème Angular 2026
Angular 19 et 20 utilisent esbuild comme bundler par défaut (via le builder Angular DevKit), avec une compatibilité Vite pour le serveur de développement. Tailwind CSS 4 s'intègre parfaitement grâce au plugin officiel @tailwindcss/vite, qui s'accroche au pipeline de compilation d'Angular.
Cette intégration est plus simple qu'avec Tailwind 3, qui nécessitait une configuration PostCSS manuelle parfois source de conflits avec le pipeline Angular. Avec Tailwind 4, le plugin Vite gère tout automatiquement.
// angular.json — comparaison avant/après
// ❌ AVANT (Tailwind 3) : configuration PostCSS dans angular.json
{
"projects": {
"mon-app": {
"architect": {
"build": {
"options": {
// Configuration PostCSS requise séparément
"postcssrc": "postcss.config.js"
}
}
}
}
}
}
// ✅ APRÈS (Tailwind 4) : plugin directement dans les plugins
// Plus de fichier postcss.config.js nécessaire
Installation dans Angular 19+
L'installation de Tailwind CSS 4 dans Angular 19+ est significativement simplifiée par rapport aux versions précédentes. Voici le processus complet, étape par étape, pour un projet Angular utilisant le builder esbuild (défaut depuis Angular 17).
Prérequis
- Angular CLI 19+ (
ng versionpour vérifier) - Node.js 20+ (LTS recommandé)
- Un projet Angular initialisé avec
ng new mon-app
Étape 1 — Installer les paquets
# Installer Tailwind CSS 4 et le plugin Vite officiel
npm install tailwindcss @tailwindcss/vite
# Vérifier les versions installées
npx tailwindcss --version
# → v4.x.x
autoprefixer ni postcss séparément — le moteur Oxide les intègre nativement.
Étape 2 — Configurer angular.json
Ajoutez le plugin Tailwind dans la section plugins du builder Angular. Cette section est disponible depuis Angular 17 avec le builder esbuild :
// angular.json — ajouter le plugin Tailwind
{
"projects": {
"mon-app": {
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/mon-app",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"styles": ["src/styles.css"],
"scripts": [],
// ✅ Ajouter cette section plugins
"plugins": [
{
"name": "@tailwindcss/vite"
}
]
}
}
}
}
}
}
Étape 3 — Importer Tailwind dans styles.css
Remplacez le contenu de votre fichier src/styles.css par l'import Tailwind 4 :
/* src/styles.css */
/* ✅ Tailwind CSS 4 — un seul import suffit */
@import "tailwindcss";
/* Vos styles globaux Angular après */
:root {
/* Variables CSS de votre application */
--af-primary: #3b82f6;
}
@tailwind base, @tailwind components, @tailwind utilities. Un seul @import "tailwindcss" les remplace tous.
Étape 4 — Vérifier l'installation
Lancez le serveur de développement et vérifiez que les classes Tailwind fonctionnent dans un composant :
ng serve
<!-- app.component.html — test rapide -->
<div class="flex items-center justify-center min-h-screen bg-gray-50">
<div class="bg-white rounded-2xl shadow-lg p-8 max-w-md w-full">
<h1 class="text-2xl font-bold text-gray-900 mb-4">
Angular + Tailwind CSS 4 ✓
</h1>
<p class="text-gray-600 text-sm">
L'intégration fonctionne correctement.
</p>
</div>
</div>
Si vous voyez une carte centrée avec ombre et coins arrondis, l'installation est réussie. Le moteur Oxide détecte automatiquement toutes les classes utilisées dans vos templates .html et .ts Angular sans aucune configuration supplémentaire.
Dépannage courant
| Problème | Cause | Solution |
|---|---|---|
| Classes non appliquées | Plugin non chargé | Vérifier angular.json → plugins |
| Conflit avec styles existants | Preflight Tailwind réinitialise tout | Utiliser @layer base pour surcharger |
| Build prod plus lent | Analyse de tous les fichiers | Configurer @source pour restreindre |
| Erreur "Cannot find module" | Version Node.js trop ancienne | Mettre à jour Node.js 20+ |
Configuration avec @theme et CSS natif
L'une des révolutions de Tailwind CSS 4 est l'abandon total du fichier tailwind.config.js au profit de la directive @theme dans votre CSS. Vos design tokens (couleurs, typographie, espacements) deviennent des variables CSS natives, accessibles partout — dans vos styles, dans vos templates Angular, et même en JavaScript.
Définir votre design system avec @theme
/* src/styles.css */
@import "tailwindcss";
/* ✅ Définir votre thème via @theme — remplace tailwind.config.js */
@theme {
/* Palette de couleurs personnalisée */
--color-brand-50: #eff6ff;
--color-brand-100: #dbeafe;
--color-brand-200: #bfdbfe;
--color-brand-500: #3b82f6;
--color-brand-600: #2563eb;
--color-brand-700: #1d4ed8;
--color-brand-900: #1e3a8a;
/* Couleurs sémantiques de l'application */
--color-primary: var(--color-brand-600);
--color-primary-hover: var(--color-brand-700);
--color-surface: #ffffff;
--color-surface-alt: #f8fafc;
/* Typographie */
--font-sans: 'Manrope', system-ui, sans-serif;
--font-mono: 'Fira Code', 'JetBrains Mono', monospace;
/* Tailles de police personnalisées */
--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
--text-3xl: 2rem;
/* Espacements personnalisés */
--spacing-18: 4.5rem;
--spacing-22: 5.5rem;
/* Border radius */
--radius-card: 0.75rem;
--radius-btn: 0.375rem;
/* Ombres */
--shadow-card: 0 1px 3px rgb(0 0 0 / 0.1), 0 1px 2px rgb(0 0 0 / 0.06);
--shadow-card-hover: 0 10px 25px rgb(0 0 0 / 0.15);
}
Ces tokens génèrent automatiquement les classes utilitaires Tailwind correspondantes. Par exemple, --color-brand-500 crée les classes text-brand-500, bg-brand-500, border-brand-500.
Utiliser les tokens dans vos templates
<!-- ✅ Classes générées depuis @theme -->
<button class="bg-brand-600 hover:bg-brand-700 text-white px-4 py-2 rounded-btn">
Action principale
</button>
<!-- Accès direct aux variables CSS (en style ou en CSS de composant) -->
<div [style.color]="'var(--color-primary)'">
Couleur primaire dynamique
</div>
Restreindre le scan avec @source
Par défaut, le moteur Oxide scanne tous les fichiers du projet. Pour les projets larges, vous pouvez restreindre le scan pour accélérer le build :
/* src/styles.css */
@import "tailwindcss";
/* Restreindre le scan aux fichiers Angular */
@source "./src/**/*.{html,ts,css}";
/* Exclure les dossiers inutiles */
@source not "./src/**/*.spec.ts";
@source not "./node_modules";
@theme {
/* ... vos tokens */
}
Composants réutilisables avec @layer
Pour créer des classes composants réutilisables (équivalent des plugins Tailwind 3), utilisez la directive @layer components :
/* src/styles.css — composants réutilisables */
@layer components {
/* Bouton primaire Angular réutilisable */
.btn-primary {
@apply bg-brand-600 hover:bg-brand-700 text-white font-semibold;
@apply px-4 py-2 rounded-btn transition-colors duration-200;
@apply focus:outline-none focus:ring-2 focus:ring-brand-500 focus:ring-offset-2;
@apply disabled:opacity-50 disabled:cursor-not-allowed;
}
/* Card standard */
.card-default {
@apply bg-surface rounded-card shadow-card p-6;
@apply border border-gray-100;
}
/* Badge de status */
.badge-success {
@apply inline-flex items-center gap-1.5 px-2.5 py-0.5;
@apply text-xs font-medium text-green-700 bg-green-100 rounded-full;
}
}
Composants Angular stylisés avec Tailwind
Intégrer Tailwind CSS 4 dans des composants Angular modernes (standalone, signal-based) suit des patterns spécifiques. La question clé est celle de l'encapsulation des vues : Angular encapsule par défaut les styles de chaque composant, ce qui impacte l'application des classes Tailwind.
Encapsulation et Tailwind : les options
| Strategy | Effet sur Tailwind | Quand l'utiliser |
|---|---|---|
Emulated (défaut) |
Classes Tailwind fonctionnent ✓ | La plupart des cas |
None |
Styles globaux + Tailwind ✓ | Composants avec contenu projeté |
ShadowDom |
⚠️ Tailwind global ne pénètre pas | Web Components isolés uniquement |
Composant Card avec Tailwind CSS 4
// src/app/shared/components/article-card/article-card.component.ts
import { Component, input, computed } from '@angular/core';
import { CommonModule } from '@angular/common';
export interface ArticleCardData {
title: string;
description: string;
tags: string[];
imageUrl: string;
date: string;
readTime: number;
}
@Component({
selector: 'app-article-card',
standalone: true,
imports: [CommonModule],
// ✅ Classes Tailwind directement dans le template
template: `
<article class="card-default group hover:shadow-card-hover transition-shadow duration-300 cursor-pointer">
<!-- Image avec ratio fixe -->
<div class="relative overflow-hidden rounded-lg mb-4 aspect-video bg-gray-100">
<img
[src]="article().imageUrl"
[alt]="article().title"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
loading="lazy"
>
</div>
<!-- Tags -->
<div class="flex flex-wrap gap-1.5 mb-3">
@for (tag of article().tags.slice(0, 3); track tag) {
<span class="badge-success">{{ tag }}</span>
}
</div>
<!-- Titre -->
<h2 class="text-lg font-bold text-gray-900 mb-2 line-clamp-2 group-hover:text-brand-600 transition-colors">
{{ article().title }}
</h2>
<!-- Description -->
<p class="text-sm text-gray-600 line-clamp-3 mb-4">
{{ article().description }}
</p>
<!-- Footer -->
<footer class="flex items-center justify-between text-xs text-gray-400 pt-3 border-t border-gray-100">
<time>{{ formattedDate() }}</time>
<span class="flex items-center gap-1">
⏱ {{ article().readTime }} min de lecture
</span>
</footer>
</article>
`,
})
export class ArticleCardComponent {
// ✅ Signal inputs Angular 19+
article = input.required<ArticleCardData>();
// ✅ Computed signal pour le formatage de date
formattedDate = computed(() => {
const date = new Date(this.article().date);
return date.toLocaleDateString('fr-FR', {
day: 'numeric',
month: 'long',
year: 'numeric',
});
});
}
Composant formulaire réactif avec Tailwind
// src/app/shared/components/contact-form/contact-form.component.ts
import { Component, signal } from '@angular/core';
import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';
@Component({
selector: 'app-contact-form',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<form
[formGroup]="form"
(ngSubmit)="onSubmit()"
class="space-y-4"
>
<!-- Champ nom -->
<div>
<label for="name" class="block text-sm font-medium text-gray-700 mb-1">
Nom complet
</label>
<input
id="name"
type="text"
formControlName="name"
class="w-full px-3 py-2 border border-gray-300 rounded-btn text-sm
focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-brand-500
disabled:bg-gray-50 disabled:text-gray-500
placeholder:text-gray-400"
placeholder="Jean Dupont"
[class.border-red-400]="isFieldInvalid('name')"
>
@if (isFieldInvalid('name')) {
<p class="mt-1 text-xs text-red-600">Le nom est requis.</p>
}
</div>
<!-- Champ email -->
<div>
<label for="email" class="block text-sm font-medium text-gray-700 mb-1">
Email
</label>
<input
id="email"
type="email"
formControlName="email"
class="w-full px-3 py-2 border border-gray-300 rounded-btn text-sm
focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-brand-500"
placeholder="jean@exemple.fr"
[class.border-red-400]="isFieldInvalid('email')"
>
</div>
<!-- Bouton soumission -->
<button
type="submit"
[disabled]="form.invalid || isSubmitting()"
class="btn-primary w-full"
>
@if (isSubmitting()) {
<span class="inline-flex items-center gap-2">
<span class="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin"></span>
Envoi en cours...
</span>
} @else {
Envoyer le message
}
</button>
</form>
`,
})
export class ContactFormComponent {
isSubmitting = signal(false);
constructor(private fb: FormBuilder) {}
form = this.fb.group({
name: ['', [Validators.required, Validators.minLength(2)]],
email: ['', [Validators.required, Validators.email]],
});
isFieldInvalid(field: string): boolean {
const control = this.form.get(field);
// Afficher l'erreur seulement après interaction
return !!(control?.invalid && control?.touched);
}
onSubmit(): void {
if (this.form.invalid) return;
this.isSubmitting.set(true);
// ... logique d'envoi
}
}
Dark mode natif avec @dark
Tailwind CSS 4 introduit la variante @dark qui s'appuie sur la media query CSS native prefers-color-scheme ou sur un attribut HTML data-theme. Angular s'intègre parfaitement avec cette approche grâce aux signals.
Configurer le dark mode dans @theme
/* src/styles.css */
@import "tailwindcss";
/* ✅ Configuration dark mode — via data-theme ou prefers-color-scheme */
@custom-variant dark (&[data-theme="dark"], [data-theme="dark"] &);
@theme {
/* Couleurs light (défaut) */
--color-surface: #ffffff;
--color-surface-alt: #f8fafc;
--color-text-primary: #111827;
--color-text-secondary: #6b7280;
--color-border: #e5e7eb;
}
/* Tokens dark mode — surchargeables */
@layer base {
[data-theme="dark"] {
--color-surface: #111827;
--color-surface-alt: #1f2937;
--color-text-primary: #f9fafb;
--color-text-secondary: #9ca3af;
--color-border: #374151;
}
}
Service Angular pour gérer le thème
// src/app/core/services/theme.service.ts
import { Injectable, signal, effect, inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
@Injectable({ providedIn: 'root' })
export class ThemeService {
private platformId = inject(PLATFORM_ID);
// ✅ Signal pour le thème courant
currentTheme = signal<'light' | 'dark'>(this.getInitialTheme());
constructor() {
// ✅ Effect : synchronise le DOM avec le signal
effect(() => {
if (isPlatformBrowser(this.platformId)) {
document.documentElement.setAttribute(
'data-theme',
this.currentTheme()
);
// Persister le choix utilisateur
localStorage.setItem('af-theme', this.currentTheme());
}
});
}
toggle(): void {
this.currentTheme.update(t => t === 'light' ? 'dark' : 'light');
}
setTheme(theme: 'light' | 'dark'): void {
this.currentTheme.set(theme);
}
private getInitialTheme(): 'light' | 'dark' {
if (typeof window === 'undefined') return 'light';
// Priorité 1 : préférence sauvegardée
const saved = localStorage.getItem('af-theme');
if (saved === 'light' || saved === 'dark') return saved;
// Priorité 2 : préférence système
return window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark' : 'light';
}
}
Composant toggle de thème
// src/app/shared/components/theme-toggle/theme-toggle.component.ts
import { Component, inject, computed } from '@angular/core';
import { ThemeService } from '@core/services/theme.service';
@Component({
selector: 'app-theme-toggle',
standalone: true,
template: `
<button
(click)="themeService.toggle()"
[attr.aria-label]="ariaLabel()"
[attr.title]="ariaLabel()"
class="p-2 rounded-full text-gray-500 dark:text-gray-400
hover:bg-gray-100 dark:hover:bg-gray-800
transition-colors duration-200 focus:outline-none
focus:ring-2 focus:ring-brand-500"
>
@if (themeService.currentTheme() === 'dark') {
<!-- Icône soleil (mode clair) -->
☀️
} @else {
<!-- Icône lune (mode sombre) -->
🌙
}
</button>
`,
})
export class ThemeToggleComponent {
themeService = inject(ThemeService);
ariaLabel = computed(() =>
this.themeService.currentTheme() === 'dark'
? 'Passer en mode clair'
: 'Passer en mode sombre'
);
}
isPlatformBrowser() avant d'accéder à localStorage ou document. Avec Angular SSR (Angular Universal), le code s'exécute aussi côté serveur où ces APIs n'existent pas.
Coexistence avec Angular Material
Angular Material et Tailwind CSS 4 peuvent coexister dans le même projet. La stratégie recommandée est de déléguer : Tailwind gère le layout, l'espacement, la typographie et les utilitaires ; Angular Material fournit les composants UI accessibles (Dialog, Table, DatePicker, etc.).
Éviter les conflits CSS
Le principal point de friction est le preflight de Tailwind (reset CSS global) qui peut entrer en conflit avec les styles de base d'Angular Material. Voici comment les harmoniser :
/* src/styles.css */
@import "tailwindcss";
/* ✅ Désactiver le preflight de Tailwind si Material gère les bases */
/* Option 1 : utiliser @layer pour ne pas écraser Material */
@layer base {
/* Vos reset personnalisés (ne touchez pas aux styles Angular Material) */
*, *::before, *::after {
box-sizing: border-box;
}
html {
/* Font définie via @theme, pas via Tailwind preflight */
font-family: var(--font-sans);
}
}
/* ✅ Étendre les tokens de Material avec @theme */
@theme {
/* Aligner les couleurs Tailwind sur le thème Material */
--color-primary: var(--mdc-theme-primary, #3b82f6);
--color-on-primary: var(--mdc-theme-on-primary, #ffffff);
--color-surface: var(--mat-app-background-color, #ffffff);
}
Pattern hybride : layout Tailwind + composants Material
<!-- Grille Tailwind, composants Angular Material -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 p-6">
@for (item of items(); track item.id) {
<!-- Card avec Tailwind -->
<div class="bg-surface rounded-card shadow-card p-4 flex flex-col gap-4">
<!-- Titre avec classe Tailwind -->
<h3 class="text-base font-semibold text-gray-900">{{ item.title }}</h3>
<!-- Chip Angular Material intégré dans le layout Tailwind -->
<div class="flex flex-wrap gap-2">
@for (tag of item.tags; track tag) {
<mat-chip-option [selected]="false">{{ tag }}</mat-chip-option>
}
</div>
<!-- Bouton Material, positionné en bas via flex -->
<div class="mt-auto pt-3 border-t border-gray-100">
<button mat-flat-button color="primary" class="w-full">
Voir le détail
</button>
</div>
</div>
}
</div>
Optimiser les performances en production
Tailwind CSS 4 est conçu pour la performance. Le moteur Oxide analyse statiquement vos templates et n'inclut que les classes réellement utilisées. En production, votre CSS généré est souvent inférieur à 10 KB (contre 3 MB pour le CSS complet de Tailwind non purgé).
Build de production Angular
ng build --configuration=production
Le pipeline de build Angular avec Tailwind 4 exécute automatiquement :
- Scan des templates : le moteur Oxide identifie toutes les classes Tailwind utilisées dans
.html,.ts - Génération CSS : seules les classes identifiées sont générées
- Minification : le CSS généré est minifié par esbuild
- Compresssion : Brotli/gzip via Angular CLI ou votre serveur
Analyser la taille du CSS généré
# Analyser le CSS de production
ng build --configuration=production --stats-json
# Ou avec source-map-explorer
npx source-map-explorer dist/mon-app/browser/*.css
# Résultat typique avec Tailwind 4 bien configuré :
# CSS total : ~8-15 KB (gzip) vs 3MB+ sans purge
Classes dynamiques et liste de classes sécurisée
Un piège fréquent avec Tailwind dans Angular : les classes construites dynamiquement ne sont pas détectées par le scanner statique.
// ❌ PROBLÈME : class dynamique non détectée au build
const status = 'success';
const className = `text-${status}-600`; // 'text-success-600' non inclus dans le CSS !
// ✅ SOLUTION 1 : mapping explicite (recommandé)
const STATUS_CLASSES: Record<string, string> = {
success: 'text-green-600 bg-green-50 border-green-200',
error: 'text-red-600 bg-red-50 border-red-200',
warning: 'text-yellow-600 bg-yellow-50 border-yellow-200',
info: 'text-blue-600 bg-blue-50 border-blue-200',
};
// ✅ SOLUTION 2 : safelist dans styles.css (pour classes vraiment dynamiques)
@source unsafe inline './src/app/**/*.ts';
Checklist performance Tailwind 4 en production
- ✅ Aucun
@source unsafeinutile (inclut trop de CSS) - ✅ Classes dynamiques mappées explicitement en TypeScript
- ✅
@tailwindcss/typographychargé seulement si utilisé - ✅ CSS final analysé avec source-map-explorer
- ✅ Compression Brotli/gzip activée côté serveur
- ✅ Police chargée via
font-display: swap - ✅ Thème personnalisé minifié dans le bundle final
Bonnes pratiques et patterns avancés
Voici les patterns avancés qui distinguent une intégration Tailwind CSS 4 + Angular de qualité production d'une simple expérimentation. Ces recommandations viennent de retours d'expériences sur des applications Angular d'entreprise.
1. Créer un design system cohérent
Centralisez tous vos tokens dans styles.css et créez des composants Angular qui encapsulent les classes Tailwind :
// src/app/shared/components/ui/button/button.component.ts
import { Component, input, output } from '@angular/core';
type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger';
type ButtonSize = 'sm' | 'md' | 'lg';
@Component({
selector: 'app-button',
standalone: true,
template: `
<button
[type]="type()"
[disabled]="disabled()"
[class]="computedClasses()"
(click)="clicked.emit()"
>
<ng-content />
</button>
`,
})
export class ButtonComponent {
variant = input<ButtonVariant>('primary');
size = input<ButtonSize>('md');
type = input<'button' | 'submit' | 'reset'>('button');
disabled = input(false);
clicked = output();
// ✅ Classes calculées une seule fois par signal
computedClasses = computed(() => {
const base = 'inline-flex items-center justify-center font-semibold rounded-btn transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed';
const variants = {
primary: 'bg-brand-600 text-white hover:bg-brand-700 focus:ring-brand-500',
secondary: 'bg-gray-100 text-gray-800 hover:bg-gray-200 focus:ring-gray-400',
ghost: 'text-brand-600 hover:bg-brand-50 focus:ring-brand-500',
danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500',
};
const sizes = {
sm: 'px-3 py-1.5 text-sm gap-1.5',
md: 'px-4 py-2 text-sm gap-2',
lg: 'px-6 py-3 text-base gap-2.5',
};
return `${base} ${variants[this.variant()]} ${sizes[this.size()]}`;
});
}
2. Variables CSS Angular + Tailwind pour le theming multi-tenant
// src/app/core/services/branding.service.ts
import { Injectable, inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
interface BrandConfig {
primaryColor: string;
primaryDark: string;
fontFamily: string;
}
@Injectable({ providedIn: 'root' })
export class BrandingService {
private platformId = inject(PLATFORM_ID);
applyBranding(config: BrandConfig): void {
if (!isPlatformBrowser(this.platformId)) return;
const root = document.documentElement;
// ✅ Surcharger les variables CSS Tailwind @theme dynamiquement
root.style.setProperty('--color-primary', config.primaryColor);
root.style.setProperty('--color-primary-hover', config.primaryDark);
root.style.setProperty('--font-sans', config.fontFamily);
}
resetBranding(): void {
if (!isPlatformBrowser(this.platformId)) return;
const root = document.documentElement;
root.style.removeProperty('--color-primary');
root.style.removeProperty('--color-primary-hover');
root.style.removeProperty('--font-sans');
}
}
3. Responsive design avec les variants Tailwind
<!-- ✅ Grid responsive sans media query manuelle -->
<div class="
grid
grid-cols-1
sm:grid-cols-2
lg:grid-cols-3
xl:grid-cols-4
gap-4
sm:gap-6
lg:gap-8
">
@for (article of articles(); track article.id) {
<app-article-card [article]="article" />
}
</div>
<!-- ✅ Typographie responsive -->
<h1 class="text-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900">
Titre de la page
</h1>
<!-- ✅ Navigation mobile-first -->
<nav class="
flex flex-col
sm:flex-row sm:items-center sm:gap-6
gap-3 py-4
">
...
</nav>
4. Animations et transitions CSS natives
/* styles.css — animations personnalisées compatibles reduced-motion */
@layer utilities {
.animate-fade-in {
animation: fade-in 0.3s ease-out forwards;
}
.animate-slide-up {
animation: slide-up 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
}
@keyframes fade-in {
from { opacity: 0; transform: translateY(4px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes slide-up {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
/* ✅ Respecter prefers-reduced-motion — accessibilité */
@media (prefers-reduced-motion: reduce) {
.animate-fade-in,
.animate-slide-up {
animation: none;
}
}
Tailwind CSS 4 est la solution CSS la plus efficace pour les projets Angular modernes. Grâce au moteur Oxide, à l'intégration Vite native et aux design tokens CSS, vous obtenez un workflow rapide, un bundle CSS minimal en production, et une cohérence visuelle garantie. Combinez-le avec les Signal Components Angular 19+ pour une architecture front-end optimale.
Checklist finale : intégration Angular + Tailwind CSS 4
- ✅
@tailwindcss/viteajouté dans les plugins angular.json - ✅
@import "tailwindcss"dans styles.css (une seule ligne) - ✅ Design tokens définis dans
@theme(pas dans tailwind.config.js) - ✅ Composants Angular standalone avec classes Tailwind dans le template
- ✅ Dark mode géré via ThemeService + signal + data-theme
- ✅ Classes dynamiques mappées explicitement (pas de concaténation de strings)
- ✅ Coexistence Angular Material documentée si utilisée
- ✅ Build production vérifié : CSS < 15 KB (gzip)
- ✅ Animations respectant prefers-reduced-motion
- ✅ Test accessibilité : focus rings visibles sur tous les éléments interactifs