Skip to content

Kisum Design System

Related documentation: Frontend Implementation · Frontend Applications · Frontend Nextkt (palette exception)

Published source of truth. This page is the full, committed design system for the Kisum platform (also live at docs.kisum.dev). Local workspace DESIGN.md (if present) is optional tooling sidecar for Impeccable/design panels — do not treat it as the GitHub canonical copy.

Cursor enforcement: .cursor/rules/00-design.mdc — applies when editing Frontend-Kisum*/**/*.{tsx,css} or System-Kisum-Checkout/**/*.{tsx,css}.


Machine-readable token block for design tools, Tailwind mapping, and Impeccable sidecars:

name: Kisum
description: Platform-wide analyst-confident design system for all Kisum product surfaces (Admin, Promoters, Artists, Venues, Finance, Checkout, Website)
colors:
kisum-primary: "#8466AC"
kisum-accent: "#9A7AFF"
kisum-tint: "#F3EEF8"
sidebar-bg: "lab(98.26% 0 0)"
main-bg: "#FFFFFF"
ink: "#18181B"
surface: "#FFFFFF"
shell: "#F4F4F5"
body-bg: "#F9FAFB"
border-subtle: "#F3F4F6"
muted-text: "#71717A"
danger: "#D34848"
md-surface: '#faf8fd'
md-surface-dim: '#dbd9de'
md-surface-bright: '#faf8fd'
md-surface-container-lowest: '#ffffff'
md-surface-container-low: '#f5f3f7'
md-surface-container: '#efedf2'
md-surface-container-high: '#e9e7ec'
md-surface-container-highest: '#e3e2e6'
md-on-surface: '#1b1b1f'
md-on-surface-variant: '#4a454f'
md-inverse-surface: '#303034'
md-inverse-on-surface: '#f2f0f4'
md-outline: '#7b7580'
md-outline-variant: '#ccc3d0'
md-surface-tint: '#6e5095'
md-primary: '#6a4d91'
md-on-primary: '#ffffff'
md-primary-container: '#8466ac'
md-on-primary-container: '#fffafa'
md-inverse-primary: '#d8b9ff'
md-secondary: '#5e5e63'
md-on-secondary: '#ffffff'
md-secondary-container: '#e4e1e8'
md-on-secondary-container: '#65646a'
md-tertiary: '#5f5f00'
md-on-tertiary: '#ffffff'
md-tertiary-container: '#78781d'
md-on-tertiary-container: '#fffaff'
md-error: '#ba1a1a'
md-on-error: '#ffffff'
md-error-container: '#ffdad6'
md-on-error-container: '#93000a'
md-primary-fixed: '#eddcff'
md-primary-fixed-dim: '#d8b9ff'
md-on-primary-fixed: '#28074d'
md-on-primary-fixed-variant: '#55397b'
md-secondary-fixed: '#e4e1e8'
md-secondary-fixed-dim: '#c8c5cc'
md-on-secondary-fixed: '#1b1b20'
md-on-secondary-fixed-variant: '#47464c'
md-tertiary-fixed: '#e8e881'
md-tertiary-fixed-dim: '#cccb68'
md-on-tertiary-fixed: '#1d1d00'
md-on-tertiary-fixed-variant: '#494900'
md-background: '#faf8fd'
md-on-background: '#1b1b1f'
md-surface-variant: '#e3e2e6'
typography:
display:
fontFamily: "var(--font-manrope), ui-sans-serif, system-ui, sans-serif"
fontSize: "1.125rem"
fontWeight: 600
lineHeight: 1.25
letterSpacing: "-0.025em"
body:
fontFamily: "Inter, ui-sans-serif, system-ui, sans-serif"
fontSize: "0.875rem"
fontWeight: 400
lineHeight: 1.5
letterSpacing: "normal"
label:
fontFamily: "Inter, ui-sans-serif, system-ui, sans-serif"
fontSize: "0.6875rem"
fontWeight: 600
lineHeight: 1.2
letterSpacing: "normal"
rounded:
sm: 0.25rem
DEFAULT: 0.5rem
md: 0.75rem
lg: 1rem
xl: 1.5rem
full: 9999px
spacing:
xs: "4px"
sm: "8px"
md: "16px"
lg: "24px"
xl: "32px"
container-max: 1280px
gutter: 1.5rem
margin-mobile: 1rem
margin-desktop: 2.5rem
stack-sm: 0.5rem
stack-md: 1rem
stack-lg: 2rem
components:
button-kisum:
backgroundColor: "{colors.kisum-primary}"
textColor: "#FFFFFF"
rounded: "{rounded.full}"
padding: "8px 16px"
button-kisum-hover:
backgroundColor: "#74579A"
textColor: "#FFFFFF"
rounded: "{rounded.full}"
padding: "8px 16px"
button-outline:
backgroundColor: "{colors.surface}"
textColor: "{colors.kisum-primary}"
rounded: "{rounded.full}"
padding: "8px 16px"
card-surface:
backgroundColor: "{colors.surface}"
textColor: "{colors.ink}"
rounded: "{rounded.xl}"
padding: "24px"
overview-badge:
backgroundColor: "{colors.kisum-tint}"
textColor: "{colors.kisum-primary}"
rounded: "{rounded.full}"
padding: "2px 8px"
backgrounds:
bg-sidebar:
css: "background-color: var(--sidebar)"
token: "{colors.sidebar-bg}"
bg-main:
css: "background-color: var(--main)"
token: "{colors.main-bg}"
css-vars:
sidebar: "{colors.sidebar-bg}"
main: "{colors.main-bg}"

