KCDS — Design system documentation
Component reference for the unified KnowledgeCity product UI. Every component below is shown in all of its states — default, hover, focus, active, disabled, error — across both density modes, with live theme and product category switching to demonstrate how the entire chrome shifts per Native product.
Color
Three tiers — primitive, semantic, tenant. Components reference semantic tokens, never raw hex. Status colors are universal across every KC product.
Brand orange ramp
Neutrals
Semantic tokens — status
Product suite accents
KCDS has three product suites — Learn, Comply, Grow — and each owns a primary color. Internal/admin tools are not a suite and carry no accent (neutral gray chrome). Buttons, focus rings, links, active states, hover tints — all resolve from the suite. KC corporate orange is the logo-mark anchor in every product; in Learn it is also the suite accent. Try the switcher in the sidebar to see every component on this page shift colors live. (3-suite model per ADR 012/013/014/015; the former Perform/indigo and Platform/cyan categories were removed.)
Three suite primaries + internal
Activated by data-kcds-product-category="..." on <body>. Valid values: learn · comply · grow · internal.
Live preview — chrome shifts per category
Each preview is scoped to its own data-kcds-product-category. The button, focus ring, active sidebar item, and link all resolve from the category.
Learn = orange — the AA exception
Learn owns the KC master orange, so the flagship learner products (KC Library, KC LMS) stay on the brand orange they ship today. Two rules apply to Learn only:
- On-primary is WHITE (#FFFFFF), light and dark. This is a deliberate, accepted WCAG AA exception — white on
orange-500is 2.39:1. The master brand orange is never darkened, and dark text on orange was rejected as off-brand (ADR 012). Links on white still useorange-700(#C75F00 — text on white, not on the orange fill). - Warnings must not rely on colour alone in Learn. Since status-warning is also orange, a warning in a Learn product is differentiated by icon + leading-stripe, not hue.
What stays orange — always
- KC logo mark — the orange "kc" circle is the corporate anchor in every product, every suite. In Comply/Grow it is the single brand accent against the suite hue; in Learn the chrome is also orange.
- Legacy products — LMS 3.0, LMS Admin Legacy, Portal Legacy — kept as
formerlyaliases; because Learn = orange, these legacy learner surfaces are already on their suite colour.
What never changes per suite
- Status colors — green success, red error, amber/orange warning, blue info, gray neutral. A "Completed" badge stays green in every suite. Status semantics always override the suite accent.
- AI augmentation — violet (#8543DA), system-fixed. An AI suggestion chip in any product (AI Teacher, KC Studio) stays violet regardless of suite.
- Severity & sensitivity colors — system-fixed across all KC products.
- Body text, borders, shadows, radius, spacing, typography — system primitives, never tinted by suite.
Indirection rule (v1.0+)
When a semantic token references another semantic token via var(...),
the cascade silently breaks at suite-override boundaries. CSS Custom
Properties resolve at the declaring element, not the styled descendant.
These tokens MUST be redeclared as direct primitives in every suite
and theme block:
| Token | Reason |
|---|---|
--kcds-color-text-link | Links render orange on non-Learn pages without explicit redeclaration. |
--kcds-color-border-focus | Focus rings render orange on non-Learn pages without explicit redeclaration. |
--kcds-color-on-primary-tint | Active sidebar / chip / pager text on tint backgrounds — needs hue-specific dark text in light theme, lifted for legibility in dark theme. |
--kcds-color-on-primary | White for every suite except internal dark, where the light neutral CTA needs dark on-primary. Redeclared in Learn (white AA exception) and internal so it never inherits the wrong value. |
Dark theme
System-wide opt-in via data-kcds-theme="dark" on <body>.
Cascades over data-kcds-product-category — accent hue persists,
but tints and fills become semi-transparent overlays. Toggle the theme in
the sidebar to see every component on this page switch live.
Surface hierarchy
The light rule "white chrome on gray canvas" inverts cleanly: dark chrome (#181D22) on darker canvas (#0F1419). Modals and popovers sit lighter still. Hierarchy reads via color contrast, not shadow — shadows in dark are nearly invisible.
Suite accents in dark — recomputed
Each suite accent is recomputed to a PD-205 OKLCH-balanced dark value (L≈0.55) — light-mode values read muddy on dark canvas. Learn mirrors the orange dark chain; Comply and Grow shift to their calibrated dark teal / blue. on-primary stays white for every suite (Learn's white-on-orange is the accepted AA exception). Internal is the inversion — a light neutral CTA reads on the dark surface, so its on-primary is dark, the one non-suite exception.
| Suite | Light primary | Dark primary | Strategy |
|---|---|---|---|
| Legacy / Orange | #FF881A | #FF881A | unchanged — hover lifts to #FF9D40 |
| KC Learn / Orange | #FF881A | #FF881A | mirrors legacy orange chain; white on-primary (AA exception) |
| KC Comply / Teal | #0A7A7A | #0A8282 | PD-205 dark teal — hover lifts to #238E8E |
| KC Grow / Blue | #006CD0 | #0874C8 | PD-205 dark blue — hover lifts to #2080D5 |
| Internal / Gray | #2C3E46 | #E8EAED | inverts — light neutral CTA on dark, dark on-primary #1F2023 |
Tint / fill — rgba overlays in dark
Solid pale-color blocks look like glowing burns on dark surfaces. A light-mode
blue tint #E4F5FF becomes dark-mode tint rgba(50, 157, 255, 0.10) —
the hue is preserved, but it sits as a transparent overlay over the actual
dark surface beneath.
Status colors lift for contrast
Saturated mid-tone status colors (green-700, red-500) become unreadable on dark surfaces. They lift to *-400/300 palette values for visibility, and their backgrounds become rgba overlays.
| Status | Light | Dark |
|---|---|---|
| Success | #00A01A | #4ADE80 |
| Error | #F03E3E | #F87171 |
| Warning | #FF881A | #FBBF24 |
| Info | #329DFF | #60A5FA |
| Featured / AI | #8543DA | #A78BFA |
Elevation in dark — borders, not shadows
Drop shadows on dark backgrounds are nearly invisible. Elevation is communicated by stepping up the surface lightness (canvas → surface → elevated) plus 1px borders. Shadow tokens stay in code — they contribute in light and degrade gracefully in dark.
Visual discipline
Four hard rules promoted to zero-rule status (F-067–F-070). They are violated more often than anything else and quietly break the system. A surface that follows them reads as KCDS before any component-level polish.
1 · Colour is a budget
One colour-carrying channel per component. The icon carries
the accent, the value stays text-primary, and the
meaning lives in the label — not three tinted things competing.
If everything is coloured, nothing reads as emphasis (F-067).
2 · The accent is never a status colour
The product accent (suite primary) and the status palette are separate systems. Never colour a "success"/"error"/"warning" state with the suite accent, and never use green/red/amber to mean "primary action". Status semantics always override the accent — a "Completed" badge is green in every suite (F-068). (In Learn, where the accent is orange and warning is also orange, differentiate the warning by icon + leading stripe.)
3 · font-weight is 400 / 700 only
Lato is loaded at 400 and 700 only. 500/600 are
forbidden — Google Fonts synthesizes a fuzzy pseudo-bold. Never mix weights
inside one line (it breaks tabular-nums); max two type styles
per card header (F-069).
| Element | Weight |
|---|---|
| Body, meta, badges, status text, tab / button / nav labels, input values | 400 |
| Card titles, structural headings (h1–h5), KPI values, table header labels | 700 |
Anything at 500 or 600 | ❌ forbidden |
4 · Never white-on-white
The fill and the border trade places depending on the parent surface. A control on a white surface uses a visible border and a subtle fill; the same control on a tinted/canvas surface uses a white fill and a lighter border. A white control on a white parent (no border) disappears (F-070).
Typography
Lato as the primary face. Twelve tokens — eight canonical, four edge cases. The default heading flow is H1 → H2 → H4. H3 only when an admin sub-section needs extra weight.
Spacing
4px-based rhythm. Density mode picks the actual values used in components.
Radius
Pattern A leans toward larger radii (32 px section frames). Pattern B keeps tighter corners.
Elevation
Three depths in light theme. In dark, drop shadows are nearly invisible — elevation is communicated by stepping up the surface lightness + 1px borders. Shadow tokens stay in code; they degrade gracefully (no-op in dark). Toggle the theme to see the three cards switch from shadow-based to surface-based elevation.
Density
Two modes — same tokens, different output values. Comfortable for learner / Portal. Compact for admin / LMS.
Spec table
| Element | Comfortable | Compact |
|---|---|---|
| Button — Medium | 56 px · r20 · 16/Regular | 32 px · r8 · 14/Bold |
| Button — Large | 64 px · r24 | 40 px · r12 |
| Button — Small | 48 px · r16 | 28 px · r6 |
| Input field | 52 px · r20 · 16 px body | 32 px · r8 · 14 px body |
| Status badge | pill · 14/600 · pad 6/16 | r4 · 12/700 · pad 2/8 |
| Table row | 56 px · pad 16 · body 16 | 32 px · pad 8/12 · body 14 |
| Default body | --kcds-text-body-m · 16/26 | --kcds-text-body-s · 14/20 |
| Card padding | 24–32 px | 12–16 px |
| Section radius | 32 px (Pattern A) | 16 px (Pattern B) |
Button
One primary per logical decision point. Secondary may sit beside it. Tertiary may sit beside either. Destructive is red and always preceded by confirmation.
Variants by weight
Sizes — Large · Medium · Small
Icon button
Icon-only actions for constrained space or universally-recognised actions (close, edit, delete, more).
Button group
For two or more closely related actions presented as a unit. Used for view switchers and segmented controls.
Text input
Label is mandatory. Placeholder is not a label. Focus ring is 3 px brand-tinted.
Textarea
Switch
For boolean settings that take effect immediately. For deferred application, use a checkbox.
Checkbox
Radio
Opinion scale
Discrete choice on a labeled spectrum — Likert (1–5 agree/disagree),
NPS (0–10 detractor/promoter), easiness (1–7), CSAT (1–5 smileys).
Only the selected position is highlighted — never cumulative.
That is the visual difference from Rating below.
Rating
A scalar star rating. Stars 1..N are all filled when rating = N — cumulative.
Used for course/instructor/content quality feedback. The opposite encoding from Opinion scale:
rating means "I reached this level", opinion means "I am at this position".
Status badge
Status semantics are universal across every KC product. Overdue is always red. In-progress is always blue.
Severity badge
Used in Compliance Operations, Safety, audit reports. Sortable by severity rank.
Filter chip
Progress
Alert
Inline page-level messages. Five variants — info, success, warning, error, AI.
Toast
Brief, dismissible, dark. For action confirmations. Disappears after 4–6 seconds unless hovered.
AI suggestion panel
Universal across every KC product where AI surfaces a suggestion. Always violet-accented. User must explicitly accept or dismiss.
Avatar
KPI tile
Used in admin dashboards. White card, subtle border, no colored borders.
Table
Header in surface-canvas tone. Hover highlight on rows. Selected rows tinted with primary-tint. Compact is the default for admin surfaces.
| Learner | Team | Progress | Status | Last active | |
|---|---|---|---|---|---|
|
Kseniia Zaitseva
kseniia.z@example.com
|
Operations | 88% |
On track | 2 hours ago | |
|
JT
Joaquín Torres
joaquin.t@example.com
|
Engineering | 42% |
Due soon | Yesterday | |
|
DH
Dafydd Hughes
dafydd.h@example.com
|
Compliance | 18% |
Overdue | 12 days ago |
| Learner | Team | Progress | Status | Last active | |
|---|---|---|---|---|---|
|
Kseniia Zaitseva
kseniia.z@example.com
|
Operations | 88% |
On track | 2 hours ago | |
|
JT
Joaquín Torres
joaquin.t@example.com
|
Engineering | 42% |
Due soon | Yesterday | |
|
DH
Dafydd Hughes
dafydd.h@example.com
|
Compliance | 18% |
Overdue | 12 days ago |
Required table pattern (v1.3.0 · F-081–F-084)
Admin tables are sortable by default. The full pattern is mandatory — a bare <th> is a failure.
- Type-aware sortable headers. Each sortable
<th>carriesaria-sortcycling three states —none → ascending → descending. Sort direction is type-aware: text A→Z, numbers low→high, dates old→new. Only one column sorted at a time. - Range-readout pagination that resets on filter change. Show "1–12 of 340", not just page numbers; when a filter changes, reset to page 1 and recompute the readout.
- Three distinct empty states. No data yet (onboarding CTA) ≠ no results for this filter (clear-filters action) ≠ error loading (retry). Never one generic "No data".
- Labelled trailing row actions. A
data-no-sortactions column; the kebab reveals on:focus-within(never hover-only — that fails keyboard/touch) and stays permanent on touch; focus returns to the originating kebab on menu close. - Card-scoped overflow. Horizontal scroll lives on the table container, never on
body/html. Numeric columns usefont-variant-numeric: tabular-numsso digits align. - Mobile (4 rules): hide secondary columns, turn sticky off, shrink the primary-cell max-width, consolidate chips. Row detail opens a bottom sheet, not a route.
Chart honesty (F-085–F-089)
- Empty ≠ small. No data renders an explicit empty state, never a flat/zero bar that reads as a real value of zero.
- Show the track. Progress/meter fills always show their container track so 0% is distinguishable from "not rendered".
- Multi-channel ramps. Series are separated by more than hue (pattern, label, position) — colour alone fails for colour-blind users.
Tabs
Pagination
Modal
For decisions that must complete before continuing. Otherwise prefer inline patterns.
Tooltip
For supplementary information on hover or focus. Never for critical info.
Loading
Skeleton for layout-known loading. Spinner for unknown duration or small surfaces.
Empty state
Always tell the user what is missing and what they can do next. Never just "No data".
Link
Navigates to another page or resource. Buttons act, links navigate. Inline links use brand color with optional underline on hover.
Read the full documentation or browse the component reference to get started.
Read the full documentation or browse the component reference to get started.
Number input
For numeric values with stepper buttons. Triggers numeric keyboard on mobile.
Password
Masked by default with a reveal toggle. Same dimensions as text input.
Email input
Same structure as text input, with type="email". Triggers
the email keyboard on mobile (with @ and .com prominent)
and gives the browser basic format validation. Visually identical to
text input — functionally distinct via the type attribute.
Date picker
For specific calendar dates. Always paired with a calendar surface — never free typing. Supports single-date and range selection.
Date range picker
Two dates marking a period. Connected dual calendar on desktop, sequential single calendar on mobile. Always offer common range presets — Last 7 days, This month, etc — they cover ~80% of use cases. Validate that end date is never before start date.
File upload
Dashed border drop zone with optional click-to-browse fallback. Uploaded files render as rows below.
Rich text editor
Use sparingly. Most descriptions don't need formatting. Toolbar exposes only the formatting that matters for the surface.
Operations team — Q2 onboarding cohort.
Target completion by end of quarter; manager review monthly.
Operations team — Q2 onboarding cohort.
Target completion by end of quarter; manager review monthly.
Signature pad
Hand-drawn attestation capture (<canvas>) that locks into a tamper-proof record on submit. A pad is never the only signing path — a typed-name equivalent is mandatory.
| State | Treatment |
|---|---|
| Empty | Dashed frame, "Sign above" hint, baseline visible |
| Drawing | Live stroke (Pointer Events, smoothing, high-DPR scaling) |
| Signed | Stroke + "Signed" confirmation, Clear enabled |
| Submitted-locked | Hands off to the tamper-proof-record sealed treatment (never invents its own) |
- Accessibility:
role="img"+aria-livestate announcements; the mandatory typed-name field is the non-drawing signing path. - Approved Lucide only:
signature,refresh-ccw,lock,shield-check. Existing tokens only; shares the bounded form-column measure.
Select
Choose one option from a list. Trigger looks like an input; menu opens below with selectable items.
Autocomplete
Like a select, but the user types to filter options. Used when the option set is large, dynamic, or partly unknown. Always debounce server queries (100-200ms), show a loading indicator while a request is in flight, and handle the no-results state with an optional "Add new" action where applicable.
Multi-select
Choose multiple options. Selected items render as removable tags inside the field.
Segmented control
For 2–4 mutually exclusive views. Different from button group: segmented sits in a tinted track, button group does not.
Choice tiles
A single-select tile group for form-submit choices that need per-option colour. One component recoloured per group via --tile-accent / --tile-tint / --tile-on. Two presets: product-accent and severity.
Product-accent preset
Severity preset (via data-level)
Severity is always a tint background + saturated text — solid red/amber/green fills fail WCAG AA.
- Corner-check pitfall: an absolute corner check knocks a centred label off optical centre — omit it on centred tiles, or left-align and reserve symmetric padding.
- Don't use a connected segmented control for a form-submit choice needing per-option colour — its
overflow:hiddenclips the selection ring and it reads as a different component family.
Sensitivity badge
Document classification — Public, Internal, Confidential, Restricted, Secret. Used in Compliance Operations and any product handling sensitive data.
AI badge
Marks any content authored, suggested, or curated by AI. Always violet-accented and gradient-tinted to distinguish from status badges.
Count badge
Numeric indicator for unread, pending, or new items. Brand color by default; error red for urgent counts.
Inline message
Lightweight feedback inside or under a field. Shorter and quieter than a full alert.
Card
A bounded surface for grouped content. Subtle gray border, no colored borders. Clickable cards lift on hover.
List row
For lists where each row needs a leading element (avatar, icon), title, sub, and trailing meta. Lighter than a table.
Stat
Standalone large number with label. Use for hero KPIs and dashboard headlines.
Key-value
For displaying paired metadata: account details, technical specs, settings summaries.
Timeline
Events on a real date axis. Encodes event type by shape — milestone (dot), range (bar), marker (rule) — never by status colour (avoids the severity collision). Not a stepper (order, not dates), not an audit log, not an activity feed.
| Event type | Shape | Use |
|---|---|---|
| Milestone | Dot | A single point in time (release, sign-off) |
| Range | Bar | A span with start + end (rollout window) |
| Marker | Rule | A reference line (today, deadline) |
- Accessibility: the absolutely-positioned track needs a mandatory ordered-list equivalent — spatial position is invisible to assistive tech. Events are focusable buttons; detail opens on hover and focus and tap.
- Responsive: collapses to a vertical timeline on narrow viewports / long sets. RTL: axis mirrors via logical properties (earliest at inline-start).
Gantt
Task rows + duration bars on a date axis, with finish-to-start dependencies. Shares the date-axis mechanics with the timeline. Critical path is emphasis (accent), explicitly not status-red — a bar can be both critical and overdue.
The outlined bar marks the critical path (accent emphasis, not red).
- Layout: two-pane with a pinned task-name column (frozen-column technique from Table).
- Accessibility: mandatory real-table equivalent (
role="table"); bars are focusable with dates + dependency in thearia-label; dependencies / critical path never conveyed by colour or line alone. - Responsive: falls back to the table on mobile / long plans.
Nine-box
A labelled 3×3 talent matrix (performance × potential) whose cells are named segments holding employee cards. Explicitly not a data table or a dot-scatter. Cell tint follows the conventional diagonal value tier via status -bg tokens with neutral borders. Used by KC Performance (Grow).
Columns = Performance (low → high) · Rows = Potential (high → low). Value tier tints the diagonal.
- Hero-element composition: the department filter, legend and summary KPIs live inside the hero panel; the page's secondary surfaces are separate cards.
- Drill-down: a cell opens the canonical right-edge detail sheet (segment roster) with focus trap / Esc / restore; bottom sheet below
md. - Mobile reflow: the 3×3 stacks into one column in grid reading order, each cell carrying a
(Performance, Potential)coordinate chip so the matrix model survives (44px targets).
Breadcrumbs
Shows the current page's position in the site hierarchy. Last item is current page (not a link).
Back button
Returns to a parent or previous page. Light text-only treatment so it doesn't compete with primary actions.
Stepper
For multi-step flows. Each step has a status: complete (green check), current (orange), or upcoming (gray).
Drawer
Side panel that slides in from an edge. For contextual editing without leaving the current page.
Popover
For richer contextual content than a tooltip. Has a title, body, optional actions, and arrow pointing to the trigger.
Bottom sheet
Mobile-only modal that slides up from the bottom. Used for filters, action menus, multi-select on small screens.
Message bubbles
User bubbles use brand orange. AI bubbles use white with subtle border. Typing dots indicate streaming response.
Citation chips
Required for factual AI answers. Each chip links to the source. Numbered for inline references in the response text.
Suggested chips
Follow-up question chips after AI responses, or starter prompts in empty conversations. Tapping sends the chip text as the next message.
Confirmation card
When AI proposes a state-changing action (enroll, assign, schedule), the user must confirm before execution. The confirmation appears as a structured card inside the chat — not as a modal. Cancel always sits left of Confirm.
Clarification
When AI is below its confidence threshold for an intent, it asks a clarifying question rather than guess. Styled as a normal AI message — not as an error or warning. Tone is curious and collaborative, never apologetic. Usually 2-3 disambiguation chips + optional free-text fallback.
Refusal / out-of-scope
When the query is outside the AI's scope, the assistant refuses and explains what it can help with. Visual register is muted — gray/tertiary text, not red error styling. Refusals are not failures, they are redirects. Tone is helpful, never apologetic.
- Training enrollment and completion data
- Performance cycle status and scores
- Safety incidents and corrective actions
- TNA reports and skill gaps
- Training enrollment and completion data
- Performance cycle status and scores
- Safety incidents and corrective actions
- TNA reports and skill gaps
Soft acknowledgment
Brief between-question responses that prevent a multi-turn conversation from feeling like a form. Same styling as regular AI message — no special bubble. 1-2 short sentences: repeat the user's input + brief affirmation. Used heavily in the TNA Interview Agent. Never flatter ("Excellent question!") and never over-acknowledge.
Next question: How would you describe the team\'s current proficiency with cloud infrastructure?
Next question: How would you describe the team\'s current proficiency with cloud infrastructure?
Voice input
Mobile and accessibility users benefit from voice-to-text. Mic button sits at the leading edge of the input. Active state uses AI violet with a pulsing animation; the waveform indicates audio capture. For reduced-motion users, the waveform falls back to a static "Listening…" label.
Resume / session continuity
Conversations persist across reloads and browser sessions. When a user returns, show a thin resume banner at the top of the conversation followed by a contextual welcome-back message. Always offer continue/start-fresh as suggested chips.
Want to pick up from where we left off, or start something new?
Want to pick up from where we left off, or start something new?
Conversation export
Users can download full transcripts. PDF for humans (with citations preserved as text), JSON for downstream consumption. Trigger lives in the chat header menu. Privacy: exports contain only role-scoped data the user already saw.
Error state
Full-surface error treatment. Always offer a path forward — retry, support contact, or alternative action.
Canonical references
Full-page, self-contained examples — specification by example. Words describe rules; these show what KC actually looks like and how interactive elements behave. Each preview is a live, scaled-down render; click any card (or Open ↗) to open the full page in a new tab. All 20 ship light + dark with a working theme toggle, on the 3-suite accent model.