Design System — Key Files & Reference
Design System — Key Files & Reference
Sección titulada «Design System — Key Files & Reference»Reference map for locating design system configuration across the codebase. For decisions and rationale, see [[adr-012-design-system]] and its sub-ADRs: [[adr-012-a-custom-component-naming]], [[adr-012-b-typography]], [[adr-012-c-theming-tokens]].
Theme Identity
Sección titulada «Theme Identity»| Property | Value |
|---|---|
| Preset | Lara |
| Primary | Noir — primary maps to surface (monochrome) |
| Surface | Zinc |
| Color Scheme | Light (dark via .app-dark class on <html>) |
| Layout | sakai-ng: fixed topbar + collapsible sidebar + main content |
| Typography | System font stack via var(--font-family) |
| Theme Config | frontend/src/app/theme.config.ts — providePrimeNG({ theme: { preset: Lara } }) |
| Runtime Theme | AppConfigurator applies Noir/Zinc at runtime via $t() |
| Shell | AppLayout (src/app/layout/component/app.layout.ts) — wraps all authenticated + showcase routes |
What “Noir” means: buttons, links, and highlights use the same neutral zinc scale as the page. Primary-950 (#09090b) is near-black. This creates a sophisticated, typography-first aesthetic where color is reserved for semantic meaning (success/warn/danger/info).
Color Rules
Sección titulada «Color Rules»Allowed
Sección titulada «Allowed»| Source | Example | When |
|---|---|---|
| PrimeNG CSS variable | style="color: var(--p-surface-700)" | Typography, custom elements |
| PrimeNG component prop | severity="success" | Component semantic color |
tailwindcss-primeui utility | text-primary, bg-surface-card | Rare — when style attr is awkward |
| PrimeNG legacy variable | var(--surface-ground), var(--text-color) | Global baseline (styles.css) |
Forbidden
Sección titulada «Forbidden»| Pattern | Why |
|---|---|
text-zinc-500, bg-slate-100 | Raw Tailwind colors bypass the design system |
text-red-500, text-green-500 | Use PrimeNG severity or var(--p-red-500) |
dark: variant | PrimeNG CSS variables auto-resolve for both schemes |
#3B82F6, rgb(...) in templates | Hardcoded values don’t respond to theme changes |
These rules extend the token conventions in [[adr-012-c-theming-tokens]].
Customization Hierarchy
Sección titulada «Customization Hierarchy»When you need to change a component’s appearance, follow this order. Stop at the first level that works.
1. Component prop → severity="warn", size="small", [outlined]="true"2. PrimeNG token → Change in KdxPreset (theme.config.ts) — cascades everywhere3. [dt] scoped → Per-instance token override (no ::ng-deep)4. [pt] passthrough → DOM-level class/attribute injection5. Tailwind class → Last resort for things PrimeNG doesn't modelExamples:
<!-- 1. Prop (best) --><p-button severity="danger" size="small" />
<!-- 3. [dt] scoped override --><p-toggleswitch [dt]="customSwitch" />// In component classcustomSwitch = { colorScheme: { light: { root: { checkedBackground: '{amber.500}' } }, dark: { root: { checkedBackground: '{amber.400}' } } }};Never use ::ng-deep. It is deprecated and breaks encapsulation.
Component Catalog
Sección titulada «Component Catalog»For EVERY UI need, there is ONE answer. The showcase (frontend/src/app/showcase/) validates each visually.
Containers
Sección titulada «Containers»| UI Need | Component | Import | Notes |
|---|---|---|---|
| Content section | p-card | Card from primeng/card | White background on zinc-100 ground. Use #header, #footer templates |
| Collapsible section | p-panel | Panel from primeng/panel | [toggleable]="true". Header bar separates from card |
| Tab container | p-tabs | Tabs, TabList, Tab, TabPanels, TabPanel from primeng/tabs | New API: <p-tabs value="0"> with <p-tablist> + <p-tabpanels> |
Actions
Sección titulada «Actions»| UI Need | Component | Import | Notes |
|---|---|---|---|
| Primary action | p-button | Button from primeng/button | Default = Noir (near-black). Strongest visual weight |
| Secondary action | p-button [outlined]="true" | same | Border only, no fill |
| Tertiary action | p-button [text]="true" | same | No border, no fill. Lowest weight |
| Destructive | p-button severity="danger" | same | Red. For delete/remove |
| With icon | p-button icon="pi pi-*" | same | PrimeIcons. Browse: primeng.org/icons |
| Sizes | p-button size="small|large" | same | Three sizes. Default for most UI |
Data Display
Sección titulada «Data Display»| UI Need | Component | Import | Notes |
|---|---|---|---|
| Tabular data | p-table | TableModule from primeng/table | [stripedRows]="true". Use #header + #body templates |
| Status indicator | p-tag | Tag from primeng/tag | severity="success|warn|danger|info|secondary" |
| Count badge | p-badge | Badge from primeng/badge | Numeric. Same severity system |
| Removable token | p-chip | Chip from primeng/chip | [removable]="true" for selections |
| User avatar | p-avatar | Avatar from primeng/avatar | label="AB" shape="circle". Circle for people, square for entities |
| Completion | p-progressbar | ProgressBar from primeng/progressbar | Default height or thin: [style]="{ height: '6px' }" |
Form Inputs
Sección titulada «Form Inputs»| UI Need | Component | Import | Notes |
|---|---|---|---|
| Text input | input pInputText | InputText from primeng/inputtext | Always inside p-floatlabel variant="on" |
| Textarea | textarea pTextarea | Textarea from primeng/textarea | Same float label pattern |
| Password | input pInputText type="password" | InputText | NOT p-password for Signal Forms ([formField] compat) |
| Number | p-inputnumber | InputNumber from primeng/inputnumber | mode="currency", mode="decimal" |
| Dropdown | p-select | Select from primeng/select | [options], optionLabel, optionValue |
| Date | p-datepicker | DatePicker from primeng/datepicker | Calendar popup |
| Checkbox | p-checkbox | Checkbox from primeng/checkbox | [binary]="true" for boolean |
| Radio | p-radiobutton | RadioButton from primeng/radiobutton | Group via same [(ngModel)] |
| Toggle | p-toggleswitch | ToggleSwitch from primeng/toggleswitch | On/off state |
| Float label | p-floatlabel | FloatLabel from primeng/floatlabel | Always use. variant="on" preferred |
| File upload | p-fileupload | FileUpload from primeng/fileupload | See kdx-angular-http for S3 pattern |
Feedback
Sección titulada «Feedback»| UI Need | Component | Import | Notes |
|---|---|---|---|
| Inline message | p-message | Message from primeng/message | severity="success|info|warn|error|secondary|contrast" |
| Loading spinner | p-progressSpinner | ProgressSpinner from primeng/progressspinner | Full component loading |
| Skeleton | p-skeleton | Skeleton from primeng/skeleton | Placeholder during @defer |
| Empty state | p-message severity="info" | Message | Inside @empty block |
Navigation & Chrome
Sección titulada «Navigation & Chrome»| UI Need | Component | Import | Notes |
|---|---|---|---|
| Page action bar | p-toolbar | Toolbar from primeng/toolbar | #start = title/context. #end = action buttons |
| Menu bar | p-menubar | Menubar from primeng/menubar | Main navigation |
| Breadcrumbs | p-breadcrumb | Breadcrumb from primeng/breadcrumb | Route hierarchy |
| Sidebar menu | p-panelmenu | PanelMenu from primeng/panelmenu | Collapsible groups |
| Section divider | p-divider | Divider from primeng/divider | Visual separator within cards |
Typography Scale
Sección titulada «Typography Scale»Exact hierarchy used throughout the application. Do not deviate.
| Class | Color token | Use for |
|---|---|---|
text-4xl font-bold | var(--p-surface-900) | Page title (one per page) |
text-2xl font-semibold | var(--p-surface-800) | Section heading |
text-lg font-semibold | var(--p-surface-700) | Card heading, subsection |
text-base | var(--p-surface-700) | Body text |
text-sm | var(--p-surface-500) | Secondary/muted text, descriptions |
text-xs | var(--p-surface-400) | Labels, captions, metadata |
text-xs font-mono | var(--p-surface-400) | Code annotations |
How to apply: always via inline style attribute for color, Tailwind class for size/weight.
<h2 class="text-2xl font-semibold mb-1" style="color: var(--p-surface-800)"> Section Title</h2><p class="mb-6" style="color: var(--p-surface-500)"> Description of this section.</p>For the 3-tier font system (Brand/Title/UI, Inter, Outfit), see [[adr-012-b-typography]].
KdxPreset Extension Pattern
Sección titulada «KdxPreset Extension Pattern»Full context in the kdx-design-system-use skill. This is the canonical TypeScript structure for extending KdxPreset in frontend/src/app/theme.config.ts (formerly app.config.ts):
const KdxPreset = definePreset(Lara, { // Primitive: raw palette values primitive: { brand: { 50: '#f0fdf4', // ... 950: '#052e16', } }, // Semantic: map semantic names to primitives semantic: { primary: { 50: '{brand.50}', // ... 950: '{brand.950}', }, // TRAP: tokens inside colorScheme must be overridden inside colorScheme colorScheme: { light: { surface: { 0: '#ffffff', 50: '{zinc.50}', /* ... */ } }, dark: { surface: { 0: '#ffffff', 50: '{slate.50}', /* ... */ } } } }});Rules:
- Always extend — never replace Lara entirely.
- Verify upstream token names at
github.com/primefaces/primeuix/tree/main/packages/themes/src/presets/lara. - The current preset is Lara (not Aura). Confirm the active preset in
theme.config.tsbefore referencing upstream.
Key Files
Sección titulada «Key Files»Typography
Sección titulada «Typography»| File | Role |
|---|---|
frontend/src/tailwind.css | @font-face (Outfit), @theme with --font-brand, --font-sans, --font-title |
frontend/src/styles.css | body { font-family: var(--font-sans) } — explicit fallback |
frontend/src/index.html | Google Fonts import (Inter variable), preload (Outfit woff2) |
Theming
Sección titulada «Theming»| File | Role |
|---|---|
frontend/src/app/theme.config.ts | KdxPreset — PrimeNG Lara preset with Noir/Zinc overrides. Single theming entry point. |
frontend/src/tailwind.css | @theme block with custom tokens extending PrimeNG’s design tokens |
Showcase
Sección titulada «Showcase»| File | Role |
|---|---|
frontend/src/app/showcase/pages/typography.component.ts | Visual showcase of the 3-tier typography system |
frontend/src/app/showcase/pages/custom-components.component.ts | Living catalog of all kdx-* components |
Tailwind v4 Extension Patterns
Sección titulada «Tailwind v4 Extension Patterns»All extensions go in frontend/src/tailwind.css. For decision logic on when and where to extend, see the kdx-tailwind-design-system skill.
Add non-color tokens only — spacing, sizing, fonts, container sizes, animations. Colors always belong in KdxPreset.
@theme { /* Spacing step not in Tailwind's scale */ --spacing-18: 4.5rem;
/* Display font (layout concern, not a brand color) */ --font-display: 'Inter', system-ui;
/* Named max-width container */ --container-content: 72rem;}Usage:
<div class="pt-18 font-display max-w-content">Namespace override — clear defaults and define a minimal custom scale (rare):
@theme { --spacing-*: initial; --spacing-1: 0.25rem; --spacing-2: 0.5rem;}@utility
Sección titulada «@utility»For styles that PrimeNG doesn’t express and that you’ll reuse across templates. All custom utilities must be prefixed kdx-.
/* Multi-line truncation — no PrimeNG equivalent */@utility kdx-truncate-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;}
@utility kdx-truncate-3 { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;}
/* Full-bleed horizontal rule using PrimeNG surface token */@utility kdx-rule-h { position: relative; &::before { content: ''; position: absolute; top: 0; left: -100vw; height: 1px; width: 200vw; background: var(--surface-border); }}
/* Text gradient — purely presentational, no PrimeNG equivalent */@utility kdx-text-gradient-primary { background: linear-gradient(to right, var(--p-primary-400), var(--p-primary-600)); background-clip: text; -webkit-background-clip: text; color: transparent;}Usage in Angular templates:
<p class="kdx-truncate-2">Long description...</p><section class="kdx-rule-h pt-6">Content below a full-bleed rule</section><h1 class="kdx-text-gradient-primary text-4xl font-bold">Title</h1>Animations
Sección titulada «Animations»Define @keyframes inside @theme {} — Tailwind v4 only outputs them when the --animate-* variable is referenced:
@theme { --animate-enter: enter 0.2s ease-out; --animate-leave: leave 0.15s ease-in; --animate-slide-up: slide-up 0.3s ease-out;
@keyframes enter { from { opacity: 0; transform: translateY(-0.25rem); } to { opacity: 1; transform: translateY(0); } }
@keyframes leave { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(-0.25rem); } }
@keyframes slide-up { from { opacity: 0; transform: translateY(1rem); } to { opacity: 1; transform: translateY(0); } }}Usage:
<div class="animate-enter">Fades in</div>Native popover animation (@starting-style)
Sección titulada «Native popover animation (@starting-style)»For custom popover elements not using PrimeNG’s overlay system:
[popover] { transition: opacity 0.2s, transform 0.2s, display 0.2s allow-discrete; opacity: 0; transform: scale(0.97);}
[popover]:popover-open { opacity: 1; transform: scale(1);}
@starting-style { [popover]:popover-open { opacity: 0; transform: scale(0.97); }}Container Queries
Sección titulada «Container Queries»Use when layout must respond to a parent container’s size, not the viewport.
@theme { --container-card: 28rem; --container-panel: 40rem;}<div class="@container"> <div class="grid grid-cols-1 @card:grid-cols-2 @panel:grid-cols-3 gap-4"> @for (item of items(); track item.id) { <app-item-card [item]="item" /> } </div></div>