avatarAbdul Khader

Summary

The provided context outlines the process of customizing PrimeNG themes, particularly the MegaMenu component, in an Angular application, detailing how to add, customize, and override out-of-the-box themes using design tokens and CSS overrides.

Abstract

The web content delves into the customization of PrimeNG's UI components within an Angular framework, focusing on the MegaMenu component. It begins by explaining how to add PrimeNG's built-in themes, such as Aura, Material, Lara, and Nora, to an application. The article then progresses to theme customization, demonstrating the use of the definePreset utility to override default design tokens with custom values, such as replacing zinc color schemes with purple. It also addresses component-specific customization, detailing the HTML structure and TypeScript code required to implement a MegaMenu with custom items. The author emphasizes the importance of using design tokens for global customization and discusses the use of ::ng-deep for local style overrides, while noting that defining a custom preset is the preferred method for theming. The article concludes with resources for further exploration of design tokens and theming in PrimeNG.

Opinions

  • The author suggests that using ::ng-deep for styling is less preferable than creating a custom preset for global theming.
  • The article implies that familiarity with PrimeNG's theming utilities and design tokens is essential for effective UI customization.
  • The author expresses a preference for vibrant colors (e.g., purple) over more subdued ones (e.g., zinc) when customizing themes to create a more engaging user interface.
  • The author indicates that the customizations applied to the theme will affect all components, ensuring a consistent look and feel across the application.
  • The article highlights the ease of integrating PrimeNG components into an Angular application, referencing the author's previous work on configuring PrimeNG with Tailwind CSS in an NX Angular workspace.

PrimeNG Theme Customization — v19 in Angular.

Table of Contents

· Add the OOTB Theme · Customize the OOTB Theme · Component Customization: MegaMenu Component

In this article, we’ll explore how to customize the CSS for a PrimeNG component in an Angular application. If you’re new to PrimeNG, I recommend checking out their installation guide. For those using NX with Angular, you can refer to my earlier article on PrimeNG and Tailwind configuration in an NX Angular app.

Let’s dive into adding and customizing a MegaMenu component for our application. You can learn more about the MegaMenu component in the PrimeNG MegaMenu documentation.

Add the OOTB Theme

Aura, Material, Lara and Nora are the available built-in theme options.

import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { providePrimeNG } from 'primeng/config';
import Aura from '@primeng/themes/aura';

export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(appRoutes),
    provideAnimationsAsync(),
    providePrimeNG({ theme: { preset: Aura } })
  ],
};

Let’s add a Button and Rating component from PrimeNG to our application and see how they look. This will give us a better idea of the overall theme and styling in action.

Customize the OOTB Theme

The definePreset utility is used to customize an existing preset during the PrimeNG setup. The first parameter is the preset to customize and the second is the design tokens to override.

I have copied the following from here.

//mypreset.ts
import { definePreset } from '@primeng/themes';
import Aura from '@primeng/themes/aura';

const MyPreset = definePreset(Aura, {
    semantic: {
        primary: {
            50: '{zinc.50}',
            100: '{zinc.100}',
            200: '{zinc.200}',
            300: '{zinc.300}',
            400: '{zinc.400}',
            500: '{zinc.500}',
            600: '{zinc.600}',
            700: '{zinc.700}',
            800: '{zinc.800}',
            900: '{zinc.900}',
            950: '{zinc.950}'
        },
        colorScheme: {
            light: {
                primary: {
                    color: '{zinc.950}',
                    inverseColor: '#ffffff',
                    hoverColor: '{zinc.900}',
                    activeColor: '{zinc.800}'
                },
                highlight: {
                    background: '{zinc.950}',
                    focusBackground: '{zinc.700}',
                    color: '#ffffff',
                    focusColor: '#ffffff'
                }
            },
            dark: {
                primary: {
                    color: '{zinc.50}',
                    inverseColor: '{zinc.950}',
                    hoverColor: '{zinc.100}',
                    activeColor: '{zinc.200}'
                },
                highlight: {
                    background: 'rgba(250, 250, 250, .16)',
                    focusBackground: 'rgba(250, 250, 250, .24)',
                    color: 'rgba(255,255,255,.87)',
                    focusColor: 'rgba(255,255,255,.87)'
                }
            }
        }
    }
});

export MyPreset;

Let’s swap out the zinc color with something more vibrant. I’m going with purple. We’ll replace all instances of zinc with purple. You can find the complete list of available colors here.

const customPreset = definePreset(Aura, {
  semantic: {
    primary: {
      50: '{purple.50}',
      100: '{purple.100}',
      200: '{purple.200}',
      300: '{purple.300}',
      400: '{purple.400}',
      500: '{purple.500}',
      600: '{purple.600}',
      700: '{purple.700}',
      800: '{purple.800}',
      900: '{purple.900}',
      950: '{purple.950}',
    },
    colorScheme: {
      light: {
        primary: {
          color: '{purple.800}',
          inverseColor: '#ffffff',
          hoverColor: '{purple.900}',
          activeColor: '{purple.800}',
        },
        highlight: {
          background: '{purple.950}',
          focusBackground: '{purple.700}',
          color: '#ffffff',
          focusColor: '#ffffff',
        },
      },
      dark: {
        primary: {
          color: '{purple.50}',
          inverseColor: '{purple.950}',
          hoverColor: '{purple.100}',
          activeColor: '{purple.200}',
        },
        highlight: {
          background: 'rgba(250, 250, 250, .16)',
          focusBackground: 'rgba(250, 250, 250, .24)',
          color: 'rgba(255,255,255,.87)',
          focusColor: 'rgba(255,255,255,.87)',
        },
      },
    },
  },
});