This is the single Kisum platform design system. Every Kisum product UI must follow it:

  • Frontend-Kisum-Promoters, Frontend-Kisum-Artists, Frontend-Kisum-Venues
  • Frontend-Kisum-Admin, Frontend-Kisum-Finance, Frontend-Kisum, Frontend-Kisum-Website
  • System-Kisum-Checkout and other Kisum-branded surfaces in this workspace

Do not fork aesthetics per app, module, or persona. Reuse the same tokens, typography, elevation rules, and component patterns.

Exception: standalone Nextkt ticketing (consumer storefront + operator admin) may retain its logo-derived teal/navy palette until an explicit migration — it is not a Kisum Core module app. See Frontend Nextkt.

Promoters product voice (persona, anti-references): Frontend-Kisum-Promoters/PRODUCT.md — complements but does not override platform tokens.

Creative North Star: “The Booking Brief”

Kisum reads like a corporate-modern research desk with an editorial twist, built for live-music industry decisions. Surfaces are calm, information-dense, and typographically precise: operators scan signals, compare metrics, and move to action without wading through decorative UI. The system serves workflow, not spectacle.

This is explicitly not generic SaaS cream UI: no warm-tinted near-white wallpaper, no endless gray card stacks, no purple CTAs on every row. Kisum purple is an accent for emphasis and brand continuity, not the dominant surface treatment. Detail tabs, list views, admin tables, and workflow surfaces across all Kisum apps share one visual language so users do not relearn layout per module.

