Design Tokens
All values are CSS custom properties. Apply data-theme="dark" or "light" on <html> to switch modes.
Color Palette
Brand, semantic, surface, and text tokens. Exact hex values below — no opacity guessing needed.
Kanban Status
Extended Platform Tokens
Additional tokens for KDispatch-specific UI patterns. Reference in both React and Angular implementations.
Status Background Tokens
Priority Tokens
Resource Tokens
Field Tokens
Shadow Tokens
Sidebar Tokens
Shadow Levels
Three elevation levels. Use consistently — no mixing.
Full CSS Token File
Copy this into your global stylesheet.
/* K1 DESIGN TOKENS — import in angular.json styles[] */ /* Font — Public Sans only */ @import url('https://fonts.googleapis.com/css2?family=Public+Sans:wght@300;400;500;600;700;800&display=swap'); :root { /* Spacing (4px grid) */ --space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px; --space-5: 20px; --space-6: 24px; --space-8: 32px; --space-10: 40px; --space-12: 48px; /* Typography */ --font: 'Public Sans', sans-serif; --text-xs: 11px; --text-sm: 12px; --text-base: 13px; --text-md: 14px; --text-lg: 16px; --text-xl: 20px; /* Heights */ --h-sm: 32px; --h-md: 36px; --h-lg: 40px; --h-xl: 48px; /* Radius */ --radius-sm: 4px; --radius-md: 8px; --radius-lg: 12px; --radius-xl: 16px; --radius-full: 9999px; }
Sidebar Tokens
Used by AppSidebar in both K-Safety and K-Traffic shells. Responds to theme toggle.
.sidebar { background: var(--sidebar-bg); width: var(--sidebar-width); } .sidebar-item { color: var(--sidebar-text); } .sidebar-item:hover { background: var(--sidebar-item-hover); } .sidebar-item.active { background: var(--sidebar-item-active); color: var(--sidebar-text-active); }
Header Tokens
Top chrome bar — height fixed at 64px. Background = page background (not a panel).
Kanban & Board Tokens
Used in KanbanSafetyView and the dispatcher's dispatch board. All change in light mode.
Priority Tokens
Incident priority — always uses these exact hues in both modes.
Severity Tokens — K-Traffic
Road severity scale used in Traffic Management Center views. Separate from incident priority.
Product Accent Tokens
Each K1 product module has its own accent used for module labels, active indicators, and status dots.
Panel & Overlay Tokens
Panel backgrounds, overlay scrims, chip/separator primitives.
Typography
Three fonts: Public Sans (UI default) · Inter (dense data, labels, headers) · JetBrains Mono (IDs, tokens, code). All loaded via Google Fonts + referenced through tokens.
Font Families
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
C5/00051 · #2b7fff
--font: 'Public Sans', sans-serif; /* default — all UI */ --font-ui: 'Inter', sans-serif; /* dense data labels */ --font-mono: 'JetBrains Mono', monospace; /* IDs, tokens, code snippets */
Type Scale — Public Sans
var(--font). Never hardcode 'Public Sans' — use the token. This ensures a single source of truth.Font Weight Reference
Spacing & Layout
4px base grid. Every padding, margin, and gap must use a token. No raw pixel values in component code.
Spacing Scale
Border Radius Scale
Component Height Reference
Fixed heights for interactive elements. Never deviate from these values.
| Token | Value | Usage |
|---|---|---|
| --h-xs | 24px | Icon-only mini buttons, tiny badges |
| --h-sm | 32px | Small buttons (btn-sm), toolbar actions, DS chrome |
| --h-md | 36px | Default inputs, selects, textareas (single-line) |
| --h-lg | 40px | Default buttons (p-button), icon buttons |
| --h-xl | 48px | Large buttons, prominent CTAs |
| --h-header | 60px | App header bar |
| --h-nav | 56px | Sidebar logo row |
Icons
All icons: inline SVG · 18×18px · stroke="currentColor" · stroke-width 1.3px · stroke-linecap/join: round. Grouped by platform domain. Click any cell to copy the SVG code.
Icon Specification
<svg width="18" height="18" fill="none" viewBox="0 0 18 18"> <path d="M..." stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" /> </svg> <!-- Angular: bind color via CSS class --> <svg class="k1-icon" width="18" height="18" fill="none" viewBox="0 0 18 18">...</svg> /* css */ .k1-icon { display: block; flex-shrink: 0; } .k1-icon-sm { width: 14px; height: 14px; } /* small context */ .k1-icon-lg { width: 20px; height: 20px; } /* large context */
Navigation & Views
Toolbar navigation icons. Always right-positioned tooltip on hover.
Incidents & Dispatch
Incident management, emergency types, and dispatch workflow.
Resources & Units
Emergency vehicles, personnel, and resource management.
Communication & Media
Radio, CCTV, phone, and communication channel icons.
Status & Feedback
System status, alerts, and user feedback icons.
Location & Map
Map, GPS, zones, and geolocation icons.
Data & Reports
Analytics, charts, reports, and data management icons.
System & Settings
Configuration, administration, and system management icons.
UI Actions
General-purpose UI interaction icons used across all components.
PrimeIcons — Required Set
K1 Dispatch uses PrimeIcons as the primary icon library for both PrimeReact (React) and PrimeNG (Angular). Usage: <i class="pi pi-{name}"></i> — inherits currentColor from parent. CDN: primeicons@7
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/primeicons@7/primeicons.css"/> <!-- React (NPM) --> npm install primeicons import 'primeicons/primeicons.css'; <!-- Angular (angular.json) --> "styles": ["node_modules/primeicons/primeicons.css"] <!-- Usage --> <i class="pi pi-send"></i> <i class="pi pi-exclamation-triangle" style="color:var(--color-error)"></i>
Buttons
Height: 40px (default) · 32px (sm) · 48px (lg). Padding: 0 16px. Radius: 8px. Font: 14px/500. All states explicitly defined.
Variants — All States
Hover the buttons to test interaction states. Each state has an exact CSS definition — no inheritance surprises.
<!-- Primary --> <p-button label="Dispatch" styleClass="k1-btn k1-btn-primary"></p-button> <!-- OR native --> <button class="k1-btn k1-btn-primary">Dispatch</button> <!-- Disabled --> <button class="k1-btn k1-btn-primary" disabled>Disabled</button>
Sizes
Buttons with PrimeIcons
Icon placement: leading icon (most common), trailing icon, or icon-only. Use pi pi-{name} inside the button. Size matches text naturally.
{/* React — leading icon */} <button className="k1-btn k1-btn-primary"> <i className="pi pi-send" /> Despachar </button> <!-- Angular — icon-only --> <button class="k1-btn k1-btn-icon-only k1-btn-outline"> <i class="pi pi-cog"></i> </button>
K1 Dispatch Button
Emergency action only. Gradient red with shadow. Not for general use.
Props Reference
| Class | Height | Padding | Font | Usage |
|---|---|---|---|---|
| k1-btn-primary | 40px | 0 16px | 14px/500 | Primary CTA, confirm actions |
| k1-btn-secondary | 40px | 0 16px | 14px/500 | Secondary actions |
| k1-btn-outline | 40px | 0 16px | 14px/500 | Tertiary, cancel |
| k1-btn-ghost | 40px | 0 16px | 14px/500 | Inline actions, low emphasis |
| k1-btn-danger | 40px | 0 16px | 14px/500 | Destructive — delete, revoke |
| k1-btn-dispatch | 40px | 0 20px | 14px/700 | Emergency dispatch ONLY |
| k1-btn-sm | 32px | 0 12px | 12px/500 | Compact spaces, table actions |
| k1-btn-lg | 48px | 0 24px | 14px/500 | Hero CTAs, form submits |
Badges & Status
Font: 11px/600. Padding: 2px 8px. Radius: 4px (badge) / 9999px (pill). All status colors are locked tokens.
Generic Badges
Kanban Status Pills
Font: 10px/700 uppercase. Padding: 3px 12px. Radius: 9999px. These are platform-specific — never use for generic labelling.
Resource Status
Form Controls
All inputs: height 36px · padding 0 12px · radius 8px · border 1px solid --color-border · focus: primary ring. Consistent across every form element.
Text Input — All States
<!-- PrimeNG input with K1 class --> <input pInputText class="k1-input" formControlName="location" placeholder="Peralvillo 87..." /> <!-- Error state driven by Angular reactive forms --> <span class="k1-error" *ngIf="form.get('location')?.invalid && submitted"> Campo requerido </span>
Input with Icon
Select / Dropdown
Custom dropdown replaces the native <select> and PrimeNG p-dropdown. Same height (36px), padding, border, and radius as text input.
<!-- Replace native select with k1-dd-wrap --> <div class="k1-dd-wrap"> <button class="k1-dd-trigger" (click)="toggleDd()"> <span class="k1-dd-trigger-label">{{ selected }}</span> <span class="k1-dd-trigger-icon"><!-- chevron svg --></span> </button> <div class="k1-dd-panel" role="listbox"> <div class="k1-dd-item" *ngFor="let opt of options" (click)="select(opt)"> <span class="k1-dd-item-text"> <span class="k1-dd-item-label">{{ opt.label }}</span> </span> </div> </div> </div>
Checkbox, Radio & Switch
Textarea
Dropdown
Custom dropdown replaces the native <select> and PrimeNG p-dropdown. Trigger height: 36px (--h-md). Panel: radius 12px, shadow-lg, animated entry. Options: 36px min-height. Supports search, groups, icons, badges, multi-select, and disabled states.
Pixel Specification
| Property | Value | Token |
|---|---|---|
| Trigger height | 36px | --h-md |
| Trigger padding | 0 12px | --space-3 |
| Trigger border-radius | 8px | --radius-md |
| Trigger font | Public Sans 13px/400 | --text-base / --fw-regular |
| Trigger border | 1px solid --color-border | — |
| Trigger bg | --color-surface-3 | — |
| Panel gap from trigger | 6px | — |
| Panel border-radius | 12px | --radius-lg |
| Panel border | 1px solid --color-border-strong | — |
| Panel shadow | --shadow-lg | — |
| Panel max-height | 320px (scrollable) | — |
| Option min-height | 36px | --h-md |
| Option padding | 8px 12px | --space-2 --space-3 |
| Option font | 13px/400 → 13px/600 selected | --text-base |
| Selected bg | --color-primary-subtle | — |
| Selected color | --color-primary | — |
| Hover bg | --color-surface-3 | — |
| Group label font | 10px/700 uppercase | --text-xs / --fw-bold |
| Chevron rotation (open) | 180° | transition: 0.18s |
| Panel animation | opacity + translateY(-6px) + scale(0.98) | 0.15s ease |
Trigger States
All interactive states use the same height and padding as k1-input — they must align visually in forms.
With Search
Use when the list has more than 8 options. Search input is sticky at the top of the panel. Font: 12px. Height: 32px (--h-sm).
With Icons & Descriptions
Leading icon (18×18) + 2-line text block: label 13px/400 + description 11px/--color-text-3. Used when options need additional context (unit status, resource type, etc.).
CSS Implementation
<!-- Wrapper provides positioning context --> <div class="k1-dd-wrap"> <!-- Trigger button --> <button class="k1-dd-trigger" aria-haspopup="listbox"> <span class="k1-dd-trigger-label is-placeholder">Seleccionar...</span> <span class="k1-dd-trigger-icon"><!-- chevron svg --></span> </button> <!-- Panel (add class is-open to show) --> <div class="k1-dd-panel" role="listbox"> <!-- Optional: search --> <div class="k1-dd-search-wrap"> <input class="k1-dd-search" placeholder="Buscar..." /> </div> <!-- Optional: group header --> <div class="k1-dd-group-label">Grupo</div> <!-- Option: add is-selected when chosen --> <div class="k1-dd-item is-selected" role="option"> <span class="k1-dd-item-icon"><!-- svg --></span> <span class="k1-dd-item-text"> <span class="k1-dd-item-label">Label</span> <span class="k1-dd-item-desc">Description</span> </span> <span class="k1-dd-item-check"><!-- checkmark svg --></span> </div> <!-- Divider --> <div class="k1-dd-divider"></div> </div> </div>
Angular / PrimeNG Integration
<p-dropdown [options]="tipoOptions" formControlName="tipo" placeholder="Seleccionar tipo..." styleClass="k1-dd-trigger" panelStyleClass="k1-dd-panel-ng" [filter]="true" filterPlaceholder="Buscar..." optionLabel="label" optionValue="value" [class.is-error]="form.get('tipo')?.invalid && submitted" ></p-dropdown>
/* Trigger wrapper */ .p-dropdown { height: var(--h-md); /* 36px */ border: 1px solid var(--color-border); border-radius: var(--radius-md); /* 8px */ background: var(--color-surface-3); transition: border-color 0.12s, box-shadow 0.12s; &:not(.p-disabled):hover { border-color: var(--color-border-strong); } &.p-focus { border-color: var(--color-primary); box-shadow: var(--focus-ring); } &.is-error { border-color: var(--color-error); } .p-dropdown-label { font-family: var(--font); font-size: var(--text-base); /* 13px */ font-weight: var(--fw-regular); color: var(--color-text-1); padding: 0 var(--space-3); line-height: var(--h-md); } .p-dropdown-label.p-placeholder { color: var(--color-text-3); } .p-dropdown-trigger { color: var(--color-text-3); width: 36px; } } /* Panel */ .p-dropdown-panel { background: var(--color-surface-2); border: 1px solid var(--color-border-strong); border-radius: var(--radius-lg); /* 12px */ box-shadow: var(--shadow-lg); margin-top: 6px; overflow: hidden; .p-dropdown-filter-container { padding: var(--space-2); .p-inputtext { height: 32px; font-size: var(--text-sm); background: var(--color-surface-3); border-color: var(--color-border); } } .p-dropdown-item { font-family: var(--font); font-size: var(--text-base); /* 13px */ min-height: 36px; padding: var(--space-2) var(--space-3); color: var(--color-text-1); transition: background 0.1s; &:hover { background: var(--color-surface-3); } &.p-highlight { background: var(--color-primary-subtle); color: var(--color-primary); font-weight: var(--fw-semibold); } } .p-dropdown-item-group { font-size: 10px; font-weight: var(--fw-bold); text-transform: uppercase; letter-spacing: 0.6px; color: var(--color-text-3); padding: var(--space-2) var(--space-3) var(--space-1); } }
Cards
Surface: --color-surface-2. Border: 1px solid --color-border. Radius: 16px (--radius-xl). Shadow: --shadow-sm. Internal padding: 20px 24px.
Base Card
Peralvillo 87, Tepito. BAMBE-01 en escena. Incendio controlado al 70%. Solicita apoyo de Protección Civil.
<!-- Option A: native div (recommended for K1) --> <div class="k1-card"> <div class="k1-card-header"> <div class="k1-card-title">Title</div> </div> <div class="k1-card-content">Content</div> <div class="k1-card-footer">Footer</div> </div> <!-- Option B: p-card with styleClass --> <p-card styleClass="k1-card"></p-card>
Alerts
Padding: 12px 16px. Radius: 8px. Font: 13px. Each variant uses token-based subtle background + border. No opacity hacks.
All Variants
Tabs
Tab height: 36px. Padding: 0 16px. Active: surface-4 bg + border + shadow-sm. Font: 13px/500 → 13px/600 active.
Default Tabs
Overlays & Panels
K1 uses slide-down panels from the header bar — not centered modals. Animation: translateY(-12px → 0) + opacity. Duration: 220ms. Easing: cubic-bezier(0.16,1,0.3,1).
Panel Pattern
/* Panel container */ .k1-panel { position: fixed; top: 60px; /* --h-header */ left: 50%; transform: translateX(-50%); width: 560px; max-height: calc(100vh - 80px); overflow-y: auto; background: var(--color-surface-2); border: 1px solid var(--color-border-strong); border-radius: 0 0 var(--radius-xl) var(--radius-xl); box-shadow: var(--shadow-lg); animation: panelIn 0.22s cubic-bezier(0.16,1,0.3,1); z-index: 200; } @keyframes panelIn { from { opacity: 0; transform: translateX(-50%) translateY(-12px); } to { opacity: 1; transform: translateX(-50%) translateY(0); } }
Kanban Board
5-column incident workflow board. Column width: 240px. Status top-border (3px) keyed to --color-status-*. Cards use the full k1-inc anatomy: tags · title · address · timer + action + case-ID · progress · priority + avatar + number.
Filter Bar
Full Board — Live Preview
Column Status Tokens
| Column | CSS var | Dark | Light | Progress fill % |
|---|---|---|---|---|
| New | --color-status-nuevo | #00b4d8 | #0096b4 | 5% |
| Review | --color-status-revision | #f59e0b | #d97706 | 25% |
| Assigned | --color-status-asignado | #4d7cfe | #3d6ef0 | 50% |
| On Scene | --color-status-escena | #ff5263 | #e8253b | 75% |
| Closed | --color-status-cerrado | #00d492 | #00a868 | 100% |
Vertical Toolbar
Width: 56px (collapsed) / 200px (expanded). Item height: 44px. Active: primary-subtle bg + 2px left border. Icons: 18×18px.
Collapsed State (56px)
Header Bar
Height: 60px (--h-header). Three zones: Logo (left) · Actions (center) · Status+Avatar (right). Live clock uses tabular-nums for stable layout.
Header Preview
Incident Card
Core Kanban tile — k1-inc component. Full anatomy: status pill + type tags + zone tag · title (13px/700) · address (11px/400) · timer + action button + case-ID link · 4px progress bar · priority chip + avatar + incident number. Radius: 8px. Padding: 10px 12px. Gap between zones: 6px.
Anatomy — Zone Breakdown
Each card is a flex column with six distinct zones. Every zone has a fixed token mapping.
| Zone | Class | Token / value |
|---|---|---|
| Status pill | k1-status k1-status-* | --color-status-* · 10px/700 |
| Type tag | k1-inc-tag k1-inc-tag-* | Semantic color · 10px/700 |
| Zone tag | k1-inc-tag k1-inc-tag-zone | --color-text-3 · border |
| Title | k1-inc-title | 13px / 700 / text-1 |
| Address | k1-inc-addr | 11px / 400 / text-2 |
| Timer | k1-inc-timer | 11px / 600 / tabular-nums |
| Units | k1-inc-units | 10px / text-3 |
| Action btn | k1-inc-action | primary or error border+bg |
| Case ID | k1-inc-caseid | 10px / primary / link |
| Progress | k1-inc-prog-fill | 4px / --color-status-* |
| Priority | k1-priority k1-priority-* | alta / media / baja |
| Avatar | k1-avatar k1-avatar-sm | 24px · surface-4 bg |
| Number | k1-inc-num | 10px / 700 / text-3 |
Card States — Default · Active · Closed
Action Button Variants — Per Workflow Stage
Avatars
Sizes: 24/32/40/48px. Status dot: 8×8px, bottom-right, 2px border (surface-2). Font weight: 700. Letter color: --color-primary.
Sizes & States
Feedback & Loading
Skeleton: shimmer animation at 1.5 s cycle. Progress track: 6 px, radius-full. Spinner: 0.75 s linear spin, three sizes. Empty state: centered, muted palette with optional CTA. Toast/Snackbar: four semantic variants (default, success, warning, error).
Skeleton Loader
Progress Bar
Spinner
Circular indeterminate loader. Sizes: is-sm (18 px), default (32 px), is-lg (48 px). Border uses --color-border; active arc uses --color-primary. Animation: 0.75 s linear spin.
.k1-spinner { width:32px; height:32px; border:3px solid var(--color-border);
border-top-color:var(--color-primary); border-radius:50%; animation:spin 0.75s linear infinite; }
.k1-spinner.is-sm { width:18px; height:18px; border-width:2px; }
.k1-spinner.is-lg { width:48px; height:48px; border-width:4px; }Empty State
Centered placeholder for zero-data views. Icon box (56 px, --radius-lg), muted title + description, optional CTA button.
<div class="k1-empty"> <div class="k1-empty-icon"><!-- icon svg --></div> <div class="k1-empty-title">No active incidents</div> <div class="k1-empty-desc">Descriptive helper text here.</div> <button class="k1-btn k1-btn-outline">Clear filters</button> </div>
Toast / Snackbar
Non-blocking feedback banners. Four variants: default, is-success, is-warning, is-error. Max-width 340 px. Shadow --shadow-lg. Positioned by a toast stack in production.
<div class="k1-toast is-success">
<svg class="k1-toast-icon" ...></svg>
<div class="k1-toast-body">
<div class="k1-toast-title">Incident closed</div>
<div class="k1-toast-msg">INC-2041 was resolved and archived.</div>
</div>
<button class="k1-toast-close">✕</button>
</div>
<!-- Variants: is-success · is-warning · is-error -->Tooltip
From Figma: 06 Feedback / 02 Tooltip. Two themes: Light (white) and Dark. Four positions: bottom ↓, top ↑, right →, left ←. Font: Public Sans 14px/400. Padding: 12px 16px. Radius: 8px. Max-width: 240px.
Light & Dark Themes
Figma specifies two distinct tooltip themes. Light uses a white background with dark text (#131523). Dark uses a near-black surface (#131523) with white text. Hover the trigger buttons below.
Tooltip Tokens — Pixel Spec
All values extracted directly from Figma. Every property is a strict token — no raw values in component CSS.
| Property | Light Theme | Dark Theme | Token / Value |
|---|---|---|---|
| Background | #ffffff | #131523 | Figma exact — not using surface tokens |
| Text color | #131523 | #ffffff | Figma exact |
| Font | Public Sans | var(--font) | |
| Font size | 14px | var(--text-md) | |
| Font weight | 400 (Regular) | var(--fw-regular) | |
| Line height | 20px | Figma: leading-[20px] | |
| Padding | 12px 16px | var(--space-3) var(--space-4) | |
| Border radius | 8px | var(--radius-md) | |
| Max width | 240px | Hardcoded max-width | |
| Arrow size | 6px | CSS border trick | |
| Gap to trigger | 10px | calc(100% + 10px) | |
| Shadow | md | var(--shadow-md) | |
| Transition | opacity + transform | 0.15s ease | |
Platform Usage in K1
Tooltips appear on icon-only toolbar buttons, header action icons, and table cell overflows. Use Dark theme on the kanban/dark backgrounds, Light on white card surfaces.
CSS Implementation
/* ── Tooltip wrapper (position: relative context) ── */ .k1-tooltip-wrap { position: relative; display: inline-flex; } /* ── Base bubble ── */ .k1-tooltip { position: absolute; z-index: 500; max-width: 240px; padding: var(--space-3) var(--space-4); /* 12px 16px */ border-radius: var(--radius-md); /* 8px */ font-family: var(--font); font-size: var(--text-md); /* 14px */ font-weight: var(--fw-regular); line-height: 20px; pointer-events: none; opacity: 0; transition: opacity 0.15s ease, transform 0.15s ease; white-space: normal; } /* ── Themes ── */ .k1-tooltip-light { background: #ffffff; color: #131523; box-shadow: var(--shadow-md); border: 1px solid var(--color-border); } .k1-tooltip-dark { background: #131523; color: #ffffff; box-shadow: var(--shadow-md); } /* ── Arrow ── */ .k1-tooltip::after { content: ''; position: absolute; width: 0; height: 0; border: 6px solid transparent; } /* ── Position: bottom (arrow at bottom of bubble) ── */ .k1-tooltip-bottom { bottom: calc(100% + 10px); left: 50%; transform: translateX(-50%) translateY(4px); } .k1-tooltip-bottom::after { top: 100%; left: 50%; transform: translateX(-50%); } .k1-tooltip-light.k1-tooltip-bottom::after { border-top-color: #ffffff; } .k1-tooltip-dark.k1-tooltip-bottom::after { border-top-color: #131523; } /* ── Position: top (arrow at top of bubble) ── */ .k1-tooltip-top { top: calc(100% + 10px); left: 50%; transform: translateX(-50%) translateY(-4px); } .k1-tooltip-top::after { bottom: 100%; left: 50%; transform: translateX(-50%); } .k1-tooltip-light.k1-tooltip-top::after { border-bottom-color: #ffffff; } .k1-tooltip-dark.k1-tooltip-top::after { border-bottom-color: #131523; } /* ── Position: right ── */ .k1-tooltip-right { left: calc(100% + 10px); top: 50%; transform: translateY(-50%) translateX(-4px); } .k1-tooltip-right::after { right: 100%; top: 50%; transform: translateY(-50%); } .k1-tooltip-light.k1-tooltip-right::after { border-right-color: #ffffff; } .k1-tooltip-dark.k1-tooltip-right::after { border-right-color: #131523; } /* ── Position: left ── */ .k1-tooltip-left { right: calc(100% + 10px); top: 50%; transform: translateY(-50%) translateX(4px); } .k1-tooltip-left::after { left: 100%; top: 50%; transform: translateY(-50%); } .k1-tooltip-light.k1-tooltip-left::after { border-left-color: #ffffff; } .k1-tooltip-dark.k1-tooltip-left::after { border-left-color: #131523; } /* ── Show on hover ── */ .k1-tooltip-wrap:hover .k1-tooltip-bottom, .k1-tooltip-wrap:hover .k1-tooltip-top { opacity: 1; transform: translateX(-50%) translateY(0); } .k1-tooltip-wrap:hover .k1-tooltip-right, .k1-tooltip-wrap:hover .k1-tooltip-left { opacity: 1; transform: translateY(-50%) translateX(0); }
Angular / PrimeNG — pTooltip Override
PrimeNG's pTooltip directive is the recommended integration. Override its generated classes to match K1 tokens exactly.
<!-- pTooltip directive — 4 positions --> <button pTooltip="Ver detalles del incidente" tooltipPosition="bottom" styleClass="k1-tooltip-dark" class="k1-btn k1-btn-icon-only k1-btn-ghost" > <!-- icon --> </button> <!-- tooltipPosition options: bottom | top | right | left --> <!-- styleClass: k1-tooltip-light (white bg) OR k1-tooltip-dark (dark bg) -->
/* Override PrimeNG tooltip to match K1 spec */ .p-tooltip .p-tooltip-text { font-family: var(--font); /* Public Sans */ font-size: var(--text-md); /* 14px */ font-weight: var(--fw-regular); /* 400 */ line-height: 20px; padding: var(--space-3) var(--space-4); /* 12px 16px */ border-radius: var(--radius-md); /* 8px */ max-width: 240px; box-shadow: var(--shadow-md); } /* Dark theme (default for K1 dark mode) */ .p-tooltip.k1-tooltip-dark .p-tooltip-text { background: #131523; color: #ffffff; border: 1px solid rgba(255,255,255,0.08); } .p-tooltip.k1-tooltip-dark.p-tooltip-bottom .p-tooltip-arrow { border-bottom-color: #131523; } .p-tooltip.k1-tooltip-dark.p-tooltip-top .p-tooltip-arrow { border-top-color: #131523; } .p-tooltip.k1-tooltip-dark.p-tooltip-right .p-tooltip-arrow { border-right-color: #131523; } .p-tooltip.k1-tooltip-dark.p-tooltip-left .p-tooltip-arrow { border-left-color: #131523; } /* Light theme */ .p-tooltip.k1-tooltip-light .p-tooltip-text { background: #ffffff; color: #131523; border: 1px solid var(--color-border); } .p-tooltip.k1-tooltip-light.p-tooltip-bottom .p-tooltip-arrow { border-bottom-color: #ffffff; } .p-tooltip.k1-tooltip-light.p-tooltip-top .p-tooltip-arrow { border-top-color: #ffffff; } .p-tooltip.k1-tooltip-light.p-tooltip-right .p-tooltip-arrow { border-right-color: #ffffff; } .p-tooltip.k1-tooltip-light.p-tooltip-left .p-tooltip-arrow { border-left-color: #ffffff; }
Usage Rules
| Context | Theme | Position | Notes |
|---|---|---|---|
| Toolbar icons | Dark | Right → | Always right — toolbar is on left edge |
| Header action buttons | Dark | Bottom ↓ | Header is at top — tooltip drops down |
| Form field hints | Light | Top ↑ | Appears above field to not cover input |
| Table cell overflow | Dark | Top ↑ | Shows full text on truncated cells |
| Inline icon buttons (on cards) | Light | Bottom ↓ | Light cards use light tooltip |
| Dispatch button (disabled) | Dark | Top ↑ | Explains why button is disabled |
PrimeReact Overrides
PrimeReact v10.x component overrides for KDispatch React product. CSS lives in src/design-system/k1-primereact-theme.css. Import once in main.tsx.
--primary, --field-bg, --background, etc.) — NOT the --color-* prefix used inside this design system HTML. Copy as-is into your React project's src/design-system/ folder.CSS Override Patterns
All overrides use K1 design tokens — no hardcoded hex values.
/* ── Dropdown ── */ .p-dropdown { height: var(--h-md); background: var(--field-bg); border: 1px solid var(--field-border); border-radius: var(--radius-md); font-family: var(--font); color: var(--field-text); transition: border-color 0.15s ease; } .p-dropdown:hover { border-color: var(--color-primary); } .p-dropdown.p-focus { box-shadow: var(--focus-ring); border-color: var(--color-primary); outline: none; } /* ── Dropdown Panel ── */ .p-dropdown-panel { background: var(--color-surface-2); border: 1px solid var(--color-border-strong); border-radius: var(--radius-md); box-shadow: var(--shadow-panel); } .p-dropdown-item { font-family: var(--font); font-size: var(--text-base); color: var(--color-text-1); border-radius: var(--radius-sm); padding: 6px 10px; } .p-dropdown-item:hover, .p-dropdown-item.p-highlight { background: var(--color-primary-subtle); color: var(--color-primary); } /* ── InputText ── */ .p-inputtext { height: var(--h-md); background: var(--field-bg); border: 1px solid var(--field-border); border-radius: var(--radius-md); font-family: var(--font); font-size: var(--text-base); color: var(--field-text); padding: 0 var(--space-3); width: 100%; } .p-inputtext:focus { box-shadow: var(--focus-ring); border-color: var(--color-primary); outline: none; } /* ── Calendar / DatePicker ── */ .p-calendar .p-inputtext { width: 100%; } .p-datepicker { background: var(--color-surface-2); border: 1px solid var(--color-border-strong); border-radius: var(--radius-lg); box-shadow: var(--shadow-panel); } .p-datepicker td > span:hover, .p-datepicker td > span.p-highlight { background: var(--color-primary-subtle); color: var(--color-primary); border-radius: var(--radius-sm); }
React Import & Usage
K1 wrapper components for PrimeReact primitives. Import from the design system components barrel.
// Design System components import { K1Select, K1Input, K1Textarea, K1Number, K1DatePicker } from '@/design-system/components'; // K1Select — wraps PrimeReact Dropdown <K1Select value={value} options={['Option A', 'Option B']} onChange={v => setValue(v)} placeholder="Select…" /> // With object options <K1Select value={status} options={[ { label: 'Active', value: 'active' }, { label: 'Closed', value: 'closed' }, ]} onChange={v => setStatus(v)} /> // K1Input <K1Input value={val} onChange={e => setVal(e.target.value)} placeholder="Enter text…" /> // k1-status-pill variant (for status dropdowns) <K1Select className="k1-status-pill" value={status} options={statusOptions} onChange={v => setStatus(v)} style={{ background: 'var(--color-primary)', color: '#fff' }} />
K1Select Props
Full props reference for the K1Select wrapper component.
| Prop | Type | Description |
|---|---|---|
value | any | Selected value (controlled) |
options | string[] | {label, value}[] | List of options — strings or label/value pairs |
onChange | (v: any) => void | Change handler — receives the selected value directly |
placeholder | string | Placeholder text when no value selected |
className | string | Extra CSS class — use k1-status-pill for pill style |
style | CSSProperties | Inline styles — only use CSS custom properties |
disabled | boolean | Disables the dropdown |
p-dialog → K1 Modal
PrimeReact Dialog override. Matches K1 overlay token values.
.p-dialog { background: var(--panel-bg); border: 1px solid var(--border-strong); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); color: var(--text-primary); } .p-dialog .p-dialog-header { background: var(--panel-header-bg); border-bottom: 1px solid var(--border); padding: var(--space-4) var(--space-5); font-size: var(--text-lg); font-weight: var(--fw-semibold); border-radius: var(--radius-lg) var(--radius-lg) 0 0; } .p-dialog .p-dialog-content { padding: var(--space-5) var(--space-6); background: var(--panel-bg); } .p-dialog .p-dialog-footer { padding: var(--space-4) var(--space-5); border-top: 1px solid var(--border); display: flex; justify-content: flex-end; gap: var(--space-2); } .p-dialog-mask { background: var(--overlay-bg); } .p-dialog .p-dialog-header-icon { color: var(--text-tertiary); &:hover { background: var(--chip); color: var(--text-primary); } }
p-toast → K1 Toast
Toast notifications. Position bottom-right. Auto-dismiss after 4s.
.p-toast .p-toast-message { background: var(--panel-bg); border: 1px solid var(--border); border-radius: var(--radius-md); box-shadow: var(--shadow-md); color: var(--text-primary); } .p-toast .p-toast-message.p-toast-message-success { border-color: var(--success); border-left: 3px solid var(--success); } .p-toast .p-toast-message.p-toast-message-error { border-left: 3px solid var(--error); } .p-toast .p-toast-message.p-toast-message-warn { border-left: 3px solid var(--warning); } .p-toast .p-toast-message-content { padding: var(--space-3) var(--space-4); font-size: var(--text-base); }
p-datatable → K1 Table
DataTable used in AdminDashboard, incident logs, unit rosters. Critical for kdispatch.
.p-datatable { border-radius: var(--radius-lg); overflow: hidden; } .p-datatable .p-datatable-thead > tr > th { background: var(--panel-header-bg); color: var(--text-tertiary); font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; padding: var(--space-2) var(--space-4); border-bottom: 1px solid var(--border); } .p-datatable .p-datatable-tbody > tr { background: var(--row-bg); border-bottom: 1px solid var(--border); transition: background 0.10s; &:hover { background: var(--row-hover); } } .p-datatable .p-datatable-tbody > tr > td { padding: var(--space-3) var(--space-4); font-size: var(--text-base); color: var(--text-primary); border: none; } .p-paginator { background: var(--panel-bg); border-top: 1px solid var(--border); padding: var(--space-2) var(--space-4); } .p-paginator .p-paginator-page.p-highlight { background: var(--primary); color: #fff; border-radius: var(--radius-sm); }
p-tag & p-chip → K1 Badge
Replace PrimeReact's default tag/chip colors with K1 semantic palette.
.p-tag { font-size: var(--text-xs); font-weight: 600; border-radius: var(--radius-sm); padding: 2px var(--space-2); border: 1px solid transparent; } .p-tag.p-tag-success { background: var(--success-subtle-bg); color: var(--success); border-color: var(--success-subtle-border); } .p-tag.p-tag-warning { background: var(--warning-subtle-bg); color: var(--warning); border-color: var(--warning-subtle-border); } .p-tag.p-tag-danger { background: var(--error-subtle-bg); color: var(--error); border-color: var(--error-subtle-border); } .p-tag.p-tag-info { background: var(--color-primary-subtle); color: var(--primary); } .p-chip { background: var(--chip); border: 1px solid var(--border); border-radius: var(--radius-full); color: var(--text-secondary); font-size: var(--text-xs); height: 24px; padding: 0 var(--space-2); }
React Patterns
KDispatch React architecture patterns. All components use CSS custom properties — zero hardcoded hex.
Token Usage Rule
Always reference tokens. Never hardcode color values in component style props or CSS files.
color: 'var(--color-primary)'
References the design token
color: '#2b7fff'
Hardcoded — breaks theme switching
Theme Switching
Set data-theme on the <html> element. All tokens update automatically — no JS color recalculation needed.
// Set dark mode document.documentElement.setAttribute('data-theme', 'dark'); // Set light mode document.documentElement.setAttribute('data-theme', 'light'); // React hook example const useTheme = () => { const [theme, setTheme] = useState<'dark' | 'light'>('dark'); useEffect(() => { document.documentElement.setAttribute('data-theme', theme); }, [theme]); return { theme, setTheme }; };
Form Field Pattern
Standard labelled field with K1Input. Use this structure for all form fields.
<div className="k1-field"> <label className="k1-label" htmlFor="incident-title"> Incident Title <span className="k1-required">*</span> </label> <K1Input id="incident-title" value={title} onChange={e => setTitle(e.target.value)} placeholder="e.g. Traffic accident on Av. Principal" /> {error && <span className="k1-field-error">{error}</span>} </div>
Status Badge Pattern
Use K1Select with the k1-status-pill class and inline color from status tokens for editable status badges.
const STATUS_COLORS: Record<string, string> = { nuevo: 'var(--status-nuevo-bg)', revision: 'var(--status-revision-bg)', asignado: 'var(--status-asignado-bg)', escena: 'var(--status-escena-bg)', cerrado: 'var(--status-cerrado-bg)', }; <K1Select className="k1-status-pill" value={status} options={statusOptions} onChange={v => setStatus(v)} style={{ background: STATUS_COLORS[status] }} />
Admin Layout Pattern
Standard flex structure used in AdminDashboardView — fixed sidebar + flex-1 content area.
<div style={{ display: 'flex', height: '100vh', overflow: 'hidden' }}> {/* Sidebar — fixed 210px */} <aside style={{ width: '210px', flexShrink: 0, background: 'var(--sidebar-bg)', borderRight: '1px solid var(--color-border)', display: 'flex', flexDirection: 'column', overflowY: 'auto', }}> <SidebarNav /> </aside> {/* Main content — flex-1 */} <main style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden', background: 'var(--color-bg)', }}> <HeaderBar /> <div style={{ flex: 1, overflowY: 'auto', padding: 'var(--space-6)' }}> <Outlet /> </div> </main> </div>
PrimeNG Overrides
Maps each PrimeNG component to the K1 system. All overrides use CSS custom properties — no hardcoded values in component-specific CSS.
--color-primary, --color-surface-*, etc.). When integrating into your Angular project, add the full token file from the Design Tokens page and use those variable names directly.Global PrimeNG Reset
Add to styles.scss or primeng.css. This establishes the K1 base for all PrimeNG components.
/* ── K1 PrimeNG Base Override ── */ :root { /* Map PrimeNG theme vars to K1 tokens */ --primary-color: var(--color-primary); --primary-color-text: #ffffff; --surface-0: var(--color-bg); --surface-50: var(--color-surface-1); --surface-100:var(--color-surface-2); --surface-200:var(--color-surface-3); --surface-300:var(--color-surface-4); --text-color: var(--color-text-1); --text-color-secondary:var(--color-text-2); --border-radius: var(--radius-md); --focus-ring: var(--focus-ring); } /* Force Public Sans on all PrimeNG elements */ .p-component { font-family: var(--font) !important; } .p-inputtext { font-family: var(--font) !important; } .p-button { font-family: var(--font) !important; } .p-dropdown { font-family: var(--font) !important; } .p-datatable { font-family: var(--font) !important; }
p-button → k1-btn
/* Remove ALL PrimeNG button defaults */ .p-button { all: unset; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); height: var(--h-lg); /* 40px */ padding: 0 var(--space-4); /* 0 16px */ border-radius: var(--radius-md); /* 8px */ font-family: var(--font); font-size: var(--text-md); /* 14px */ font-weight: var(--fw-medium); /* 500 */ background: var(--color-primary); color: #fff; border: 1px solid transparent; transition: background 0.12s, box-shadow 0.12s; white-space: nowrap; &:hover { background: var(--color-primary-hover); } &:focus { outline: none; box-shadow: var(--focus-ring); } &:active { background: var(--color-primary-active); } &.p-button-secondary { background: var(--color-surface-4); color: var(--color-text-1); border-color: var(--color-border); &:hover { background: var(--color-surface-3); } } &.p-button-outlined { background: transparent; color: var(--color-text-1); border-color: var(--color-border-strong); &:hover { background: var(--color-surface-2); } } &:disabled { opacity: 0.4; pointer-events: none; } } /* Small variant */ .p-button.p-button-sm { height: var(--h-sm); /* 32px */ padding: 0 var(--space-3); /* 0 12px */ font-size: var(--text-sm); /* 12px */ border-radius: var(--radius-sm); }
p-inputText / p-dropdown
.p-inputtext {
height: var(--h-md); /* 36px */
padding: 0 var(--space-3); /* 0 12px */
border-radius: var(--radius-md); /* 8px */
border: 1px solid var(--color-border);
background: var(--color-surface-3);
color: var(--color-text-1);
font-family: var(--font);
font-size: var(--text-base); /* 13px */
font-weight: var(--fw-regular);
transition: border-color 0.12s, box-shadow 0.12s;
box-shadow: none;
&::placeholder { color: var(--color-text-3); }
&:enabled:hover { border-color: var(--color-border-strong); }
&:enabled:focus { border-color: var(--color-primary); box-shadow: var(--focus-ring); }
&.ng-invalid.ng-dirty { border-color: var(--color-error); }
}
/* p-dropdown wrapper */
.p-dropdown {
height: var(--h-md);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background: var(--color-surface-3);
.p-dropdown-label { font-family: var(--font); font-size: var(--text-base); }
.p-dropdown-panel { background: var(--color-surface-2); border: 1px solid var(--color-border-strong); border-radius: var(--radius-md); box-shadow: var(--shadow-md); }
.p-dropdown-item { font-family: var(--font); font-size: var(--text-base); padding: var(--space-2) var(--space-3); }
.p-dropdown-item:hover { background: var(--color-surface-4); }
.p-dropdown-item.p-highlight { background: var(--color-primary-subtle); color: var(--color-primary); }
}p-table
.p-datatable {
.p-datatable-thead > tr > th {
background: var(--color-surface-2);
color: var(--color-text-3);
font-family: var(--font);
font-size: 10px;
font-weight: var(--fw-bold);
text-transform: uppercase;
letter-spacing: 0.5px;
padding: var(--space-2) var(--space-4);
border-bottom: 1px solid var(--color-border);
}
.p-datatable-tbody > tr > td {
font-family: var(--font);
font-size: var(--text-base);
padding: var(--space-3) var(--space-4);
border-bottom: 1px solid var(--color-border);
color: var(--color-text-2);
}
.p-datatable-tbody > tr:hover > td {
background: var(--color-surface-3);
}
.p-datatable-tbody > tr.p-highlight > td {
background: var(--color-primary-subtle);
}
}p-dialog
.p-dialog {
background: var(--color-surface-2);
border: 1px solid var(--color-border-strong);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-lg);
font-family: var(--font);
.p-dialog-header {
padding: var(--space-5) var(--space-6);
border-bottom: 1px solid var(--color-border);
font-size: var(--text-lg);
font-weight: var(--fw-bold);
color: var(--color-text-1);
}
.p-dialog-content { padding: var(--space-6); }
.p-dialog-footer {
padding: var(--space-4) var(--space-6);
border-top: 1px solid var(--color-border);
display: flex;
justify-content: flex-end;
gap: var(--space-2);
}
}
.p-dialog-mask { background: rgba(0,0,0,0.6); backdrop-filter: blur(4px); }p-toast → K1 Toast
PrimeNG Toast notifications — override to match K1 semantic colors exactly.
.p-toast .p-toast-message { background: var(--color-surface-2); border: 1px solid var(--color-border); border-radius: var(--radius-md); box-shadow: var(--shadow-md); color: var(--color-text-1); } .p-toast .p-toast-message.p-toast-message-success { border-left: 3px solid var(--color-success); } .p-toast .p-toast-message.p-toast-message-error { border-left: 3px solid var(--color-error); } .p-toast .p-toast-message.p-toast-message-warn { border-left: 3px solid var(--color-warning); } .p-toast .p-toast-message.p-toast-message-info { border-left: 3px solid var(--color-info); } .p-toast .p-toast-message-content { padding: var(--space-3) var(--space-4); font-size: var(--text-base); }
p-table → K1 DataTable
Angular DataTable for admin views, incident logs, and unit rosters. Compact row height.
.p-datatable { border-radius: var(--radius-lg); overflow: hidden; } .p-datatable .p-datatable-thead > tr > th { background: var(--color-surface-4); color: var(--color-text-3); font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; padding: var(--space-2) var(--space-4); border-bottom: 1px solid var(--color-border); } .p-datatable .p-datatable-tbody > tr { background: var(--color-surface-1); border-bottom: 1px solid var(--color-border); transition: background 0.10s; &:hover { background: var(--color-surface-3); } } .p-datatable .p-datatable-tbody > tr > td { padding: var(--space-3) var(--space-4); font-size: var(--text-base); color: var(--color-text-1); border: none; } .p-paginator { background: var(--color-surface-2); border-top: 1px solid var(--color-border); padding: var(--space-2) var(--space-4); } .p-paginator .p-paginator-page.p-highlight { background: var(--color-primary); color: #fff; border-radius: var(--radius-sm); }
p-tag & p-chip → K1 Badge / Chip
PrimeNG tag and chip components mapped to K1 semantic colors.
.p-tag { font-size: var(--text-xs); font-weight: 600; border-radius: var(--radius-sm); padding: 2px var(--space-2); border: 1px solid transparent; } .p-tag.p-tag-success { background: var(--color-success-subtle); color: var(--color-success); border-color: var(--color-success-border); } .p-tag.p-tag-warning { background: var(--color-warning-subtle); color: var(--color-warning); border-color: var(--color-warning-border); } .p-tag.p-tag-danger { background: var(--color-error-subtle); color: var(--color-error); border-color: var(--color-error-border); } .p-tag.p-tag-info { background: var(--color-primary-subtle); color: var(--color-primary); } .p-chip { background: rgba(255,255,255,0.06); border: 1px solid var(--color-border); border-radius: var(--radius-full); color: var(--color-text-2); font-size: var(--text-xs); height: 24px; padding: 0 var(--space-2); }
Angular Patterns
Component naming, module structure, and template patterns for K1 Angular implementation.
CSS Architecture
src/
styles/
tokens.css # CSS custom properties (copy from Tokens page)
primeng.scss # PrimeNG overrides (copy from PrimeNG page)
components.css # k1-btn, k1-input, k1-badge, k1-card, etc.
global.scss # @import all of the above
app/
components/
k1-button/ # Wraps p-button with K1 defaults
k1-input/ # Wraps pInputText + k1-field + k1-label
k1-badge/ # Status / resource / generic badge
k1-incident-card/# Kanban tile with all states
k1-kanban/ # Board container + columns
k1-toolbar/ # Vertical sidebar toolbar
k1-header/ # App header bar with clockangular.json — Styles
"styles": [ "node_modules/primeng/resources/themes/lara-dark-blue/theme.css", "node_modules/primeng/resources/primeng.min.css", "node_modules/primeicons/primeicons.css", "src/styles/tokens.css", // K1 tokens "src/styles/components.css", // K1 component classes "src/styles/primeng.scss", // PrimeNG overrides (LAST — highest specificity) "src/styles.scss" ]
K1Input Component Pattern
@Component({ selector: 'k1-input', template: ` <div class="k1-field"> <label class="k1-label" *ngIf="label"> {{ label }} <span style="color:var(--color-error)" *ngIf="required"> *</span> </label> <div [class.k1-input-icon-wrap]="iconLeft"> <ng-content select="[slot=icon-left]"></ng-content> <input pInputText [class.k1-input]="true" [class.is-error]="control?.invalid && control?.touched" [placeholder]="placeholder" [disabled]="disabled" [formControl]="control" /> </div> <span class="k1-error" *ngIf="control?.invalid && control?.touched"> {{ errorMessage }} </span> <span class="k1-hint" *ngIf="hint && !control?.invalid">{{ hint }}</span> </div> ` }) export class K1InputComponent { @Input() label = ''; @Input() placeholder = ''; @Input() hint = ''; @Input() errorMessage = 'Campo requerido'; @Input() required = false; @Input() disabled = false; @Input() iconLeft = false; @Input() control!: FormControl; }
Component Class Naming Rules
| Pattern | Example | Rule |
|---|---|---|
| k1-{component} | k1-btn, k1-card | Base component |
| k1-{comp}-{variant} | k1-btn-primary | Visual variant |
| k1-{comp}-{size} | k1-btn-sm, k1-avatar-md | Size modifier |
| is-{state} | is-active, is-error, is-on | JS-toggled state (Angular class binding) |
| --color-{name} | --color-primary | CSS token — always use token, never raw value |