Construisez un thème PrimeNG sur mesure avec Aura : design tokens, definePreset, palettes, dark mode natif, CSS variables et pass-through par instance Angular.
Lara vs Aura : le grand virage PrimeNG 18
PrimeNG 18 (sortie fin 2025) marque un changement majeur dans la gestion du theming. L'ancien système Lara reposait sur des fichiers CSS pré-compilés (un par variante de couleur et de mode clair/sombre). Le nouveau système Aura repose sur des design tokens TypeScript résolus à l'exécution — palettes calculées, dark mode natif, branding personnalisable sans recompilation.
Comparatif des deux approches
| Critère | Lara (legacy) | Aura (PrimeNG 18+) |
|---|---|---|
| Configuration | Fichier CSS importé | Objet TypeScript via providePrimeNG |
| Dark mode | Charger 2 CSS séparés | Natif via darkModeSelector |
| Branding custom | Override CSS lourd | definePreset() + tokens |
| Palette générée | Variantes 50→900 manuelles | Auto depuis primary + surface |
| Changement runtime | Reload des CSS | Mise à jour à chaud |
| Bundle CSS | ~ 350 ko (theme + primeng) | ~ 0 — tout est en TS |
Aura : configuration et design tokens
Installation
# PrimeNG 18+ et le module thèmes
npm install primeng @primeng/themes primeicons
npm install @angular/animations
Configurer providePrimeNG dans app.config.ts
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideAnimations } from '@angular/platform-browser/animations';
import { providePrimeNG } from 'primeng/config';
import Aura from '@primeng/themes/aura';
export const appConfig: ApplicationConfig = {
providers: [
provideAnimations(),
providePrimeNG({
theme: {
preset: Aura, // base de design tokens
options: {
prefix: 'p', // préfixe des CSS variables : --p-*
darkModeSelector: '.p-dark', // classe à appliquer pour le dark mode
cssLayer: false, // active @layer pour gérer la spécificité
},
},
ripple: true, // effet ripple Material-like
}),
],
};
angular.json — n'importer que primeicons
// Plus aucun import CSS de thème dans angular.json avec Aura
"styles": [
"node_modules/primeicons/primeicons.css",
"src/styles.css"
]
Créer un preset à partir d'Aura
Le rôle d'un preset est de définir vos couleurs de marque, espacements, rayons, ombres et typographie. definePreset() étend Aura et n'écrase que les valeurs que vous personnalisez — le reste reste cohérent avec la base.
Preset complet — branding "AfaCorp"
// app/themes/afa-corp.preset.ts
import { definePreset } from '@primeng/themes';
import Aura from '@primeng/themes/aura';
export const AfaCorpPreset = definePreset(Aura, {
semantic: {
// Palette principale : 11 nuances de votre couleur de marque
primary: {
50: '#eff6ff',
100: '#dbeafe',
200: '#bfdbfe',
300: '#93c5fd',
400: '#60a5fa',
500: '#3b82f6', // teinte de référence (boutons, liens)
600: '#2563eb',
700: '#1d4ed8',
800: '#1e40af',
900: '#1e3a8a',
950: '#172554',
},
// Palette neutre : background, borders, text
surface: {
0: '#ffffff',
50: '#f8fafc',
100: '#f1f5f9',
200: '#e2e8f0',
300: '#cbd5e1',
400: '#94a3b8',
500: '#64748b',
600: '#475569',
700: '#334155',
800: '#1e293b',
900: '#0f172a',
950: '#020617',
},
// Tokens partagés
borderRadius: {
none: '0',
xs: '2px',
sm: '4px',
md: '6px',
lg: '8px',
xl: '12px',
},
// Mode clair / sombre — overrides spécifiques
colorScheme: {
light: {
primary: {
color: '{primary.500}',
contrastColor: '#ffffff',
hoverColor: '{primary.600}',
activeColor: '{primary.700}',
},
surface: {
0: '#ffffff',
50: '{surface.50}',
100: '{surface.100}',
},
},
dark: {
primary: {
color: '{primary.400}',
contrastColor: '{surface.900}',
hoverColor: '{primary.300}',
activeColor: '{primary.200}',
},
surface: {
0: '{surface.950}',
50: '{surface.900}',
100: '{surface.800}',
},
},
},
},
});
Activer le preset dans app.config.ts
import { AfaCorpPreset } from './themes/afa-corp.preset';
providePrimeNG({
theme: {
preset: AfaCorpPreset, // votre preset au lieu d'Aura brut
options: { darkModeSelector: '.p-dark' },
},
}),
Palettes : primary, surface, sémantique
Couleurs sémantiques — succès, alerte, danger, info
// Étendre encore le preset avec les statuts métier
export const AfaCorpPreset = definePreset(Aura, {
semantic: {
primary: { /* ... */ },
surface: { /* ... */ },
// Couleurs d'état — utilisées par p-toast, p-message, p-tag, p-badge
success: {
50: '#f0fdf4', 100: '#dcfce7', 500: '#22c55e',
600: '#16a34a', 700: '#15803d',
},
warning: {
50: '#fffbeb', 100: '#fef3c7', 500: '#f59e0b',
600: '#d97706', 700: '#b45309',
},
danger: {
50: '#fef2f2', 100: '#fee2e2', 500: '#ef4444',
600: '#dc2626', 700: '#b91c1c',
},
info: {
50: '#ecfeff', 100: '#cffafe', 500: '#06b6d4',
600: '#0891b2', 700: '#0e7490',
},
},
});
Référencer un token dans un autre token
// Notation { ... } pour référencer une autre clé du preset
colorScheme: {
light: {
formField: {
borderColor: '{surface.300}',
hoverBorderColor: '{surface.400}',
focusBorderColor: '{primary.500}',
placeholderColor: '{surface.500}',
},
},
}
Inspecter les tokens générés
// Tous les tokens deviennent des CSS variables : --p-primary-500, --p-surface-100…
// Ouvrez les devtools, onglet Computed sur <html> pour les voir tous.
// Utilisation directe en CSS dans votre app :
.btn-brand {
background-color: var(--p-primary-500);
color: var(--p-primary-contrast-color);
border: 1px solid var(--p-primary-600);
border-radius: var(--p-border-radius-md);
}
.btn-brand:hover {
background-color: var(--p-primary-hover-color);
}
Dark mode natif avec Aura
Service de bascule (Signal-based)
// theme.service.ts
import { Injectable, signal, effect } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class ThemeService {
// Signal d'état : 'light' | 'dark' | 'auto'
mode = signal<'light' | 'dark' | 'auto'>(
(localStorage.getItem('theme-mode') as any) ?? 'auto'
);
constructor() {
effect(() => {
const m = this.mode();
const sys = window.matchMedia('(prefers-color-scheme: dark)').matches;
const isDark = m === 'dark' || (m === 'auto' && sys);
// La classe .p-dark est lue par le darkModeSelector configuré
document.documentElement.classList.toggle('p-dark', isDark);
localStorage.setItem('theme-mode', m);
});
}
toggle(): void {
const next = this.mode() === 'dark' ? 'light' : 'dark';
this.mode.set(next);
}
}
Bouton de bascule
// theme-toggle.component.ts
import { Component, inject } from '@angular/core';
import { ButtonModule } from 'primeng/button';
import { ThemeService } from './theme.service';
@Component({
selector: 'app-theme-toggle',
standalone: true,
imports: [ButtonModule],
template: `
<p-button
[icon]="theme.mode() === 'dark' ? 'pi pi-sun' : 'pi pi-moon'"
[text]="true"
[rounded]="true"
severity="secondary"
(onClick)="theme.toggle()"
ariaLabel="Basculer le thème">
</p-button>
`,
})
export class ThemeToggleComponent {
theme = inject(ThemeService);
}
Suivre la préférence système
// Écouter le changement de prefers-color-scheme en live
constructor() {
const mq = window.matchMedia('(prefers-color-scheme: dark)');
mq.addEventListener('change', () => {
if (this.mode() === 'auto') {
// Force la ré-évaluation de l'effect()
this.mode.set('auto');
}
});
}
CSS variables runtime
Aura expose chaque token sous forme de CSS variable. Vous pouvez les surcharger localement (par section, par composant, par instance) sans modifier le preset global.
Override par section de page
/* Section "promo" : couleur primaire orange uniquement ici */
.section-promo {
--p-primary-50: #fff7ed;
--p-primary-500: #f97316;
--p-primary-600: #ea580c;
--p-primary-700: #c2410c;
--p-primary-color: var(--p-primary-500);
--p-primary-hover-color: var(--p-primary-600);
--p-primary-active-color: var(--p-primary-700);
}
<section class="section-promo p-4">
<!-- Tous les boutons p-button ici sont orange, sans config supplémentaire -->
<p-button label="Profiter de l'offre" />
</section>
Mise à jour dynamique JavaScript
// Animation de couleur primaire sur scroll (effet pub)
ngAfterViewInit(): void {
window.addEventListener('scroll', () => {
const ratio = Math.min(window.scrollY / 500, 1);
const hue = Math.round(220 + ratio * 60); // 220 → 280
document.documentElement.style
.setProperty('--p-primary-500', `hsl(${hue} 80% 55%)`);
}, { passive: true });
}
:root par défaut. Vous pouvez créer des "sous-thèmes" en redéfinissant ces variables dans n'importe quel sélecteur descendant — utile pour une zone admin avec une palette différente du front public.
Personnaliser un composant ciblé
Vous voulez seulement changer le bouton danger, pas tout le thème ? Aura prévoit des tokens spécifiques par composant via la clé components du preset.
Tokens par composant — exemple Button
export const AfaCorpPreset = definePreset(Aura, {
semantic: { /* ... */ },
components: {
button: {
// Rayon plus prononcé pour TOUS les boutons
borderRadius: '{border.radius.lg}',
// Variations par sévérité
primary: {
background: '{primary.500}',
hoverBackground: '{primary.600}',
activeBackground: '{primary.700}',
color: '#ffffff',
},
danger: {
background: '{danger.600}', // plus profond que la palette
hoverBackground: '{danger.700}',
activeBackground: '#7f1d1d', // valeur littérale
color: '#ffffff',
focusRing: {
color: '{danger.500}',
shadow: '0 0 0 0.2rem {danger.100}',
},
},
},
inputtext: {
paddingY: '0.625rem',
paddingX: '0.875rem',
fontSize: '0.95rem',
},
card: {
background: '{surface.0}',
borderRadius: '{border.radius.xl}',
shadow: '0 4px 12px rgba(15, 23, 42, 0.08)',
},
},
});
Pass-through (pt) — styles par instance
Le système pass-through permet d'attacher des classes ou des attributs à un élément interne d'un composant PrimeNG sans toucher au theming global. Idéal pour une variante unique demandée par le design.
Cibler les sous-éléments d'un composant
<!-- Une instance précise du Calendar avec un fond pastel personnalisé -->
<p-calendar
[(ngModel)]="date"
[pt]="{
root: { class: 'shadow-sm' },
input: { class: 'fw-bold text-primary' },
panel: { class: 'cal-pastel-panel' },
header: { class: 'border-bottom' },
weekday: { class: 'text-uppercase small' },
day: { class: 'cal-pastel-day' }
}">
</p-calendar>
/* styles.css — appliqué uniquement aux instances qui utilisent ces classes */
.cal-pastel-panel {
background: #fdf2f8;
border: 1px solid #f9a8d4;
border-radius: 14px;
}
.cal-pastel-day:hover {
background: #fbcfe8 !important;
color: #831843;
}
Pass-through global via providePrimeNG
// Appliquer un pt à TOUS les composants p-button du projet
providePrimeNG({
theme: { preset: AfaCorpPreset },
pt: {
button: {
root: { class: 'shadow-sm transition-all' },
label: { class: 'fw-semibold' },
},
},
}),
Theming Lara (héritage)
Pour les projets PrimeNG < 18 toujours en production, Lara reste pleinement supporté. Le theming s'effectue via fichiers CSS et override de variables Sass / CSS classiques.
Importer un thème Lara
// angular.json
"styles": [
"node_modules/primeng/resources/themes/lara-light-blue/theme.css",
"node_modules/primeng/resources/primeng.min.css",
"node_modules/primeicons/primeicons.css",
"src/styles.css"
]
Override via CSS variables Lara
/* styles.css — surcharger uniquement après l'import du thème */
:root {
--primary-color: #1d4ed8;
--primary-color-text: #ffffff;
--surface-0: #ffffff;
--surface-100: #f1f5f9;
--text-color: #0f172a;
--text-color-secondary: #64748b;
--content-padding: 1rem;
--border-radius: 8px;
--focus-ring: 0 0 0 0.2rem rgba(29, 78, 216, 0.25);
}
Bascule dark mode avec Lara
// theme-loader.service.ts — change le href du link CSS dynamiquement
private setTheme(theme: 'lara-light-blue' | 'lara-dark-blue'): void {
const link = document.getElementById('theme-link') as HTMLLinkElement;
link.href = `assets/themes/${theme}/theme.css`;
}
<!-- index.html : link avec id pour pouvoir le repérer -->
<link id="theme-link" rel="stylesheet" href="assets/themes/lara-light-blue/theme.css" />
Aligner avec un Design System d'entreprise
Synchroniser les tokens Figma → Aura
Si votre Design System est documenté dans Figma avec Tokens Studio (ou Style Dictionary), exportez les tokens en JSON puis convertissez-les en preset Aura. Le pont garantit qu'un changement de couleur dans Figma se propage à toute l'application Angular.
// scripts/figma-tokens-to-aura.ts
import figmaTokens from '../design-tokens/tokens.json';
import { writeFileSync } from 'fs';
const preset = {
semantic: {
primary: figmaTokens.colors.brand,
surface: figmaTokens.colors.neutral,
success: figmaTokens.colors.success,
danger: figmaTokens.colors.danger,
borderRadius: figmaTokens.radius,
},
};
const ts = `import { definePreset } from '@primeng/themes';
import Aura from '@primeng/themes/aura';
export const BrandPreset = definePreset(Aura, ${JSON.stringify(preset, null, 2)});`;
writeFileSync('src/app/themes/brand.preset.ts', ts);
console.log('✔ Preset Aura généré depuis tokens Figma');
Multi-marques avec un sélecteur
// brand-loader.service.ts — bascule entre presets à chaud
import { Injectable, signal, effect, inject } from '@angular/core';
import { PrimeNG } from 'primeng/config';
import { BrandAPreset } from './themes/brand-a.preset';
import { BrandBPreset } from './themes/brand-b.preset';
@Injectable({ providedIn: 'root' })
export class BrandLoader {
private cfg = inject(PrimeNG);
brand = signal<'A' | 'B'>('A');
constructor() {
effect(() => {
const preset = this.brand() === 'A' ? BrandAPreset : BrandBPreset;
this.cfg.theme.set({ preset, options: { darkModeSelector: '.p-dark' } });
});
}
}
- Une seule source de vérité — tokens Figma exportés en JSON
- Génération automatisée du preset Aura (script CI ou prebuild)
- Tests visuels (Chromatic, Percy, Storybook) sur chaque composant
- Vérification WCAG AA des contrastes via axe DevTools
- Documentation des tokens autorisés ↔ tokens internes du preset
- Versioning sémantique du preset (breaking change → bump majeur)
- Migration progressive Lara → Aura plutôt que big bang
Conclusion
Le theming PrimeNG en 2026 repose sur trois leviers complémentaires : Aura + definePreset() pour les valeurs systémiques (palettes, rayons, ombres), CSS variables runtime pour les overrides locaux ou animés, pass-through (pt) pour les variantes par instance. Le tout sans recompilation, sans bundle CSS gonflé, et avec un support natif du dark mode.
Si votre projet est encore sous Lara, planifiez la migration : la verbosité des overrides CSS classiques disparaît, le dark mode devient un signal Angular plutôt qu'une bascule de fichier, et l'alignement avec un Design System Figma se fait par script. Aura n'est pas qu'une nouvelle façon d'écrire le même thème — c'est une rupture qui rapproche enfin Angular et PrimeNG des standards modernes (Tailwind, shadcn, Material 3).