Key Characteristics:

  • Analyst-grade density with scannable hierarchy (section headers, rank numerals, metric blocks)
  • Refined management-dashboard polish: generous whitespace, disciplined grids, and listings treated as decision-quality content cards
  • Flat-by-default surfaces; depth from borders, tint, and spacing rather than heavy shadow stacks
  • Inter for UI body copy; Manrope for semibold section titles and display-weight emphasis
  • Kisum purple (#8466AC) reserved for ranks, badges, primary actions, and focus moments
  • ShadCN/Radix primitives with Tailwind 4 tokens; extend patterns, do not fork aesthetics per screen

A restrained product palette: cool neutrals carry the shell, white cards hold content, Kisum purple marks decision points.

  • Kisum Purple (#8466AC / kisum-600): Primary brand accent. Section count badges, rank numerals, kisum button fills, top-loader accent (#9A7AFF). Use for emphasis, not backgrounds.
  • Kisum Accent (#9A7AFF / kisum-500): Lighter highlight for theme-color, hover states, and chart emphasis. Pair with purple-600 for legibility on white.
  • Kisum Tint (#F3EEF8): Soft purple wash for pills and chips (e.g. Overview section count badges). Never as full-page background.
  • Material Accent Tokens (md-* in token YAML above): Imported low-level tonal tokens kept for compatibility/reference only. Do not let them override the canonical Kisum semantic tokens (kisum-primary, surface, body-bg, ink).
  • Ink (#18181B / zinc-950): Primary text on light surfaces. Body and titles on cards.
  • Muted Ink (#71717A / zinc-500): Secondary labels, subtitles, metadata. Must still meet 4.5:1 on white; bump toward ink if contrast fails on tinted surfaces.
  • Surface (#FFFFFF): Card and row backgrounds on detail sections, list rows, and admin panels.
  • Shell (#F4F4F5 / zinc-100): App chrome at lg+ (lg:bg-zinc-100 on <html>).
  • Body Background (#F9FAFB / gray-50): Page canvas inside the shell — see §6 App shell (mandatory); never replace with white or cream.
  • Sidebar Background (lab(98.26% 0 0) / --sidebar): Cool near-white sidebar panel via bg-sidebar (token sidebar-bg).
  • Main Background (#FFFFFF / --main): White main content panel via bg-main or bg-background on MainColumn / <main> (token main-bg).
  • Border Subtle (#F3F4F6 / gray-100): Row and tile borders at rest (border-gray-100).
  • Neutral Slate (#5E5E62): Legacy management-surface text reference; prefer Ink for primary text and use this only where existing components already depend on it.
  • Danger (#D34848): Destructive actions and error emphasis. Hover #B53030.

The Purple Budget Rule. Kisum purple appears on ≤10% of any given screen. If purple is everywhere, hierarchy collapses and the UI reads as generic SaaS.

The No-Cream Rule. Do not tint the page background warm (cream, sand, parchment). Neutrals stay cool (zinc/gray). Warmth comes from artist imagery and data, not body bg.

Display Font: Manrope (local, --font-manrope) with ui-sans-serif fallback
Body Font: Inter (Google, display: swap) with system-ui fallback
Label Font: Inter (same stack as body)

Character: Inter keeps dense tables and metadata readable; Manrope adds confident weight to section titles without switching to a display serif. Pairing is utilitarian-analyst, not editorial-magazine.

  • Display (600, text-lg / 1.125rem, tight tracking): Overview section headers (OverviewSectionHeader), tab-level titles. text-wrap: balance on multi-line headings.
  • Headline (600, text-smtext-base): Row titles, artist names in lists, KPI values.
  • Title (500–600, text-sm): Card titles, semibold metadata labels.
  • Body (400, text-sm / 0.875rem, line-height 1.5): Descriptions, bio preview. Cap prose at ~65–75ch (max-w-3xl on biography blocks).
  • Label (600, text-[11px]text-xs): Count badges, platform pills, freshness badges. Sentence case preferred; avoid all-caps body copy.
  • Data fields (400–600, text-sm, tabular where numeric): Use Inter for scan accuracy across rankings, listings, metrics, and management rows.

The Density Rule. Prefer text-sm body on analyst surfaces. Step up to text-base only for hero artist name or primary KPI, not every paragraph.

Mostly flat surfaces. Depth is conveyed through tonal layering (shell → white card → bordered row) and 1px borders, not stacked shadows. Shadows appear as a response to interaction (hover:shadow-sm, hover:-translate-y-0.5 on TrackRow), not at rest on every container.

ShadCN Card uses shadow-sm at rest; on dense list/overview surfaces prefer the flatter row pattern (border-only at rest, shadow on hover). Do not nest card-in-card-in-card stacks.

  • Hover lift (box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05)): Track rows and interactive tiles on hover only.
  • Focus ring (ring-[3px] ring-ring/50): Buttons and inputs via ShadCN focus-visible treatment.
  • Modal / Popover depth: Standard dialog shadow with backdrop blur only where focus isolation is needed for a management task.

The Flat-By-Default Rule. Surfaces are flat at rest. Shadows signal interactivity, not decoration.

  • Shape: rounded-md (6px) for default ShadCN variants; rounded-full for branded kisum / kisum_outline CTAs.
  • Primary (kisum): bg-kisum-600 (#8466AC), white text, shadow-xs, hover bg-kisum-600/90.
  • Outline (kisum_outline): White background, border-kisum-600, purple text, hover bg-kisum-600/10.
  • Icon buttons: Prefer icon-only or icon-leading management actions with subtle hover backgrounds, especially edit/delete/overflow actions.
  • Hover / Focus: transition-all; focus-visible ring 3px at 50% ring color. Destructive uses kisum_destructive full-round variant.
  • Overview badge: rounded-full bg-[#F3EEF8] px-2 py-0.5 text-[11px] font-semibold text-[#8466AC].
  • Genre / status pills: Small rounded pills on biography; positive/negative accent chips for boolean states.
  • Corner Style: rounded-xl (12px) for overview rows and ShadCN cards.
  • Background: White (bg-white) on gray shell; avoid bg-gray-100 cards unless grouping personnel under agencies.
  • Border: border-gray-100 at rest, border-gray-200 on hover.
  • Internal Padding: p-2.5 compact rows; p-4p-6 for section containers.
  • Style: ShadCN input with border-input, rounded-md, background bg-background.
  • Filter bars: Treat filters as one cohesive control group. Use segmented controls for binary/mode toggles and Select dropdowns with refined chevrons for option sets.
  • Filtering is instant-apply, in the toolbar (canonical). Every facet is a toolbar dropdown that writes straight to the live filter state and refetches; applied filters are shown as removable chips below the toolbar. Do not add a filter drawer/sheet with tempFilter + an Apply button — that pattern is retired. Drawers/sheets remain correct for forms (create/edit/detail), not for filtering. As of 2026-07-22 Promoters has no filter drawer left — Venues (Country / Type / Capacity) and City rankings (City / Region) were the last two and are now dropdowns.
  • Range facets (capacity, fee, size) are preset bands, not sliders. A range facet is a dropdown of selectable bands; each band maps to the exact min / max pair the query already accepts. Never invent a band the API cannot express, and never stage a slider behind an Apply button.
  • Labels: Prefer labels above inputs or compact label-sm treatment; avoid floating labels unless already present in the component family.
  • Focus: Ring-based focus-visible per ShadCN defaults.
  • Error: aria-invalid ring destructive/20.
  • App sidebar structure: Mandatory — Promoters, Venues, and Artists MUST use the canonical app-shell tree (see §6 App shell); only SideNavBody menu may differ.
  • Detail tabs: Horizontal tab strip on entity detail pages; active state uses brand emphasis without full purple fill of the tab bar.

6. App shell (mandatory — all Kisum frontends)

Section titled “6. App shell (mandatory — all Kisum frontends)”

Every Kisum product frontend MUST use this shell layering. Do not fork per app, module, or persona. Nextkt is the only documented exception.

The root layout <body> MUST always use exactly these classes (no substitutions, no omissions):

className="min-h-screen bg-gray-50 antialiased dark:bg-gray-900"

This is the page canvas. It must stay cool gray — not white, not cream, not zinc-100 alone.

Reference: Frontend-Kisum-Promoters/src/app/layout.tsx.

The ShadCN Sidebar desktop wrapper (data-slot="sidebar") MUST always include this exact base class string:

'group peer hidden text-sidebar-foreground xl:block'

State, variant, and collapsible classes may be appended via cn(); do not replace or drop the mandatory base. Required on all Kisum frontends that use the shared sidebar pattern.

Reference: Frontend-Kisum-Promoters/src/components/ui/app-shell.tsx (SideNav desktop block).

Kisum app-shell primitive naming (Promoters canonical)

Section titled “Kisum app-shell primitive naming (Promoters canonical)”

Promoters renames the forked shadCN sidebar block for readability. Other Kisum frontends should adopt the same export names when they sync primitives from Promoters.

shadCN nameKisum nameRole
SidebarProviderAppShellProviderShell state (open/collapse/mobile)
useSidebaruseAppShellHook for shell state
SidebarSideNavLeft navigation panel
SidebarContentSideNavBodyScrollable nav items
SidebarHeader / SidebarFooterSideNavHeader / SideNavFooterTop/bottom of side nav
SidebarInsetMainColumnMain work column (<main>)
SidebarTriggerSideNavToggleOpen/collapse control

File: Frontend-Kisum-Promoters/src/components/ui/app-shell.tsx. Application composition: Frontend-Kisum-Promoters/src/components/app-shell/.

In-scope persona frontends (mandatory): Promoters (canonical), Venues, Artists. Copy this tree per repo — no shared package. Public Form (Fullstack-Kisum-Public /form/*): same app-shell primitives and §6 mechanical rules; documented substitutions — Register header row + Log in footer instead of company switcher + NavUser; empty SideNavBody. Out of scope: Admin, Checkout, Website, legacy base, Nextkt. Finance (2026-07-13, full redesign pass): visual alignment complete on existing Sidebar + MainLayoutClient + vendor layout — transparent desktop sidebar on the gray body canvas, white bg-main panel, zero gradient CTAs/text, flat-at-rest surfaces, shared Button kisum variants, Manrope h1–h3, charcoal dark tokens (see Frontend Finance — Design system alignment); full AppShellProvider + @main tree still optional future work.

Top header (mandatory — Promoters canonical, 2026-07-21)

Section titled “Top header (mandatory — Promoters canonical, 2026-07-21)”

The shell header (components/app-shell/main-chrome/page-header.tsx) is a single flex row:

height: var(--head-h) /* 68px */; flex-shrink: 0; display: flex; align-items: center;
justify-content: space-between; gap: 20px; padding: 0 var(--pg-pad);
border-bottom: 1px solid var(--border); background: var(--surface-shell)

Contents, left → right: breadcrumbs · search · theme · notifications · section CTA.

  • Breadcrumbs: font-size: 11px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase, separator / in --border-hover (.k-crumb-sep), last crumb --text-strong, earlier crumbs --muted and linked (.k-crumb-link). Overflow truncates with an ellipsis — it does not scroll. The sidebar toggle sits to their left on mobile / collapsed desktop.
  • Search: height: 40px; padding: 0 12px; background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--radius-btn); flex: 1 1 auto; min-width: 120px; max-width: 420px — a 16px lucide search in --muted, the placeholder in 13px --muted, and a right-aligned ⌘K chip (11px/700 --muted, 1px solid var(--border), radius 4px, padding 1px 5px). Opens a CommandDialog; ⌘K / Ctrl+K works anywhere in the app.
  • Theme toggle and notification bell: both 40×40 .k-ib buttons (--surface-card on a --border, --radius-btn, --text-body). Theme shows lucide moon in light and sun in dark. The bell’s unread state is a 7px --accent dot at top: 8px; right: 9px with a 1.5px --surface-card ring.
  • Primary CTA: height: 40px; padding: 0 16px; background: var(--accent); color: var(--text-on-accent); border-radius: var(--radius-btn); font-size: 13px; font-weight: 700; box-shadow: var(--shadow-accent) with a leading 16px lucide icon (plus by default).

Narrow viewports (mandatory). Five controls do not fit at 375px, so the header degrades rather than overflowing: page padding drops to 16px below 768px; the search field collapses to a 40px icon button below 1024px (losing its label and ⌘K chip); the CTA drops its label to a 40px accent icon button below 640px; and the breadcrumb trail shows only the current crumb below 768px. These rules live in .k-appbar* / .k-crumb-item in kisum-ds.css — the header must not be styled inline, or the media queries cannot win.

The CTA is per-route and page-owned. Pages publish their primary action to the shell through a small client registry (src/stores/use-header-action.tsuseHeaderAction({ label, onClick | href, icon?, disabled? })); the header renders whatever is registered and renders no button at all when a route registers nothing. A page must publish the handler it already owns (drawer / dialog / sheet / link) rather than gain new behaviour, and must not keep a duplicate copy of the same button in its own PageHeader action slot. Never register a label with no real action behind it.

The search palette must query real endpoints. In Promoters it is wired to /api/artists/search and /api/venues/search (the only entity search endpoints the promoter BFF exposes), grouped as Artists and Venues, debounced ~300ms, with server-side results (shouldFilter={false} on the cmdk root) that navigate to the real detail route. A third Go to group lists in-app destinations derived from the sidebar menu. Do not add a result group for an entity with no search endpoint.

Dark mode is a pure token swap, never a per-component dark: sweep. .dark on <html> is the only switch; src/styles/kisum-ds.css re-points the raw Kisum tokens and the Tailwind --color-* tokens the shadCN components read, so both styling systems follow one class.

  • State lives in src/stores/use-theme.ts (useTheme() / applyTheme()), persisted to localStorage under kisum-theme, default light.
  • A tiny inline script in the root layout applies the stored class before first paint; <html> carries suppressHydrationWarning because the server and client class lists intentionally differ.
  • Tailwind’s own dark: variant is enabled in Promoters since 2026-07-22 (@custom-variant dark (&:where(.dark, .dark *))), bound to the same .dark class. The token layer stays the source of truth. An element that already uses a design token (bg-surface, bg-shell, text-foreground, text-muted-foreground, border-border, var(--surface-card), …) is already theme-correct and MUST NOT also carry a dark: class — that is how double-darkened surfaces and invisible text appear. Use dark: only for a fixed colour that has no token (Tailwind palette values such as bg-zinc-950/10, bg-emerald-100, bg-amber-50, border-white/10, fixed gradients, and native <option> colours).
  • Consequence for the brand scale: --kisum-400/500/900/950 have no dark override, so dark:text-kisum-400, dark:text-kisum-200/300 and dark:bg-kisum-950/* render wrong or invisible on dark surfaces. Never hand-pick a brand step behind dark: — use text-kisum-600 / bg-kisum-tint, which follow the theme.

Stylesheet split (Promoters — share kisum-ds.css)

Section titled “Stylesheet split (Promoters — share kisum-ds.css)”
FileRole
src/styles/kisum-ds.cssCanonical Kisum tokens (--surface-*, --text-*, --kisum-*), .dark overrides, shadcn bridge vars, .k-* helpers, scrollbars, app-bar/crumb rules, and shared page patterns (.page-container, .stat-card, .data-table, .badge-*, .btn-*, shell data-* hooks). Copy or import this file in other Kisum persona frontends before app Tailwind.
src/styles/tailwind.cssTailwind v4 + shadcn @theme inline wiring only. Must load after kisum-ds.css (layout.tsx import order).
src/styles/globals.cssLegacy widgets (calendar-era buttons, TipTap, masonry) — not the color source of truth.

Every in-scope persona frontend MUST use this exact layout. Domain views (calendar, dashboard, feature pages) live outside components/app-shell/.

src/
components/
ui/
app-shell.tsx # Kisum primitives (AppShellProvider, SideNav, MainColumn, …)
app-shell/
index.ts # export AppShellLayout
app-shell-layout.tsx # client shell composer
footer.tsx
side-nav/
app-sidebar.tsx
nav-main.tsx
nav-user.tsx
companies-switcher.tsx
sidebar-package-plan.tsx
create-company-modal.tsx
create-company-form.tsx
subscribe-package-modal.tsx
modal-tour-guest.tsx
combobox-add/ # 3 combobox forms
skeleton/ # skeleton-menu, -page, -switcher, -user, -event
main-chrome/
page-header.tsx
custom-breadcrumb.tsx
notification-dropdown.tsx
overlays/
company-select-dialog.tsx
subscribe-company-overlay.tsx
middle-agent/ # addon only — Promoters, Artists, Venues
middle-agent-app-shell-layout.tsx
side-nav/middle-agent-sidebar.tsx
main-chrome/page-header.tsx
app/
layout.tsx # main parallel slot + RootSlotSwitcher
_components/RootSlotSwitcher.tsx
default.tsx
@main/
layout.tsx # thin server → AppShellLayout
default.tsx
…routes…
(middle-agent)/ # addon route group (not parallel slot)
layout.tsx
…routes…

Promoters-only (do not copy to other modules): side-nav/nav-events.tsx, app/public/layout.tsx + public sidebar subtree.

  • Root: app/layout.tsx accepts children (auth, (middle-agent)/*, /intake/*) and main (@main slot). RootSlotSwitcher renders main when authenticated except on /middle-agent/* and /intake/*, which must use the children slot. @main/default.tsx and root default.tsx return null (never redirect) so parallel-slot fallbacks do not fight (middle-agent) URLs. Venues/Artists have no /public branch; Promoters also switches to children for /public/*.
  • Persona app: app/@main/layout.tsx (server) reads cookies/bootstrap → wraps AppShellLayout (client) → {children} are route pages.
  • Middle-agent addon: app/(middle-agent)/layout.tsxMiddleAgentAppShellLayout from components/app-shell/middle-agent/. URLs under /middle-agent/*. Finance has no middle-agent shell.
  • Middle-agent sidebar: Same shell contract as main (SideNav collapsible="icon", SideNavToggle, SideNavRail, SideNavGroup + SideNavMenuButton nav, company switcher, footer back-link + NavUser). Violet accent classes only — structure matches main.
  • Main ↔ middle-agent transition: ShellTransitionLink sets sessionStorage direction; ShellViewTransition wraps both AppShellLayout and MiddleAgentAppShellLayout for a 300ms fade/slide on cross-shell navigation.
flowchart TB
  root[app/layout.tsx]
  rsw[RootSlotSwitcher]
  auth[children slot signin]
  mainSlot["@main/layout.tsx"]
  asl[AppShellLayout]
  sideNav[side-nav/AppSidebar]
  mainCol[MainColumn]
  ph[main-chrome/PageHeader]
  content[route children]
  root --> rsw
  rsw --> auth
  rsw --> mainSlot
  mainSlot --> asl
  asl --> sideNav
  asl --> mainCol
  mainCol --> ph
  mainCol --> content
ItemPromotersVenuesArtistsFinanceAdmin / Checkout / Website / legacy
Canonical app-shell treecanonicalmandatorymandatoryvisual alignment complete (full redesign 2026-07-13); structural tree TODOout of scope
nav-events.tsxyesnononono
app/public/ shellyesnononono
Middle-agent shellwhen addonwhen addonwhen addonnono
Finance vendor portaln/an/an/afully aligned (same tokens; no gradients, kisum Button)n/a
  1. Promoters — canonical (done).
  2. Venues — copy tree + @main + Kisum primitive names; relocate domain files out of app-shell/.
  3. Artists — same + normalize middle-agent/ under app-shell/middle-agent/.
  4. Finance — full visual redesign done (2026-07-13): transparent desktop sidebar, white bg-main panel, gradient-free components, kisum Button variants, Manrope headings, charcoal dark tokens; optional later: port full Promoters AppShellProvider + @main tree.
  5. New persona apps — MUST adopt this tree from day one; no ui/sidebar.tsx or layouts/sidebar/.

Plan reference: workspace docs/superpowers/plans/2026-06-27-kisum-app-shell-normalization.md.

LayerUtility / tokenValueUse
Page canvasbg-gray-50 on <body>#F9FAFBOutermost shell behind side nav + main
Side navbg-sidebarvar(--sidebar)lab(98.26% 0 0)Side nav inner surfaces (data-sidebar="sidebar")
Main contentbg-main or bg-background on MainColumn / <main>#FFFFFFPrimary work area — always white
Cards / boxes / rowsbg-white / bg-surface + border-gray-100#FFFFFFContent objects on the white main panel

Wire --main: #FFFFFF (or alias --main to --background) and expose bg-main in Tailwind @theme where not already present. The token YAML maps sidebar-bg--sidebar and main-bg--main. Cards and panels on the main area use white/surface — not bg-gray-50 on top of white main unless an existing grouped-subsection pattern already requires it.

App sidebar structure (mandatory — all Kisum frontends)

Section titled “App sidebar structure (mandatory — all Kisum frontends)”

Every Kisum persona frontend in scope (Promoters, Venues, Artists) MUST use the same app-sidebar shell. Admin, Checkout, Website, legacy base, and Nextkt are out of scope for this contract. Finance migration is Phase 2.

Only SideNavBody navigation items may differ per module (menu keys, optional secondary blocks such as events). Header, collapse behavior, company switcher row, footer user block, guest upsell slot, spacing, icons, and colors MUST match the canonical Promoters implementation.

Canonical reference (do not reinvent):

Render inside <SideNav collapsible="icon" className="z-40"> in exactly this order:

  1. SideNavHeader — logo row + company switcher row
  2. SideNavBody — module menu only (variable)
  3. Guest upsell slot (conditional — see below)
  4. SideNavFooterNavUser
  5. SideNavRail — last child; edge collapse toggle
<SideNav collapsible="icon" className="z-40">
{/* …header, body, guest?, footer… */}
<SideNavRail />
</SideNav>
  • collapsible="icon" is mandatory — desktop collapses to an icon rail, not offcanvas-only hide.
  • className="z-40" on the side nav root (stacking above main chrome where needed).
  • SideNavRail MUST be present as the final child.

Logo row (mandatory — inside SideNavHeader)

Section titled “Logo row (mandatory — inside SideNavHeader)”

Wrapper classes (exact):

className="mb-6 flex w-full items-center pt-2.5 group-data-[collapsible=icon]:flex-col-reverse group-data-[collapsible=icon]:pt-2 group-data-[collapsible=icon]:pl-0"
AssetExpandedIcon-collapsed (group-data-[collapsible=icon])
Full wordmark/logo.svg, className="flex h-auto w-32 group-data-[collapsible=icon]:hidden"hidden
Icon mark/logo-icon.svg, className="hidden h-auto w-6 group-data-[collapsible=icon]:flex"visible (w-6)

Both use alt="KISUM". Do not substitute different logos or sizes per module.

SideNavToggle (mandatory — expanded header only)

Section titled “SideNavToggle (mandatory — expanded header only)”
<SideNavToggle className="ml-auto group-data-[collapsible=icon]:hidden" />
  • ShadCN ghost icon button with PanelLeftIcon (from app-shell.tsx primitive).
  • ml-auto in the logo row when expanded.
  • Hidden when icon-collapsed — expand/collapse in icon mode via SideNavRail (and SideNavToggle in PageHeader on mobile).
  • Action: toggleSideNav() (provided by primitive).
<SideNavRail />

Thin edge hit area on the side nav border for collapse/expand when the header trigger is hidden. Do not omit or replace with a custom control.

Company switcher row (mandatory — below logo row, inside SideNavHeader)

Section titled “Company switcher row (mandatory — below logo row, inside SideNavHeader)”

Same visual treatment in every module; only eligibility/filter logic may differ internally.

StateComponentKey classes / behavior
LoadingSkeleton placeholderMatch Promoters SkeletonSwitcher pattern
No active companyNoCompanySelectedPlaceholderDashed border, Building2 icon, muted “No company selected” copy; icon-only when collapsed
Active companyCompaniesSwitcherRow: bg-gray-200/80 p-2 rounded-md; size-10 square avatar; bold name + subtitle; dropdown with ChevronsUpDown

Icon-collapsed: compact avatar (group-data-[collapsible=icon]:p-0 on row; labels group-data-[collapsible=icon]:hidden).

SideNavBody (module-specific — menu only)

Section titled “SideNavBody (module-specific — menu only)”

Navigation items, grouping, and optional module-only blocks (e.g. Promoters upcoming events in nav-events.tsx) live here. Do not move header, footer, guest upsell, or logo into the body.

Guest upsell slot (mandatory pattern when guest role applies)

Section titled “Guest upsell slot (mandatory pattern when guest role applies)”

Between SideNavBody and SideNavFooter, when the user has guest access and profile is loaded:

Expanded (icon rail hidden content):

  • Wrapper: className="group-data-[collapsible=icon]:hidden"
  • SidebarPackagePlan card: rounded-none border-x-0 py-4 shadow-none; title “You’re currently using free plan”; full-width variant="kisum" button, rounded-full py-5, label Subscribe Plan

Icon-collapsed:

  • Hide the card wrapper above
  • Show BellRingIcon button: className="m-2.5 hidden size-8 rounded-md p-0 text-kisum group-data-[collapsible=icon]:flex hover:bg-kisum-100/75", variant="secondary"
  • Tooltip: content “Subscribe to a plan”, side="right", align="center"
  • Same onClick as the expanded subscribe CTA (opens subscription flow)

Omit the entire slot when not guest or while profile is loading.

<SideNavFooter id="sidebar-footer-user" data-onborda="sidebar-footer-user">
{/* SkeletonUser while loading; NavUser when ready */}
</SideNavFooter>

Footer trigger row (SideNavMenuButton size="lg"):

  • Container: min-h-16, hover:bg-kisum-50
  • Avatar: size-12 rounded-full border border-gray-200size-8 when icon-collapsed
  • Company pill (when company selected): rounded-md border border-gray-200 px-2 py-1 text-xs font-medium text-kisum-600
  • User name: font-bold capitalize, truncated
  • Chevron: ChevronsUpDownIcon with ml-auto size-4

Dropdown (desktop: side="right", align="end", min-w-80): user summary, switch company, profile, companies, plan & billing, get started, sign out — mirror Promoters nav-user.tsx structure and icon colors (text-kisum-600 for nav icons, text-red-500 for sign out).

Section titled “Sidebar icons, spacing, and colors (mandatory)”
  • Surface: inner sidebar bg-sidebar, text text-sidebar-foreground — not main-panel white.
  • Spacing: header/footer primitives use ShadCN p-2; logo row mb-6 pt-2.5 (collapsed: pt-2 pl-0).
  • Purple accent (sidebar): company pills, guest CTA, footer hover hover:bg-kisum-50, guest bell text-kisum / hover:bg-kisum-100/75, dropdown icons text-kisum-600 — respect Purple Budget Rule.
  • Neutrals: company row bg-gray-200/80, borders border-gray-200, secondary copy text-muted-foreground.
  • Collapse visibility: use group-data-[collapsible=icon]:* selectors on children — do not invent alternate collapse widths, breakpoints, or offcanvas-only sidebars for Kisum module apps.

Overview / dense list section (signature pattern)

Section titled “Overview / dense list section (signature pattern)”

Reference implementations today live in Promoters (OverviewSectionHeader, TrackRow); other apps should match this pattern or extract shared components — do not invent alternate row/card aesthetics.

  • Section header: text-lg font-semibold tracking-tight text-gray-900, optional 20px source icon, purple count badge, right-aligned action (ml-auto).
  • Dense row: Rank or index in purple tabular nums, 44px thumb when media applies, truncated title/subtitle, metric block right-aligned; hover lift -translate-y-0.5 + shadow-sm.
  • Editorial Insight Card: High-quality artist or venue imagery may anchor the card, but the card must remain a management object: clear metadata, status, owner/action context, and a predictable action footer.
  • Status badges: Float over imagery only when contrast is guaranteed; otherwise place in a dedicated header row.
  • Action footer: Keep primary view/action and edit/unpublish/manage controls aligned and consistently padded.

The layout follows a fixed-grid management philosophy for readability and comparison. Keep app content centered in the 1280px container token, use a 12-column grid for dense listing/filter surfaces, and collapse cleanly to 2 columns on tablet and 1 column on mobile with 16px margins.

Page main section (mandatory — Promoters, Artists, Venues)

Section titled “Page main section (mandatory — Promoters, Artists, Venues)”

Every @main page (below the sticky app header / breadcrumb bar, above page content) MUST wrap its primary content in the canonical page container. Do not add extra horizontal padding on the app-shell {children} wrapper — padding lives on this container only.

Container (mandatory class string):

mx-auto flex w-full max-w-7xl flex-1 flex-col gap-12 p-6 sm:p-8 lg:p-10

Prefer the shared helper in each persona app:

  • PageMainSection / PAGE_MAIN_SECTION_CLASS
  • PageSectionHeader with PAGE_SECTION_TITLE_CLASS + PAGE_SECTION_DESCRIPTION_CLASS
  • Path: src/components/app-shell/main-chrome/page-main-section.tsx (Promoters canonical)

Page section title (primary h1 at top of main content):

className="text-4xl font-bold"

Page section description (subtitle directly under that h1):

className="mt-2 text-base leading-relaxed text-slate-500"

Do not use text-2xl font-semibold, text-muted-foreground, or ad-hoc max-w-[1160px] / max-w-6xl / max-w-5xl wrappers for standard list and workflow pages. Detail sub-heroes, cards, modals, and tab bodies are excluded.

  • Desktop: Listing cards may span 4 columns (3 per row) or 3 columns (4 per row) depending on information density.
  • Tablet: 2-column grid unless data comparison requires a table or horizontal scroll pattern already used elsewhere.
  • Mobile: 1-column grid, no horizontal overflow.
  • Vertical rhythm: stack-sm (8px) for label/control pairs, stack-md (16px) for card internals, stack-lg (32px) for major section breaks.

Right-anchored form drawers (Create task, entity editors) follow one shape:

  • Panel: fixed width (Create task uses 460px) capped at 94vw, background: var(--surface-card), border-left: 1px solid var(--border), box-shadow: var(--shadow-modal), flex column, full height.
  • Backdrop: rgba(24,24,27,.45) with backdrop-filter: blur(3px).
  • Rhythm: header and body 24px 28px, footer 18px 28px, both separated by 1px solid var(--border-subtle). Body sections sit 20px apart; label/control rows sit 14px apart.
  • Label/control rows: display: grid; grid-template-columns: 96px 1fr; gap: 14px. Labels are 12.5px / 700 / var(--text-body). Controls are 42px tall, 0 12px padding, var(--surface-page) on 1px solid var(--border) at var(--radius-btn), with a trailing chevron-down at var(--muted) / opacity: .6.
  • Accent-tinted surfaces (the title panel, a secondary Upload button) use border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent) — not the plain --border token.
  • Drop zones (attachments) use a dashed 1px var(--border) border at var(--radius-card).
  • Write these with inline style using the CSS variables. Design tokens expressed as arbitrary Tailwind values lose to shadcn base classes through tailwind-merge; inline styles always win.

Nested layers inside a drawer. While a Radix DropdownMenu / Popover / Select is open it becomes the top-most dismissable layer and sets pointer-events: none on the drawer content, while the drawer’s overlay stays interactive. A click that visually lands inside the drawer is therefore delivered to the overlay and read as a backdrop click, which closed the drawer and discarded the form. SheetContent must guard onPointerDownOutside / onInteractOutside / onFocusOutside and dismiss only when no nested popper is mounted and the interaction really hit the overlay. Escape needs no handling — Radix gives it to the top-most layer.

  • Do set <body className="min-h-screen bg-gray-50 antialiased dark:bg-gray-900"> on every Kisum frontend root layout.
  • Do keep the sidebar desktop root at group peer hidden text-sidebar-foreground xl:block and main content white (bg-main / bg-background).
  • Do implement AppSidebar with collapsible="icon", logo/logo-icon swap, SideNavToggle, SideNavRail, company switcher row, NavUser footer, and guest upsell slot exactly as in Promoters — mandatory on Promoters, Venues, Artists.
  • Do wrap every @main page body in PageMainSection (or PAGE_MAIN_SECTION_CLASS) with text-4xl font-bold page titles and mt-2 text-base leading-relaxed text-slate-500 subtitles — mandatory on Promoters, Artists, Venues.
  • Do keep Kisum purple for ranks, badges, and primary CTAs only (The Purple Budget Rule).
  • Do verify muted text contrast on tinted surfaces; bump text-muted-foreground toward ink when close to 4.5:1.
  • Do respect prefers-reduced-motion: replace hover translate lifts with border/color shifts only.
  • Do pair platform brand colors in charts with labels and numeric context, not color alone.
  • Do style drawer panels, rows, and controls with inline style + CSS variables, and guard SheetContent against nested-popper dismissals (see 7b).
  • Don’t change the mandatory <body> or sidebar desktop root classes per app or route.
  • Don’t fork side nav header, footer, collapse, logo swap, company switcher layout, or guest upsell pattern per module — only menu items in SideNavBody may differ.
  • Don’t omit SideNavRail, use collapsible="offcanvas" instead of collapsible="icon", or hide the logo-icon swap on collapse.
  • Don’t set the main content area or default page panel to bg-gray-50 — main is white; gray-50 is body-only.
  • Don’t ship generic SaaS cream UI: warm-tinted near-white backgrounds, muted gray card stacks, purple CTAs everywhere, identical icon-heading-text grids.
  • Don’t clone consumer social profiles (Instagram/Spotify artist page layout with engagement-first hierarchy).
  • Don’t add dashboard chaos: competing chart widgets with no clear primary signal on one screen.
  • Don’t use border-left or border-right greater than 1px as a colored accent stripe on cards or list items.
  • Don’t use gradient text (background-clip: text) for headings or metrics.
  • Don’t nest identical card grids (card inside card inside card).
  • Don’t add shell-level px-4 py-6 padding around {children} when pages already use PageMainSection — avoids double padding.
  • Don’t let an outside-interaction on a drawer close it while a nested dropdown, popover, calendar, or select is open — that click is only dismissing the popper.

  1. Edit this page (System-Kisum-Docs/src/content/docs/frontend/3-0-kisum-design-system.md) — tokens YAML block + sections 0–8.
  2. Update .cursor/rules/00-design.mdc if agent-facing constraints change.
  3. Optionally sync local DESIGN.md for Impeccable/design-tool sidecars (local only; not required on GitHub).
  4. Do not maintain duplicate full copies under individual frontend repos — use a pointer file (see Frontend-Kisum-Promoters/DESIGN.md).