Ir al contenido

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]].

PropertyValue
PresetLara
PrimaryNoir — primary maps to surface (monochrome)
SurfaceZinc
Color SchemeLight (dark via .app-dark class on <html>)
Layoutsakai-ng: fixed topbar + collapsible sidebar + main content
TypographySystem font stack via var(--font-family)
Theme Configfrontend/src/app/theme.config.tsprovidePrimeNG({ theme: { preset: Lara } })
Runtime ThemeAppConfigurator applies Noir/Zinc at runtime via $t()
ShellAppLayout (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).

SourceExampleWhen
PrimeNG CSS variablestyle="color: var(--p-surface-700)"Typography, custom elements
PrimeNG component propseverity="success"Component semantic color
tailwindcss-primeui utilitytext-primary, bg-surface-cardRare — when style attr is awkward
PrimeNG legacy variablevar(--surface-ground), var(--text-color)Global baseline (styles.css)
PatternWhy
text-zinc-500, bg-slate-100Raw Tailwind colors bypass the design system
text-red-500, text-green-500Use PrimeNG severity or var(--p-red-500)
dark: variantPrimeNG CSS variables auto-resolve for both schemes
#3B82F6, rgb(...) in templatesHardcoded values don’t respond to theme changes

These rules extend the token conventions in [[adr-012-c-theming-tokens]].

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 everywhere
3. [dt] scoped → Per-instance token override (no ::ng-deep)
4. [pt] passthrough → DOM-level class/attribute injection
5. Tailwind class → Last resort for things PrimeNG doesn't model

Examples:

<!-- 1. Prop (best) -->
<p-button severity="danger" size="small" />
<!-- 3. [dt] scoped override -->
<p-toggleswitch [dt]="customSwitch" />
// In component class
customSwitch = {
colorScheme: {
light: { root: { checkedBackground: '{amber.500}' } },
dark: { root: { checkedBackground: '{amber.400}' } }
}
};

Never use ::ng-deep. It is deprecated and breaks encapsulation.

For EVERY UI need, there is ONE answer. The showcase (frontend/src/app/showcase/) validates each visually.

UI NeedComponentImportNotes
Content sectionp-cardCard from primeng/cardWhite background on zinc-100 ground. Use #header, #footer templates
Collapsible sectionp-panelPanel from primeng/panel[toggleable]="true". Header bar separates from card
Tab containerp-tabsTabs, TabList, Tab, TabPanels, TabPanel from primeng/tabsNew API: <p-tabs value="0"> with <p-tablist> + <p-tabpanels>
UI NeedComponentImportNotes
Primary actionp-buttonButton from primeng/buttonDefault = Noir (near-black). Strongest visual weight
Secondary actionp-button [outlined]="true"sameBorder only, no fill
Tertiary actionp-button [text]="true"sameNo border, no fill. Lowest weight
Destructivep-button severity="danger"sameRed. For delete/remove
With iconp-button icon="pi pi-*"samePrimeIcons. Browse: primeng.org/icons
Sizesp-button size="small|large"sameThree sizes. Default for most UI
UI NeedComponentImportNotes
Tabular datap-tableTableModule from primeng/table[stripedRows]="true". Use #header + #body templates
Status indicatorp-tagTag from primeng/tagseverity="success|warn|danger|info|secondary"
Count badgep-badgeBadge from primeng/badgeNumeric. Same severity system
Removable tokenp-chipChip from primeng/chip[removable]="true" for selections
User avatarp-avatarAvatar from primeng/avatarlabel="AB" shape="circle". Circle for people, square for entities
Completionp-progressbarProgressBar from primeng/progressbarDefault height or thin: [style]="{ height: '6px' }"
UI NeedComponentImportNotes
Text inputinput pInputTextInputText from primeng/inputtextAlways inside p-floatlabel variant="on"
Textareatextarea pTextareaTextarea from primeng/textareaSame float label pattern
Passwordinput pInputText type="password"InputTextNOT p-password for Signal Forms ([formField] compat)
Numberp-inputnumberInputNumber from primeng/inputnumbermode="currency", mode="decimal"
Dropdownp-selectSelect from primeng/select[options], optionLabel, optionValue
Datep-datepickerDatePicker from primeng/datepickerCalendar popup
Checkboxp-checkboxCheckbox from primeng/checkbox[binary]="true" for boolean
Radiop-radiobuttonRadioButton from primeng/radiobuttonGroup via same [(ngModel)]
Togglep-toggleswitchToggleSwitch from primeng/toggleswitchOn/off state
Float labelp-floatlabelFloatLabel from primeng/floatlabelAlways use. variant="on" preferred
File uploadp-fileuploadFileUpload from primeng/fileuploadSee kdx-angular-http for S3 pattern
UI NeedComponentImportNotes
Inline messagep-messageMessage from primeng/messageseverity="success|info|warn|error|secondary|contrast"
Loading spinnerp-progressSpinnerProgressSpinner from primeng/progressspinnerFull component loading
Skeletonp-skeletonSkeleton from primeng/skeletonPlaceholder during @defer
Empty statep-message severity="info"MessageInside @empty block
UI NeedComponentImportNotes
Page action barp-toolbarToolbar from primeng/toolbar#start = title/context. #end = action buttons
Menu barp-menubarMenubar from primeng/menubarMain navigation
Breadcrumbsp-breadcrumbBreadcrumb from primeng/breadcrumbRoute hierarchy
Sidebar menup-panelmenuPanelMenu from primeng/panelmenuCollapsible groups
Section dividerp-dividerDivider from primeng/dividerVisual separator within cards

Exact hierarchy used throughout the application. Do not deviate.

ClassColor tokenUse for
text-4xl font-boldvar(--p-surface-900)Page title (one per page)
text-2xl font-semiboldvar(--p-surface-800)Section heading
text-lg font-semiboldvar(--p-surface-700)Card heading, subsection
text-basevar(--p-surface-700)Body text
text-smvar(--p-surface-500)Secondary/muted text, descriptions
text-xsvar(--p-surface-400)Labels, captions, metadata
text-xs font-monovar(--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]].

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.ts before referencing upstream.
FileRole
frontend/src/tailwind.css@font-face (Outfit), @theme with --font-brand, --font-sans, --font-title
frontend/src/styles.cssbody { font-family: var(--font-sans) } — explicit fallback
frontend/src/index.htmlGoogle Fonts import (Inter variable), preload (Outfit woff2)
FileRole
frontend/src/app/theme.config.tsKdxPreset — 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
FileRole
frontend/src/app/showcase/pages/typography.component.tsVisual showcase of the 3-tier typography system
frontend/src/app/showcase/pages/custom-components.component.tsLiving catalog of all kdx-* components

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.

frontend/src/tailwind.css
@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;
}

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>

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>

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);
}
}

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>