Skip to main content

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

VersionDateAuthorChanges
0.1{{DATE}}{{AUTHOR}}Initial draft

1. Design Principles

PrincipleDescription
Clarity firstEvery UI element must communicate its purpose without explanation
Consistent over cleverPrefer familiar patterns over novel interactions
Accessible by defaultWCAG AA compliance is a baseline, not a feature
Density-awareSupport comfortable and compact density modes
TODO: Add principle{{DESCRIPTION}}

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:

PairRetired hex RatioLabel in old doc WCAG AA (4.5:1)WCAG AAA (7:1)
Text primary on bg primary{{X:1}}{{Pass/Fail}}{{Pass/Fail}}
Text secondary on bg primary{{X:1}}{{Pass/Fail}}{{Pass/Fail}}
Interactive on bg primary{{X:1}}{{Pass/Fail}}{{Pass/Fail}}
White on brand-500{{X:1}}{{Pass/Fail}}{{Pass/Fail}}

TODO: Run contrast checks with {{axe | Colour Contrast Analyser}} and fill table.


3. Typography

3.1 Font Families

TokenValueUsageReason
--font-heading#00E5A0 {{Inter,Primary sans-serif}}teal-green H1–H4,Wrong display textbrand
--font-body#00B380 {{Inter,Primary sans-serif}}dark teal BodyWrong copy, labels, UIbrand
--font-mono#33EBB3 {{JetBrainsPrimary Mono,light monospace}}teal Code,Wrong technical data

3.2 Type Scale

Replacedby
TokenSizeWeightLine HeightLetter SpacingUsage
--text-display48px7001.1-0.02emHero headingsbrand
--text-h1#111113 36pxSidebar dark 700 1.2 -0.01emPage titles
--text-h2#14111F28px6001.25-0.01emSection headings
--text-h322px6001.30Subsections
--text-h418px6001.40Card headings
--text-body-lg18px4001.60Lead paragraphs
--text-body16px4001.60Default body copy
--text-body-sm14px4001.50Secondary text, captions
--text-label14px5001.40.01emForm labels, UI labels
--text-caption12px4001.40.02emTimestamps, meta
--text-code14px4001.60Inline code

TODO: Verify scale against Figma — update values if mismatched.


4. Spacing & Layout

4.1 Spacing Scale (4px Base Unit)

TokenValueUsage
--space-00px
--space-14pxMicro gaps, icon padding
--space-28pxTight inline spacing
--space-312pxCompact form elements
--space-416pxDefault content spacing
--space-520pxCard padding (compact)
--space-624pxCard padding, section padding
--space-832pxLarge section gaps
--space-1040pxFeature section padding
--space-1248pxSection separation
--space-1664pxPage-level padding
--space-2080pxHero sections

4.2 Grid System

PropertyValue
Column count12
Column gutter24px (mobile: 16px)
Container max-width1280px
Container side padding24px (mobile: 16px)

4.3 Responsive Breakpoints

NameMin WidthTarget Devices
xs0pxSmall phones
sm480pxLarge phones
md768pxTablets
lg1024pxSmall laptops
xl1280pxDesktops
2xl1536pxLarge displays

5.2. Component LibraryTypography

Fonts: National Park (headings) · Work Sans (body, UI) · DM Mono (numbers, data, code)

5.1Type Primitive Components (Atoms)

ComponentStatusVariantsStorybook
Button`{{DoneWIPPlanned}}`
Input`{{DoneWIPPlanned}}`
Select`{{DoneWIPPlanned}}`
Checkbox`{{DoneWIPPlanned}}`
Radio`{{DoneWIPPlanned}}`
Toggle/Switch`{{DoneWIPPlanned}}`
Textarea`{{DoneWIPPlanned}}`
Badge`{{DoneWIPPlanned}}`
Avatar`{{DoneWIPPlanned}}`
Tooltip`{{DoneWIPPlanned}}`
Spinner`{{DoneWIPPlanned}}`
Divider`{{DoneWIPPlanned}}`

5.2 Composite Components (Molecules)

ComponentStatusStorybook
Card{{Status}}{{URL}}
Modal / Dialog{{Status}}{{URL}}
Dropdown Menu{{Status}}{{URL}}
Table{{Status}}{{URL}}
Pagination{{Status}}{{URL}}
Toast / Notification{{Status}}{{URL}}
Form Field (label + input + error){{Status}}{{URL}}
Search Bar{{Status}}{{URL}}
Breadcrumb{{Status}}{{URL}}
Tabs{{Status}}{{URL}}
Accordion{{Status}}{{URL}}
Date Picker{{Status}}{{URL}}

5.3 Layout Components

ComponentDescription
ContainerMax-width wrapper with responsive padding
StackVertical flex stack with configurable gap
InlineHorizontal flex row with configurable gap/alignment
GridCSS grid layout wrapper
PageLayoutFull-page layout with sidebar/header/main/footer slots
SectionContent section with standard vertical rhythm

6. Iconography Guidelines

ItemStandard
Library`{{Lucide React
DeliveryInline SVG via component (no sprite, no icon font)
Sizes16px (sm), 20px (md, default), 24px (lg), 32px (xl)
Stroke width1.5px at 24px, scaled proportionally
ColorInherits currentColor — never hardcoded
Interactive iconsMust have visible focus ring + 44×44px touch target
Custom iconsSVG optimized via SVGO, placed in src/components/icons/

Accessibility rule: Icons conveying meaning must have aria-label. Decorative icons: aria-hidden="true".


7. Motion & Animation Standards

7.1 Duration Tokens

TokenValueUsage
--duration-instant50msMicro-feedback (checkbox check)
--duration-fast100msButton states, hover
--duration-normal200msModals, dropdowns
--duration-slow300msPage transitions, large panels
--duration-slower500msOnboarding, loading states

7.2 Easing Tokens

TokenValueUsage
--ease-defaultcubic-bezier(0.4, 0, 0.2, 1)General UI
--ease-entercubic-bezier(0, 0, 0.2, 1)Elements entering
--ease-exitcubic-bezier(0.4, 0, 1, 1)Elements leaving
--ease-springcubic-bezier(0.34, 1.56, 0.64, 1)Playful, emphasis

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

RequirementButtonsInputsModalsTablesNavigation
Keyboard accessible
Focus visibleFocus trap
ARIA rolebuttontextbox/comboboxdialogtable/gridnav
Screen reader labelaria-label or visible text<label> associatedaria-labelledbyCaption + headersaria-label
Error statearia-describedby error msgaria-invalid
Touch target44×44px min44px heightRow: 44px min44×44px

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 h1h3 and 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:

  1. Default story — basic render with minimal props
  2. All Variants story — every visual variant displayed
  3. All States story — hover, focus, disabled, error, loading
  4. Responsive story — behavior at each breakpoint
  5. Accessibility 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.


Approval

RoleNameDateSignature
Author
Lead Designer
Frontend Lead
Accessibility Reviewer