
Upgrade Angular UI Libraries During Version Migrations — Material MDC + PrimeNG 17 Without UX Regression (Angular 20+, Signals, Nx)
A field-tested playbook to migrate Angular Material and PrimeNG across breaking versions without jitter, density drift, or a11y regressions—using tokens, adapters, and CI visual diff guardrails.
“Upgrading Angular libraries should feel invisible to users. Tokens, adapters, and guardrails make it boring—and boring is beautiful in production.”Back to all posts
I’ve shipped multiple Angular 20+ upgrades where the “upgrade” went fine—but the UX didn’t. Material’s MDC switch changed density; PrimeNG shifted classes; keyboards lost focus cues; charts no longer matched the palette. In a telecom analytics dashboard, our QA caught a subtle PrimeNG Table padding change that destroyed the above-the-fold insights. That’s the kind of regression that erodes user trust.
This article is the migration playbook I use on Fortune 100 dashboards—employee tracking, airport kiosks, ads analytics, telematics—where a Material/PrimeNG upgrade must not jitter, blur, or break. We’ll align both libraries to a single visual language (tokens), bridge breaking changes with adapters, and enforce guardrails with Nx + Storybook + Chromatic/Cypress. We’ll also lean on Angular 20 Signals/SignalStore to toggle density and themes on the fly.
The real problem: breaking UI libraries change more than APIs—they change perception
Angular Material’s move to MDC and PrimeNG’s 16→17 styling updates are great for consistency, but they subtly tweak spacing, typography, and focus rings. If you lead a role‑based dashboard with real‑time charts (D3/Highcharts/Canvas/Three.js) and dense tables, those tweaks can bury the KPI a VP expects to see first. We fix that by making the visual contract explicit and testable.
Step 1 — Freeze the visual contract (baselines before code)
- Capture critical states in Storybook: default, hover, focus, error, disabled, loading, compact density, dark mode.
- Record Chromatic/visual snapshots and Cypress component screenshots.
- Add Lighthouse and axe checks to CI for Core Web Vitals and a11y.
# .github/workflows/ui-guardrails.yml
name: ui-guardrails
on: [pull_request]
jobs:
storybook-visual:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npx nx run ui:build-storybook
- run: npx chromatic --project-token=$CHROMATIC_TOKEN --exit-zero-on-changes=false --auto-accept-changes=false
lighthouse-a11y:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npx lhci autorun --upload.target=temporary-public-storage
- run: npx axe .dist/app/index.html --exit 1This gives you a red/green signal on visuals, performance, and accessibility. I rarely start a library migration without it.
Step 2 — One token system to rule Material and PrimeNG
Create a single source of truth for color, typography, and density. I use an AngularUX palette with WCAG AA contrast and map it to both libraries.
/* src/styles/tokens.scss */
:root {
/* AngularUX color palette */
--ux-bg: #0f1220; /* surfaces */
--ux-bg-elevated: #171a2b;
--ux-text: #e6e9f2; /* body text */
--ux-muted: #a6accd; /* secondary */
--ux-primary: #5b8cff; /* action */
--ux-accent: #22d3ee; /* highlights */
--ux-danger: #ff5577;
/* density scale */
--ux-density: 0; /* -2 compact .. +2 comfortable */
/* type scale */
--ux-font: 'Inter', system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
--ux-size-100: 12px; --ux-size-200: 14px; --ux-size-300: 16px; --ux-size-400: 18px; --ux-size-500: 20px;
}
/* Material theme bridge */
@include mat.core();
$primary: mat.define-palette(mat.$indigo-palette, 500);
$accent: mat.define-palette(mat.$teal-palette, 400);
$warn: mat.define-palette(mat.$red-palette, 400);
$theme: mat.define-dark-theme((
color: (primary: $primary, accent: $accent, warn: $warn),
typography: mat.define-typography-config(
$font-family: var(--ux-font),
$body-1: mat.define-typography-level(var(--ux-size-300))
),
density: var(--ux-density)
));
@include mat.all-component-themes($theme);
/* PrimeNG bridge */
:root {
--primary-color: var(--ux-primary);
--text-color: var(--ux-text);
--surface-card: var(--ux-bg-elevated);
--font-family: var(--ux-font);
}Two libraries, one visual language. This also makes charts consistent: bind Highcharts/D3 palettes to these CSS variables so bars, lines, and alerts match components.
// chart-theme.ts
export const chartPalette = getComputedStyle(document.documentElement);
export const seriesColors = [
chartPalette.getPropertyValue('--ux-primary').trim(),
chartPalette.getPropertyValue('--ux-accent').trim(),
chartPalette.getPropertyValue('--ux-danger').trim()
];Step 3 — Adapter components for high-churn controls
Wrap frequently used components (buttons, inputs, form-field, table cells, menus) behind a stable contract. Under the hood you can switch Material legacy→MDC or PrimeNG 16→17 without touching call sites.
// ui/button/button.component.ts
import { Component, Input, computed, signal } from '@angular/core';
@Component({
selector: 'ux-button',
template: `
<button *ngIf="lib() === 'mat'" mat-raised-button [color]="color" [disabled]="disabled">
<ng-content />
</button>
<p-button *ngIf="lib() === 'prime'" [severity]="primeSeverity()" [disabled]="disabled">
<ng-content />
</p-button>
`,
standalone: true,
imports: []
})
export class UxButtonComponent {
@Input() color: 'primary'|'accent'|'warn'|'basic' = 'primary';
@Input() disabled = false;
private choice = signal<'mat'|'prime'>('mat');
lib = computed(() => this.choice());
primeSeverity = computed(() => this.color === 'warn' ? 'danger' : this.color);
}This pattern let us flip a feature flag to test PrimeNG buttons across a telematics dashboard while keeping the rest on Material.
Step 4 — Density/Theme toggles with Signals/SignalStore
I use a tiny SignalStore to change density and theme at runtime to smoke-test UX drifts on real data (including virtual scroll lists and WebSocket-updating charts).
// app/ui-store.ts
import { signal, computed, Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class UiStore {
readonly density = signal<-2|-1|0|1|2>(0);
readonly theme = signal<'dark'|'light'>('dark');
readonly vars = computed(() => ({ '--ux-density': String(this.density()) }));
}<!-- app.component.html -->
<div [ngStyle]="ui.vars()" [class.dark]="ui.theme()==='dark'">
<app-shell />
</div>
<button (click)="ui.density.update(d => Math.max(-2, d-1))">Compact</button>
<button (click)="ui.density.update(d => Math.min(2, d+1))">Comfortable</button>This caught a PrimeNG v17 InputNumber label shift in a finance dashboard when density=-2. Fix was a small CSS alias shim until the full refactor landed.
Step 5 — CSS alias shims for smoother diffs
For PrimeNG and MDC, small alias layers buy you time without repainting the whole app.
/* alias-shims.scss */
/* PrimeNG class rename shim */
.p-inputtext, .p-inputtext-sm { // old selectors
@extend .p-inputtext; // ensure both paths styled
}
/* Focus ring: force visible rings for a11y */
:where(button, [role='button']):focus-visible {
outline: 2px solid var(--ux-accent);
outline-offset: 2px;
}
/* Table cell density alignment */
.p-datatable .p-datatable-tbody > tr > td { padding-block: calc(8px + var(--ux-density) * 1px); }Material specifics: legacy→MDC without surprises
- Use official codemods, then check form-field, button, menu, tooltip. Ripples and elevations changed.
- Typography moved to define-typography-config; apply your font + size tokens.
- Density is supported; wire it to your Signals store and test -2..+2.
- MatIcon often needs registry updates; PrimeIcons require replacements.
# Example upgrade flow (always on a branch with baselines frozen)
ng update @angular/core@20 @angular/cli@20 --force
ng update @angular/material@17
# run codemods and fix deprecations
npx ng-morph schematic material-migration/* material-theme.scss */
@use '@angular/material' as mat;
@include mat.core();
$my-typography: mat.define-typography-config(
$font-family: var(--ux-font),
$body-1: mat.define-typography-level(var(--ux-size-300))
);
$my-theme: mat.define-dark-theme((
color: (primary: $primary, accent: $accent, warn: $warn),
typography: $my-typography,
density: var(--ux-density)
));
@include mat.all-component-themes($my-theme);PrimeNG specifics: 16→17 changes that bite
- Validate p-table templates; header/body templates changed naming in some versions; verify selection/filter chips.
- pRipple, icons, and overlay z-index tweaks can shift visuals; alias or update selectors.
- PrimeFlex vs library classnames drift; lock spacing via CSS variables and utility classes.
- Confirm InputNumber, Dropdown, and Calendar behaviors at compact densities.
Accessibility, typography, and motion preferences
- Ensure WCAG AA: run axe in CI; contrast ties back to token colors.
- Respect prefers-reduced-motion: disable heavy animations in PrimeNG and only run lightweight Angular animations.
- Keep focus rings visible (never remove outline); use :focus-visible tokenized styles.
- Type scale: 16px base, clamp for responsive headings. Test with real tables and chart legends.
Charts and virtualization stay on brand
- D3/Highcharts: inject CSS-tokened colors for series, grids, and tooltips.
- Canvas/Three.js: adapt theme at runtime from Signals store; minimal re-render.
- Virtual scroll lists (CDK): verify row heights across densities so virtualization remains accurate.
- Real-time dashboards: throttle WebSocket update styling changes; use typed event schemas and backoff logic you can test.
Performance budgets during UI upgrades
- Keep CSS bundle budgets strict; prefer tokenized variables over giant theme overrides.
- Lighthouse: enforce LCP/INP budgets in CI.
- Angular DevTools: watch render counts when swapping components (MDC vs PrimeNG variants).
- SSR/Hydration: confirm no layout shift on hydrate; track CLS under 0.1.
When to hire an Angular developer for a legacy UI library rescue
If your app mixes Material 11 styles, PrimeNG 14 components, and custom SCSS, you’ll move faster with an Angular consultant who’s done cross-library harmonization. I’ve rescued kiosk apps (Docker-simulated hardware, offline-tolerant flows) and ads analytics dashboards without freezing delivery. Typical engagement: baselines in week 1, adapters in week 2, rollout week 3–4.
Example Nx task wiring
Wire Storybook, Chromatic, Lighthouse, and a11y into Nx so every lib change triggers the right guardrails.
// project.json (excerpt)
{
"targets": {
"storybook": { "executor": "@nx/storybook:build", "options": { "outputPath": "dist/storybook" }},
"chromatic": { "executor": "nx:run-commands", "options": { "command": "chromatic --project-token=$CHROMATIC_TOKEN" }},
"lhci": { "executor": "nx:run-commands", "options": { "command": "lhci autorun" }}
}
}Measurable outcomes you can show leadership
- 0 critical visual diffs on baseline stories; <0.2% minor diffs allowed by threshold.
- Lighthouse Mobile improved 72→94 after tokenizing and cleaning dead CSS (from a Signals + tokens refresh).
- INP below 200ms on dense tables; no CLS during SSR hydration.
- Accessibility: axe violations → zero in critical flows.
If you need a remote Angular expert to lead this with Signals, PrimeNG/Material, Firebase, Nx, and rigorous CI, I’m available for hire. Let’s review your library migration plan and keep your UX stable while you upgrade to Angular 20+.
Quick migration script starter
# 1) Baseline
npx nx run ui:build-storybook && npx chromatic
# 2) Upgrade core and libraries
ng update @angular/core@20 @angular/cli@20
ng update @angular/material@17
npm i primeng@^17 primeicons@^7 --save
# 3) Run tests + lighthouse + a11y
npm run test && npx lhci autorun && npx axe dist/index.htmlTakeaways
- Freeze visuals before code. Tokens, stories, and screenshots are your safety net.
- Bridge upgrades with adapters + CSS aliases to avoid churn.
- Signals/SignalStore make density/theme testing instant and measurable.
- Guardrails in CI catch regressions early so production never sees them.
Ready to migrate Material and PrimeNG without UX regressions? Review my live component work at the NG Wave component library and let’s discuss your Angular roadmap.
The real problem: breaking UI libraries change more than APIs—they change perception
Subtle drifts break trust
Material MDC and PrimeNG updates improve consistency but can silently alter spacing and focus. In executive dashboards, those pixels matter.
Density shifts push KPIs below the fold
Typography changes alter scan patterns
Focus styles regress and hurt keyboard users
Step 1 — Freeze the visual contract (baselines before code)
What to baseline
Use Storybook to capture key states. Add Chromatic/Cypress images, Lighthouse, and axe to CI so you can detect regressions immediately.
Default/hover/focus/error/disabled
Compact/comfortable densities
Dark/light themes
Step 2 — One token system to rule Material and PrimeNG
Map tokens to both libraries
Define CSS variables once, then apply to Material and PrimeNG themes so both follow the same visual language and charts match too.
Color: AA contrast palette
Type: 16px base, responsive scale
Density: -2..+2 wired to Signals
Step 3 — Adapter components for high-churn controls
Stabilize the API you own
Adapter components let you swap libraries incrementally and test in production behind role/route flags.
Wrap buttons, inputs, menus, table cells
Flip implementation behind a flag
Minimize call-site changes
Step 4 — Density/Theme toggles with Signals/SignalStore
Test UX under real conditions
A small SignalStore enables quick smoke tests for compact layouts and motion preferences without redeploys.
Switch density live with Signals
Verify virtualization and charts
Expose toggles for QA
Step 5 — CSS alias shims for smoother diffs
Buy time with shims
CSS shims reduce the blast radius while you complete deeper refactors.
Alias renamed classes
Normalize focus rings
Pad table cells by density
Material specifics: legacy→MDC without surprises
High-risk areas
Run codemods, map tokens to MDC theming, and verify focus/hover/error states in Storybook.
Form-field, button, menu, tooltip
Typography and density mapping
MatIcon registry, ripples
PrimeNG specifics: 16→17 changes that bite
What to check
Confirm behaviors across densities; patch with alias shims where needed before full updates.
p-table templates and selection
InputNumber/Dropdown/Calendar
Icons, z-index overlays, ripple
Accessibility, typography, and motion preferences
A11y and readable UIs
Run axe in CI; never remove outlines; respect prefers-reduced-motion. Keep the 16px base with a responsive scale.
WCAG AA contrast
Focus-visible rings
Reduced motion
Charts and virtualization stay on brand
Visualizations that match the system
Bind chart series to CSS variables, adapt canvas themes from Signals, and confirm virtualization accuracy at each density.
D3/Highcharts colors from tokens
Signals to theme Canvas/Three.js
Virtual scroll row height checks
Performance budgets during UI upgrades
Guardrails that matter
Keep budgets strict and verify SSR hydration has no CLS regressions.
CSS bundle budgets
Lighthouse CI budgets
Render count checks
When to Hire an Angular Developer for a Legacy UI Library Rescue
Signs you need help
An experienced Angular consultant accelerates tokenization, adapters, and CI guardrails so you ship upgrades without freezing features.
Mixed library versions and custom SCSS
Tight deadlines with high UX risk
Need zero-downtime rollout
Measurable outcomes you can show leadership
Numbers that win trust
Translate migration work into board-ready metrics and tie improvements to tokens and adapters.
0 critical visual diffs
Lighthouse Mobile 72→94
INP < 200ms; CLS < 0.1
Key takeaways
- Freeze the visual contract first: Storybook baselines + Chromatic/Cypress screenshots before any code change.
- Bridge breaking APIs with adapter components and CSS alias shims to avoid mass refactors.
- Unify Material + PrimeNG with a shared token system (color, typography, density) mapped to both libraries.
- Use Signals/SignalStore for live theme/density toggles and to test UX at runtime without redeploys.
- Automate guardrails: CI visual diffs, Lighthouse budgets, and a11y checks to catch regressions early.
Implementation checklist
- Snapshot current UI states in Storybook; record Chromatic/Cypress baselines.
- Introduce shared tokens (SCSS/JSON) for color, typography, density.
- Wrap high-churn components (buttons, inputs, menus) with adapters.
- Add CSS var aliases to smooth class/var renames.
- Feature-flag rollout by route or role using SignalStore.
- Enforce CI guardrails: visual diff thresholds, bundle budgets, Lighthouse, axe.
- Complete library codemods; fix deprecations with lint rules.
- Run a11y pass: focus rings, contrast, readable type scale, motion preferences.
Questions we hear from teams
- How long does an Angular Material/PrimeNG upgrade take?
- For a mid-size dashboard, expect 2–4 weeks: week 1 baselines and tokens, week 2 adapters and shims, week 3–4 rollout and fixes. Complex multi-tenant apps or heavy table customizations can extend to 6–8 weeks.
- What’s the safest way to avoid UX regressions during a library migration?
- Freeze visuals first with Storybook + Chromatic/Cypress, migrate behind adapter components, and enforce CI guardrails for a11y and Lighthouse. Use Signals/SignalStore to test density and themes at runtime.
- Do I need both Material and PrimeNG?
- Many enterprises mix them for historical reasons. If consolidation isn’t feasible, use a shared token system and adapter components so the two libraries present a consistent visual language to users.
- How much does it cost to hire an Angular developer for this work?
- Engagements start with a fixed-fee assessment and baselining. Implementation is typically time-and-materials or milestone-based. Contact me for a scoped estimate based on app size, coverage, and deadlines.
- Will charting (D3/Highcharts/Canvas) match the new theme automatically?
- If you bind chart colors and typography to your CSS tokens, charts will switch with the rest of the system. I provide helpers that read CSS variables and re-theme without heavy re-renders.
Ready to level up your Angular experience?
Let AngularUX review your Signals roadmap, design system, or SSR deployment plan.
NG Wave
Angular Component Library
A comprehensive collection of 110+ animated, interactive, and customizable Angular components. Converted from React Bits with full feature parity, built with Angular Signals, GSAP animations, and Three.js for stunning visual effects.
Explore Components