Intégration web angularforall.com

- PrimeNG Theming : créer un thème custom Angular

Primeng Primeng-Theming Aura-Preset Design-Tokens Dark-Mode Css-Variables Angular Angular-Standalone Design-System Branding Lara-Theme Ui-Library Front-End Integration-Web
PrimeNG Theming : créer un thème custom Angular

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+)
ConfigurationFichier CSS importéObjet TypeScript via providePrimeNG
Dark modeCharger 2 CSS séparésNatif via darkModeSelector
Branding customOverride CSS lourddefinePreset() + tokens
Palette généréeVariantes 50→900 manuellesAuto depuis primary + surface
Changement runtimeReload des CSSMise à jour à chaud
Bundle CSS~ 350 ko (theme + primeng)~ 0 — tout est en TS
Recommandation : sur un nouveau projet Angular, démarrez directement avec Aura. Sur un projet existant en Lara, planifiez la migration en 3 étapes : (1) installer @primeng/themes, (2) recréer votre branding via definePreset, (3) supprimer les imports CSS Lara.

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"
]
Avec Aura, les styles des composants sont injectés à la demande lors du premier rendu du composant. Cela élimine le bundle CSS de ~350 ko de Lara et améliore le score Lighthouse First Contentful Paint.

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' },
  },
}),
Astuce génération de palette : utilisez uicolors.app ou palettte.app pour générer les 11 nuances depuis votre couleur de référence (500). Vérifiez ensuite les contrastes WCAG AA via les devtools Chrome.

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 });
}
Les CSS variables PrimeNG sont scopées sur :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' },
    },
  },
}),
pt vs theming : utilisez le preset pour les valeurs systémiques (palette, rayons, espacements) qui touchent l'ensemble. Utilisez pt pour des classes utilitaires Bootstrap/Tailwind ou des variantes ponctuelles. Cela évite que votre preset global gonfle avec des cas particuliers.

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" />
Lara reste maintenu pour la sécurité et la stabilité jusqu'en 2027 selon la roadmap PrimeNG. Au-delà, planifiez votre migration vers Aura : palette + composants critiques d'abord, le reste en parallèle des évolutions fonctionnelles.

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' } });
    });
  }
}
Checklist d'un theming d'entreprise réussi :
  • 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).

Partager