Design System
title: Design System
Documentationowner: vizu
last-updated: 2026-05-16
supersedes: docs/frontend/DESIGN-SYSTEM.md (2026-02-25 — teal palette, retired)
status: canonical
Bilko Design System
Project:RETIRED: {{PROJECT_NAME}}The Version:previous {{VERSION}}DESIGN-SYSTEM.md Date:documented {{DATE}}a Author:teal {{AUTHOR}}(#00E5A0) Status:palette. DraftThat |document Inis Reviewwrong |and Approvedarchived. Reviewers:The {{REVIEWERS}}canonical brand is plum (#8B6BBF). See docs/branding/branding/bilko-brand-guidelines.md for the authoritative brand source.
Document History
Project:RETIRED: {{PROJECT_NAME}}The Version:previous {{VERSION}}DESIGN-SYSTEM.md Date:documented {{DATE}}a Author:teal {{AUTHOR}}(#00E5A0) Status:palette. DraftThat |document Inis Reviewwrong |and Approvedarchived. Reviewers:The {{REVIEWERS}}canonical brand is plum (#8B6BBF). See docs/branding/branding/bilko-brand-guidelines.md for the authoritative brand source.
1. Design Principles
2. Color System
2.1 Primitive Palette — Canonical Plum
Source of truth: bilko-brand-guidelines.md (Raw2026-05-16). Values)Tokens below are the Tailwind 4 @theme mapping.
CSS Custom Properties (@theme block in globals.css)
@theme {
/* BrandPrimary primitivesplum — do NOT use directly in componentsscale */
--color-brand-50:primary: {{#F0F9FF}};#8b6bbf; /* Plum Primary — CTAs, active states, links */
--color-brand-100:primary-hover: {{#E0F2FE}};#5b3e8a; /* Deep Plum — hover on primary elements */
--color-brand-500:primary-light: {{#0EA5E9}};#c4a8e0; /* Light Plum — subtle backgrounds, chips */
--color-brand-600:primary-subtle: {{#0284C7}};#ede5f8; /* Plum tint — focus rings, hover surfaces */
/* Accent */
--color-brand-900:accent: {{#0C4A6E}};#f2c87a; /* Gold — highlights, accent dots, icons */
--color-neutral-0:accent-hover: #FFFFFF;#e8ae4a; --color-neutral-50: {{#F8FAFC}};
--color-neutral-100: {{#F1F5F9}};
--color-neutral-400: {{#94A3B8}};
--color-neutral-700: {{#334155}};
--color-neutral-900: {{#0F172A}};
--color-neutral-1000: #000000;
2.2 Semantic Tokens (Light Mode)
/* Gold dark — hover on accent elements */
/* Surface / Background */
--color-bg-primary:surface: var(--color-neutral-0);#f9f7fc; /* Light Lavender — preferred background */
--color-bg-secondary:surface-dark: var(--color-neutral-50);#14111f; /* Dark mode background */
--color-bg-elevated:bg: var(--color-neutral-0);#ffffff; /* Pure white — card backgrounds, modals */
/* Text */
--color-text-primary: var(--color-neutral-900);#231c33; /* Dark plum-toned text — on light backgrounds */
--color-text-secondary: var(--color-neutral-700);
--color-text-disabled: var(--color-neutral-400);
--color-text-inverse: var(--color-neutral-0);#6b7280; /* InteractiveMid gray — labels, secondary copy */
--color-interactive-primary:text-light: var(--color-brand-500);#e4def0; /* On dark backgrounds */
--color-interactive-primary-hover:text-muted: var(--color-brand-600);#9ca3af; --color-interactive-primary-active:/* var(--color-brand-700);Muted, disabled states */
/* Semantic */
--color-success: {{#10B981}};#22c55e;
--color-warning: {{#F59E0B}};#f59e0b;
--color-error: {{#EF4444}};#ef4444;
--color-info: {{#3B82F6}};#3b82f6;
/* 2.3Border Semantic Tokens (Dark Mode)
[data-theme="dark"] {*/
--color-bg-primary:border: var(--color-neutral-900);#e5e1ef; /* Plum-tinted border */
--color-bg-secondary:border-strong: var(--color-neutral-800);#c4a8e0; /* Stronger border for focused elements */
/* Sidebar */
--color-text-primary:sidebar-bg: var(--color-neutral-50);#14111f; /* TODO:Dark Completeplum dark mode token mappingsidebar */
--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;
}
2.4Color Contrast RatiosRules (WCAG)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:
TODO: Run contrast checks with {{axe | Colour Contrast Analyser}} and fill table.
3. Typography
3.1 Font Families
|
teal-green |
|
|
dark teal |
|
|
teal |
3.2 Type Scale
| |||||
|
|||||
| |||||
| |||||
| |||||
| |||||
| |||||
| |||||
| |||||
| |||||
|
TODO: Verify scale against Figma — update values if mismatched.
4. Spacing & Layout
4.1 Spacing Scale (4px Base Unit)
| ||
| ||
| ||
| ||
| ||
| ||
| ||
| ||
| ||
| ||
| ||
|
4.2 Grid System
4.3 Responsive Breakpoints
| ||
| ||
| ||
| ||
| ||
|
5.2. Component LibraryTypography
Fonts: National Park (headings) · Work Sans (body, UI) · DM Mono (numbers, data, code)
5.1Type Primitive Components (Atoms)
5.2 Composite Components (Molecules)
| ||
| ||
| ||
| ||
| ||
| ||
| ||
| ||
| ||
| ||
| ||
|
5.3 Layout Components
| |
| |
| |
| |
| |
|
6. Iconography Guidelines
| |
|
Accessibility rule: Icons conveying meaning must have aria-label. Decorative icons: aria-hidden="true".
7. Motion & Animation Standards
7.1 Duration Tokens
| ||
| ||
| ||
| ||
|
7.2 Easing Tokens
| | |
| | |
| | |
| |
7.3 Reduced MotionScale
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
Rule: Every animated component MUST respect prefers-reduced-motion.
8. Accessibility Requirements Per Component
| | | | | |
| | | |||
| | ||||
9. Design Token Format
9.1 CSS Custom Properties (Source of Truth)
/* tokens/colors.css */
:root@theme {
--color-brand-500:font-heading: #0EA5E9;'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 */
}
9.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 Configclasses Extensionwith 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
// tailwind.config.tsExtend exportbuttonVariants defaultfor a "plum-outline" variant
const buttonVariants = cva(/* base */, {
theme:variants: {
extend:variant: {
colors: {
brand: {
500: 'var(--color-brand-500)',
// ... }existing variants
'plum-outline': 'border-2 border-primary text-primary hover:bg-primary-subtle',
},
},
}
})
9.3cn() JavaScript/TypeScriptUsage ConstantsRule
Always compose class lists with cn() — never with template literals or string concatenation:
// tokens/colors.tsCorrect
—<div forclassName={cn('base-class', usecondition in charting libraries, canvas, etc.
export const colors = {
brand500:&& '#0EA5E9'conditional-class', className)} />
// ...Wrong
<div className={`base-class ${condition ? 'conditional-class' : ''} as${className}`} const;/>
Token update process: Figma → Style Dictionary export → CSS/JS/Tailwind files → PR review.
10. Component Documentation Standard
Each component story must include:
Defaultstory — basic render with minimal propsAll Variantsstory — every visual variant displayedAll Statesstory — hover, focus, disabled, error, loadingResponsivestory — behavior at each breakpointAccessibility story— keyboard navigation, screen reader notes
Component JSDoc minimum:
/**
* Button component for primary user actions.
*
* @example
* <Button variant="primary" onClick={handleSubmit}>Save Changes</Button>
*/
Props table: Every prop must have: type, default, required flag, description.