Use the custom preset instead of the default out-of-the-box (OOTB) in your app.config.ts.

import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { appRoutes } from './app.routes';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { providePrimeNG } from 'primeng/config';
import customPreset from './custom.preset';

export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(appRoutes),
    provideAnimationsAsync(),
    providePrimeNG({ theme: { preset: customPreset }})
  ],
};

Once the theme override is applied, let’s check how they appear. The changes we made apply to all the components.

Component Customization: MegaMenu Component

We’ll begin by integrating the MegaMenu component into our app. Below is the HTML structure we used for the MegaMenu:

<p-megamenu [model]="items" orientation="vertical" styleClass="p-4">
    <ng-template #item let-item>
        <a *ngIf="item.root" pRipple class="flex flex-col items-center cursor-pointer px-4 py-2 overflow-hidden relative font-semibold text-lg uppercase" style="border-radius: 2rem">
            <i [ngClass]="item.icon" style="font-size: 1.6rem"></i>
            <span style="font-size: 11px;">{{ item.label }}</span>
        </a>
        <a *ngIf="!item.root && !item.image" class="flex items-center p-4 cursor-pointer mb-2 gap-2">
            <span class="inline-flex items-center justify-center rounded-full bg-primary text-primary-contrast w-12 h-12">
                <i [ngClass]="item.icon + ' text-lg'"></i>
            </span>
            <span class="inline-flex flex-col gap-1">
                <span class="font-medium text-lg text-surface-900 dark:text-surface-0">{{ item.label }}</span>
                <span class="whitespace-nowrap">{{ item.subtext }}</span>
            </span>
        </a>
        <div *ngIf="item.image" class="flex flex-col items-start gap-4">
            <img [src]="item.image" alt="megamenu-demo" class="w-full" />
            <span>{{ item.subtext }}</span>
            <p-button [label]="item.label" [outlined]="true"></p-button>
        </div>
    </ng-template>
</p-megamenu>

Here’s the corresponding TypeScript code to define the MegaMenu items:

import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MegaMenuItem } from 'primeng/api';
import { MegaMenu } from 'primeng/megamenu';
import { ButtonModule } from 'primeng/button';
@Component({
  selector: 'app-root',
  imports: [MegaMenu, ButtonModule, CommonModule],
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {
  items: MegaMenuItem[] | undefined;
  ngOnInit() {
    this.items = [
      {
        label: 'Home',
        icon: 'pi pi-home',
        root: true,
      },
      {
        label: 'Projects',
        icon: 'pi pi-folder',
        root: true,
      },
      {
        label: 'Templates',
        icon: 'pi pi-database',
        root: true,
      },
    ];
  }
}

This configuration creates a vertical MegaMenu with three root items: Home, Projects, and Templates. Each item includes an icon and label.

How can we modify the color of the icon and label, as well as the background color, when they are focused?

Color and background updated using ::ng-deep

To override the styles of PrimeNG components, using regular CSS in your component won’t work because PrimeNG components are encapsulated in their own styles. You have two options to apply your custom styles:

  1. You can use the ::ng-deep selector to target styles inside PrimeNG components and override them.
  2. or add the css in the global styles.

Following css will help us acheive above results using ::ng-deep.

:host ::ng-deep {
    .p-megamenu-item:not(.p-disabled) > .p-megamenu-item-content:hover{
        background-color: #f5f5f5;
        color: purple;
    }
    .p-megamenu-item-content{
        color: purple;
    }
}

But is there any better way of achieving?

We can use the design tokens of a specific component. However, Overriding components tokens is not the recommended approach if you are building your own style, building your own preset should be preferred instead. This configuration is global and applies to all MegaMenu components, in case you need to customize a particular component on a page locally, view the Scoped CSS section.

import { definePreset } from '@primeng/themes';
import Aura from '@primeng/themes/aura';

const customPreset = definePreset(Aura, {
  components: {
    megamenu: {
      colorScheme: {
        light: {
          item: {
            color: '{purple.700}',
            focus: {
              background: '{purple.50}',
              color: '{purple.700}',
            },
          },
          dark: {
            item: {
              color: '{purple.700}',
            },
          },
        },
      },
    },
  },
  semantic: {
    primary: {
      50: '{purple.50}',
      100: '{purple.100}',
      ....
    },
    colorScheme: {
      ...
      },
      dark: {
       ....
      },
    },
  },
});

export default customPreset;

You can get the tokens of each component from the component’s documentation. Mega menu’s Design Tokens.

Angular
Primeng
Theme Customization
Themes
Angular Development
Recommended from ReadMedium