Design System
title: Design System owner: vizu last-updated: 2026-05-16 supersedes: docs/frontend/DESIGN-SYSTEM.md (2026-02-25 — teal palette, retired) status: canonical
Bilko Design System
RETIRED: The previous DESIGN-SYSTEM.md documented a teal (#00E5A0) palette. That document is wrong and archived. The canonical brand is plum (#8B6BBF). See
docs/branding/branding/bilko-brand-guidelines.mdfor the authoritative brand source.
1. Color Palette — Canonical Plum
Source of truth: bilko-brand-guidelines.md (2026-05-16). Tokens below are the Tailwind 4 @theme mapping.
CSS Custom Properties (@theme block in globals.css)
@theme {
/* Primary plum scale */
--color-primary: #8b6bbf; /* Plum Primary — CTAs, active states, links */
--color-primary-hover: #5b3e8a; /* Deep Plum — hover on primary elements */
--color-primary-light: #c4a8e0; /* Light Plum — subtle backgrounds, chips */
--color-primary-subtle: #ede5f8; /* Plum tint — focus rings, hover surfaces */
/* Accent */
--color-accent: #f2c87a; /* Gold — highlights, accent dots, icons */
--color-accent-hover: #e8ae4a; /* Gold dark — hover on accent elements */
/* Surface / Background */
--color-surface: #f9f7fc; /* Light Lavender — preferred background */
--color-surface-dark: #14111f; /* Dark mode background */
--color-bg: #ffffff; /* Pure white — card backgrounds, modals */
/* Text */
--color-text-primary: #231c33; /* Dark plum-toned text — on light backgrounds */
--color-text-secondary: #6b7280; /* Mid gray — labels, secondary copy */
--color-text-light: #e4def0; /* On dark backgrounds */
--color-text-muted: #9ca3af; /* Muted, disabled states */
/* Semantic */
--color-success: #22c55e;
--color-warning: #f59e0b;
--color-error: #ef4444;
--color-info: #3b82f6;
/* Border */
--color-border: #e5e1ef; /* Plum-tinted border */
--color-border-strong: #c4a8e0; /* Stronger border for focused elements */
/* Sidebar */
--color-sidebar-bg: #14111f; /* Dark plum sidebar */
--color-sidebar-text: #e4def0;
--color-sidebar-active: #8b6bbf;
/* Chart colors */
--color-chart-1: #8b6bbf;
--color-chart-2: #f2c87a;
--color-chart-3: #5b3e8a;
--color-chart-4: #c4a8e0;
--color-chart-5: #ede5f8;
}
Color Rules (from brand guidelines)
- Never use
--color-primary(#8B6BBF) as body text on white — WCAG contrast fails at small sizes (3.6:1). - Gold Accent (#F2C87A) is for highlights only — never as a primary CTA color.
- Surface (#F9F7FC) is preferred over pure white for page backgrounds.
- Focus rings must use
--color-primary-hover(#5B3E8A) at minimum 3:1 contrast against adjacent color (WCAG 2.4.11).
Retired Colors
The following colors appeared in the old DESIGN-SYSTEM.md and are explicitly retired. Do not use:
| Retired hex | Label in old doc | Reason |
|---|---|---|
#00E5A0 |
Primary teal-green | Wrong brand |
#00B380 |
Primary dark teal | Wrong brand |
#33EBB3 |
Primary light teal | Wrong brand |
#111113 |
Sidebar dark | Replaced by #14111F |
2. Typography
Fonts: National Park (headings) · Work Sans (body, UI) · DM Mono (numbers, data, code)
Type Scale
@theme {
--font-heading: 'National Park', system-ui, sans-serif;
--font-body: 'Work Sans', system-ui, sans-serif;
--font-mono: 'DM Mono', 'Courier New', monospace;
--text-xs: 0.75rem; /* 12px — fine print */
--text-sm: 0.875rem; /* 14px — labels, secondary */
--text-base: 1rem; /* 16px — body copy */
--text-lg: 1.125rem; /* 18px — large body, lead */
--text-xl: 1.25rem; /* 20px — small headings */
--text-2xl: 1.5rem; /* 24px — H3 */
--text-3xl: 2rem; /* 32px — H2 */
--text-4xl: 3rem; /* 48px — H1 landing */
}
Usage Rules
- Headings: National Park Bold. Use for
h1–h3and the logo wordmark. - Body: Work Sans Regular/SemiBold. All UI copy, labels, table cells.
- Mono: DM Mono Regular. Invoice numbers, amounts, account codes, code blocks.
- Minimum body size: 16px (1rem). Never below 12px (WCAG 1.4.4).
3. Spacing System
Base unit: 8px. All spacing values are multiples of 8px. Never use arbitrary pixel values.
@theme {
--spacing-xs: 4px; /* 0.5 × base */
--spacing-sm: 8px; /* 1 × base */
--spacing-md: 16px; /* 2 × base */
--spacing-lg: 24px; /* 3 × base */
--spacing-xl: 32px; /* 4 × base */
--spacing-2xl: 48px; /* 6 × base */
--spacing-3xl: 64px; /* 8 × base */
}
4. Border Radius
@theme {
--radius-sm: 6px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-xl: 16px;
--radius-full: 9999px; /* pills, badges */
}
5. Shadows
@theme {
--shadow-card: 0 1px 3px rgba(35, 28, 51, 0.08), 0 1px 2px rgba(35, 28, 51, 0.06);
--shadow-modal: 0 4px 24px rgba(35, 28, 51, 0.16);
--shadow-dropdown: 0 2px 8px rgba(35, 28, 51, 0.12);
}
6. Dark Mode Strategy
Bilko uses CSS prefers-color-scheme for automatic dark mode, togglable via a data-theme attribute on <html>.
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #14111f;
--color-surface: #1e1929;
--color-text-primary: #e4def0;
--color-border: #3a2f55;
}
}
[data-theme='dark'] {
--color-bg: #14111f;
--color-surface: #1e1929;
--color-text-primary: #e4def0;
--color-border: #3a2f55;
}
OPEN QUESTION OQ-3: Dark mode is not implemented in
apps/web/as of 2026-05-16. The token system above is the planned architecture. Timeline for implementation is not assigned.
7. shadcn/ui Variant API via cva
All shadcn/ui components use class-variance-authority (cva) for variant composition. The cn() utility merges Tailwind classes with conflict resolution via tailwind-merge.
Button — verified cva definition (from components/ui/button.tsx)
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const buttonVariants = cva(
// Base styles — always applied
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-10 px-4 py-2',
sm: 'h-11 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-11 w-11',
},
},
defaultVariants: { variant: 'default', size: 'default' },
},
)
Adding a Custom Variant
// Extend buttonVariants for a "plum-outline" variant
const buttonVariants = cva(/* base */, {
variants: {
variant: {
// ... existing variants
'plum-outline': 'border-2 border-primary text-primary hover:bg-primary-subtle',
},
},
})
cn() Usage Rule
Always compose class lists with cn() — never with template literals or string concatenation:
// Correct
<div className={cn('base-class', condition && 'conditional-class', className)} />
// Wrong
<div className={`base-class ${condition ? 'conditional-class' : ''} ${className}`} />