
Connect Figma Tokens to PrimeNG in Angular 20+: Storybook + Chromatic Guardrails (Nx, Signals, A11y)
Design tokens that compile from Figma to CSS variables, wire into PrimeNG and charts, previewed in Storybook, and locked by Chromatic—polish with metrics.
If a token change can’t pass through Storybook + Chromatic, it doesn’t belong in production.Back to all posts
I’ve watched token drift sink enterprise UI polish. The fix is a pipeline: Figma tokens compile to CSS variables, PrimeNG reads them, Storybook previews them, and Chromatic guards them. In this playbook I’ll show the exact Nx + Angular 20+ setup I use on dashboards, kiosks, and multi‑tenant apps.
Hook: Figma → PrimeNG with Guardrails (Storybook + Chromatic)
The scene
Quarter‑close at a telecom client: charts didn’t match the brand refresh, density toggles were inconsistent, and a bug turned DataTable focus outlines invisible. We wired Figma tokens into our Angular 20 + PrimeNG stack, previewed everything in Storybook, and let Chromatic block regressions. The next brand change shipped in under an hour.
If you need to hire an Angular developer or Angular consultant for a similar integration, this is the exact, repeatable approach I use across Fortune 100 dashboards and kiosk software.
Why Figma Tokens in Angular 20 + PrimeNG Matter
Outcomes you can measure
As teams plan 2025 Angular roadmaps, token pipelines turn subjective polish into a measurable engineering practice. UX polish coexists with performance budgets, and design consistency scales across role‑based, multi‑tenant apps.
Fewer theme bugs: 60–80% drop in visual defects after Chromatic adoption (my averages on media/insurance dashboards)
Faster brand refresh: minutes, not sprints—tokens propagate to PrimeNG and charts instantly
Accessible by default: AA contrast enforced via Storybook a11y and token constraints
Performance preserved: CSS variables don’t inflate bundles; Lighthouse Mobile stays high
Architecture: Nx Libs, Signals/SignalStore, and CSS Variables
// libs/design-tokens/src/tokens.json
{
"color": {
"primary": { "500": { "value": "#4C7AF2" } },
"accent": { "500": { "value": "#00C2A8" } },
"danger": { "500": { "value": "#E65050" } },
"surface": { "0": { "value": "#0B0E14" }, "100": { "value": "#111827" }, "900": { "value": "#FFFFFF" } }
},
"typography": {
"fontFamily": { "value": "'Inter', system-ui, -apple-system, Segoe UI, Roboto" },
"size": { "base": { "value": 16 }, "scale": { "value": 1.125 } }
},
"density": { "cozy": { "value": 8 }, "compact": { "value": 4 } }
}/* libs/design-tokens/dist/tokens.css */
:root { /* AngularUX palette */
--ux-color-primary-500: #4C7AF2;
--ux-color-accent-500: #00C2A8;
--ux-color-danger-500: #E65050;
--ux-surface-0: #0B0E14;
--ux-surface-100: #111827;
--ux-surface-900: #FFFFFF;
--ux-font-family: 'Inter', system-ui, -apple-system, Segoe UI, Roboto;
--ux-font-size-base: 16px;
--ux-font-scale: 1.125;
--ux-density-cozy: 8px;
--ux-density-compact: 4px;
}// libs/ui-theme/src/lib/theme.store.ts
import { signal, computed, effect, Injector } from '@angular/core';
import { signalStore, withState } from '@ngrx/signals';
interface ThemeState {
mode: 'light' | 'dark';
density: 'cozy' | 'compact';
scale: 1 | 1.125 | 1.25;
}
export const ThemeStore = (injector: Injector) => signalStore(
withState<ThemeState>({ mode: 'light', density: 'cozy', scale: 1 }),
)((store) => {
const cssVars = computed(() => ({
'--ux-density': store.density() === 'cozy' ? 'var(--ux-density-cozy)' : 'var(--ux-density-compact)',
'--ux-font-size-base': `calc(var(--ux-font-size-base) * ${store.scale()})`,
'--p-primary-500': 'var(--ux-color-primary-500)', // PrimeNG variables
'--p-green-500': 'var(--ux-color-accent-500)',
'--p-red-500': 'var(--ux-color-danger-500)'
}));
effect(() => {
const el = document.documentElement;
const vars = cssVars();
for (const [k, v] of Object.entries(vars)) el.style.setProperty(k, v);
el.classList.toggle('theme-dark', store.mode() === 'dark');
});
return {
mode: store.mode,
density: store.density,
scale: store.scale,
setMode: (m: ThemeState['mode']) => store.mode.set(m),
setDensity: (d: ThemeState['density']) => store.density.set(d),
setScale: (s: ThemeState['scale']) => store.scale.set(s)
};
});Workspace layout
Keep tokens in an Nx lib so they version with code and drive Storybook + Chromatic previews in isolation.
libs/design-tokens: Figma JSON + build to CSS variables
libs/ui-theme: ThemeStore (Signals/SignalStore) + helpers
libs/ui-components: PrimeNG wrappers + Storybook stories
apps/web: Angular 20+ app consuming tokens
Figma → CSS variables
Export with Tokens Studio (Figma) to JSON. Transform with Style Dictionary (or a tiny Node script) to :root CSS variables so PrimeNG and charts share the same source.
State via Signals/SignalStore
Signals make runtime theme flips instant without change detection storms. SignalStore adds dev ergonomics and testability.
Theme (light/dark), density (cozy/compact), typography scale
Persisted per tenant/role via Firebase Remote Config or localStorage
Map Tokens to PrimeNG and Your Components
/* apps/web/src/styles/_primeng-overrides.scss */
:root {
--p-focus-ring: 0 0 0 3px color-mix(in srgb, var(--ux-color-primary-500) 40%, transparent);
--p-content-padding: var(--ux-density);
}
.p-inputtext, .p-button { font-family: var(--ux-font-family); }
.p-datatable .p-datatable-tbody > tr > td { padding: var(--ux-density); }
.theme-dark {
--p-text-color: var(--ux-surface-900);
--p-surface-0: var(--ux-surface-100);
}PrimeNG variables
PrimeNG reads CSS variables, so your tokens apply to DataTable, Input, Menus without rewriting component SCSS.
Use the new PrimeNG CSS variable themes (e.g., Lara/Saga) as a base
Override with your token variables at :root or .theme-* scopes
Density + focus states
In kiosk flows for a major airline, tokenized focus rings and compact density saved seconds per passenger, even offline.
Token‑driven padding/row heights
AA‑visible focus outlines and state colors
Storybook Integration: A11y, Typography, and Density Controls
// .storybook/preview.ts
import type { Preview } from '@storybook/angular';
const preview: Preview = {
globalTypes: {
themeMode: { defaultValue: 'light' },
density: { defaultValue: 'cozy' },
scale: { defaultValue: 1 }
},
decorators: [
(story, context) => {
const root = document.documentElement;
root.classList.toggle('theme-dark', context.globals.themeMode === 'dark');
root.style.setProperty('--ux-density', `var(--ux-density-${context.globals.density})`);
root.style.setProperty('--ux-font-size-base', `calc(16px * ${context.globals.scale})`);
return story();
}
],
parameters: {
a11y: { element: '#root', manual: false }
}
};
export default preview;Toolbar for live tokens
Design and engineering preview the same tokens. Screenshots and specs live with components, not in PDFs.
Theme (light/dark), density, font scale
Docs that show token values next to components
A11y and contrast
We caught invisible link text in an insurance telematics dashboard before it hit staging—because contrast was a Storybook rule, not a suggestion.
@storybook/addon-a11y with WCAG AA rules
Chromatic links a11y results to visual diffs
Chromatic Visual Regression + CI Budgets
# .github/workflows/ci.yml
name: ci
on: [pull_request]
jobs:
test_build_storybook:
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-many -t test build-storybook --parallel
- name: Chromatic
uses: chromaui/action@v1
with:
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
exitOnceUploaded: true
autoAcceptChanges: false
onlyChanged: true
- name: Lighthouse CI
run: |
npx lhci autorun --config=./lighthouse/lighthouserc.json// angular.json (snippet)
"budgets": [{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "700kb"
}]Why Chromatic
Chromatic has saved my teams from hard‑to‑spot regressions in PrimeNG tables, calendars, and stepper flows countless times.
Token diffs show up as visual changes on stories
Approvals become a design+eng gate, not a Slack thread
Wire into CI
Per‑PR Chromatic runs with thresholds
Fail fast on bundle budgets and a11y errors
Budgets and metrics
UX polish should not cost you performance. Prove it every PR.
Lighthouse CI for INP/LCP smoke checks
Angular budgets to catch accidental bloat
Charts (D3/Highcharts/Three.js) on the Same Tokens
// libs/ui-components/src/lib/highcharts-theme.ts
import Highcharts from 'highcharts';
export function applyHighchartsTheme() {
const primary = getComputedStyle(document.documentElement)
.getPropertyValue('--ux-color-primary-500').trim();
const accent = getComputedStyle(document.documentElement)
.getPropertyValue('--ux-color-accent-500').trim();
Highcharts.setOptions({
colors: [primary, accent, '#a3a3a3'],
chart: { backgroundColor: 'transparent' },
title: { style: { color: primary, fontFamily: 'var(--ux-font-family)' } },
tooltip: { backgroundColor: 'var(--p-surface-0)', borderColor: primary }
});
}Why it matters for dashboards
On a telecom advertising analytics platform, we used tokens to unify colors across Highcharts, PrimeNG tables, and D3 mini‑sparklines—one change, everywhere.
Role‑based palettes (finance vs. ops) without JS rewrites
Data virtualization + Canvas/Three.js respects density tokens
Accessibility, Typography, and Density: UX Details that Scale
Typography scale
Modular scale via tokens ensures consistent rhythm
Storybook controls show 1x/1.125x/1.25x live
Contrast and focus
I’ve shipped kiosk flows on airport hardware with card readers and scanners; tokenized focus states and larger tap targets reduced mis‑taps and sped up boarding.
WCAG AA as a build check, not a guideline
PrimeNG focus rings and hover states tokenized
Density controls
In employee tracking systems and VPS schedulers, compact density increased visible rows by ~25% while staying AA accessible.
Compact mode for analysts, cozy for managers
Virtualized tables use tokenized rowHeight
Example: Wiring a PrimeNG DataTable Story with Tokens
// libs/ui-components/src/lib/data-table/data-table.stories.ts
import { Meta, StoryObj } from '@storybook/angular';
import { TableModule } from 'primeng/table';
import { CommonModule } from '@angular/common';
const meta: Meta = {
title: 'Data/PrimeNG DataTable',
component: undefined as any,
decorators: [
moduleMetadata({ imports: [CommonModule, TableModule] })
],
argTypes: { density: { control: 'radio', options: ['cozy', 'compact'] } }
};
export default meta;
type Story = StoryObj;
export const Basic: Story = {
args: { density: 'cozy' },
render: (args) => ({
template: `
<p-table [value]="rows" [style.padding]="densityPadding">
<ng-template pTemplate="header">
<tr><th>Employee</th><th>Status</th></tr>
</ng-template>
<ng-template pTemplate="body" let-r>
<tr><td>{{r.name}}</td><td>{{r.status}}</td></tr>
</ng-template>
</p-table>
`,
props: {
rows: [{ name: 'A. Lee', status: 'Active' }],
densityPadding: `var(--ux-density-${args['density']})`
}
})
};Story and controls
This is the pattern I drop into role‑based dashboards: density and theme toggleable per story, so product can sign off without staging wars.
Performance Guardrails and Operationalizing
Keep it fast
CSS variables are static and tiny to ship
No dynamic SCSS at runtime; avoid style recalcs
Measure
On IntegrityLens (AI‑powered verification system), we measured INP while streaming and toggling density—Signals kept reflows limited and deterministic.
Angular DevTools render counts for token toggles
Lighthouse Mobile targets (e.g., 90+ LCP/INP)
Rollout
I’ve done this for multi‑tenant insurance telematics portals and device management dashboards—zero forks, zero copy/paste.
Feature‑flag theme variants (Firebase Remote Config)
Tenant‑specific palettes without forks
When to Hire an Angular Developer for Legacy Rescue
Good fits
If your codebase is chaotic, I can help stabilize. See my work at the NG Wave component library and the code rescue playbook at gitPlumbers—both are live and battle‑tested.
AngularJS → Angular migrations that need a design system
PrimeNG apps with inconsistent themes and accessibility gaps
Teams wanting Chromatic + Storybook discipline and CI budgets
How an Angular Consultant Approaches Signals Migration
Pragmatic adoption
Signals + SignalStore are a low‑risk entry point: UX teams see immediate value, and engineering builds confidence with metrics.
Start with theme/density store for quick UX wins
Measure render counts before/after with DevTools
Final Takeaways and Next Steps
Recap
If you need a remote Angular expert for hire to wire this up, I can join as a contractor or consultant and leave you with a durable, documented pipeline.
Figma tokens → CSS variables in an Nx lib
Signals/SignalStore ThemeStore applies vars at runtime
PrimeNG + charts consume the same tokens
Storybook previews; Chromatic guards; CI budgets enforce performance
Key takeaways
- Ship Figma tokens as CSS variables, not hardcoded SCSS—PrimeNG and charts read from the same source.
- Use an Nx tokens lib, a Signals/SignalStore ThemeStore, and a Storybook toolbar for theme/density/a11y previews.
- Chromatic blocks token drift: visual diffs, contrast checks, and per‑PR approvals.
- Typography, density, and color tokens drive PrimeNG, Highcharts/D3, and data tables consistently.
- Performance budgets and Lighthouse/INP are protected: tokens don’t bloat bundles when compiled as CSS vars.
- Multi‑tenant/role‑based themes are Feature‑Flaggable (Firebase Remote Config) without code rewrites.
Implementation checklist
- Export tokens from Figma (Tokens Studio) to JSON in an Nx lib
- Transform tokens → CSS variables via Style Dictionary or custom builder
- Map tokens to PrimeNG variables and component CSS parts
- Build a ThemeStore (Signals/SignalStore) for theme + density + contrast modes
- Wire Storybook toolbar toggles; add a11y + interactions addons
- Enable Chromatic visual tests and thresholds; require approvals on diffs
- Connect chart libraries (Highcharts/D3) to the same tokens
- Add performance budgets and Lighthouse/INP checks in CI
Questions we hear from teams
- How much does it cost to hire an Angular developer for a tokens + Storybook + Chromatic setup?
- Typical engagements start at 2–4 weeks. Small teams land it in 40–80 hours; complex, multi‑tenant platforms run 3–6 weeks. Fixed‑fee pilots available after a short code assessment.
- What does an Angular consultant do in this workflow?
- I set up Nx libs, token builds, ThemeStore with Signals, PrimeNG overrides, Storybook a11y + Chromatic, and CI budgets. I also train your team to own the pipeline and measure results.
- How long does an Angular upgrade or Signals adoption take?
- Signals adoption for theming/density is 1–2 sprints. Full upgrades (e.g., Angular 15→20) run 4–8 weeks alongside delivery, using feature flags and CI guardrails to avoid freezes.
- Can we use these tokens for charts and role-based dashboards?
- Yes. Highcharts/D3/Canvas read the same CSS variables. Role‑based palettes and density settings can be feature‑flagged per tenant without forking code.
- Will Chromatic slow us down?
- No. It speeds review by making token diffs visual and reviewable. Approvals take minutes, and you stop shipping accidental regressions that cost days later.
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