Design System Architecture
The busflow design system follows a federated approach. A single monorepo of UI components (@busflow/ui-core, based on ShadCN/Tailwind) radically alters appearance, density, and ergonomics based on the application context.
For token definitions see
config-tailwind. For component implementations seeui-core. For visual testing seehistoire.
Core Philosophy: "One Codebase, Three Realities"
Instead of building three separate <Button> components for the Passenger, Dispatcher, and Driver, we build one polymorphic <Button> that is context-aware. We separate component logic (state, accessibility) from presentation (colors, typography) and ergonomics (touch targets, layout).
The Three Architectural Layers
1. Primitives Layer (Skeleton)
The underlying Radix UI / ShadCN foundation. Handles ARIA attributes, keyboard navigation, and state management (open/closed, focused/blurred). Remains identical across all environments.
2. Token Context Layer (Skin)
Strict usage of semantic Tailwind CSS variables (bg-primary, text-foreground). Hardcoded hex values or utility colors (bg-blue-500) are strictly forbidden inside ui-core.
The layout or router level applies three CSS theme scopes (.theme-passenger, .theme-dispatcher, .theme-driver), instantly swapping the entire palette without touching component code.
3. Adaptive Ergonomics Layer (Muscle)
Components use Tailwind contextual modifiers to change physical shape based on their parent theme class. A <Dialog> centers by default but docks to the bottom inside .theme-driver. A <Button> forces min-h-14 (56px) for thumb-tapping in the driver cockpit.
The Three Environments
| Element | .theme-passenger (B2C) | .theme-dispatcher (B2B) | .theme-driver (Mobile) |
|---|---|---|---|
| Vibe | Nostalgic Modernism / Boutique Hotel | Neo-Corporate / Functional Minimalism | Tactile Utility / Rugged |
| Typography | font-serif headings (Playfair Display), font-sans data | font-sans exclusively (Geist/Inter), tabular nums | font-sans exclusively, scaled up |
| Background | Alabaster (#F9F7F1) | Slate-tinted off-white | Off-black (minimize night-driving glare) |
| Primary | Deep Forest Green (#1B3B2E) | Deep Navy Blue | High-visibility Bright Blue |
| Corners | Soft (8px) | Sharp (4px) | Tactile (6px) |
| Shadows | Warm, dispersed (shadow-soft-warm) | Minimal shadow-sm | None on static; shadow-tactile on buttons |
| Transitions | Sweeping ease-in-out 300ms | Instant / Snappy (150ms) | Instant / Snappy (150ms) |
| Ergonomics | Standard | High data density | Oversized touch targets (min 56px), bottom-docked modals |
Component Polymorphism Examples
<Button>
- Passenger: Forest Green bg,
shadow-soft-warm, shadow deepens smoothly over 300ms on hover. - Dispatcher: Navy Blue bg,
4pxsharp corners,ring-offsetfor fast keyboard tab-navigation. - Driver: Forced
min-h-14(56px) for thumb-tapping.active:translate-y-[1px]for physical "press" feedback.
<Card>
- Passenger: High-quality stationery.
font-serif text-2xlheader, no borders — warm dispersed shadow only. - Dispatcher: Tight data container.
p-4padding, crisp1pxborder, no drop shadows. - Driver: Rugged basics.
p-6padding, contrasting dark gray bg, thick semantic left-borders for status.
<Dialog>
- Passenger: Warm glassmorphism backdrop (
backdrop-blur-md bg-stone-900/20), centered, serif typography. - Dispatcher: Centered, sharp corners, instant snappy animations.
- Driver: Bottom-docked Action Sheet at 100% width for one-handed thumb operation.
Color Mode Architecture
The design system supports a two-dimensional theming model: role themes and color modes compose independently via CSS class stacking on <html>.
The Two Dimensions
| Dimension | Controls | CSS Class | Who decides |
|---|---|---|---|
| Role theme | Colors, typography, radii, shadows — the personality | .theme-dispatcher, .theme-passenger, .theme-driver | The app itself (build-time) |
| Color mode | Light ↔ dark surface/text swap within the active theme | .dark | The user (or system preference) |
They compose via class stacking: <html class="theme-dispatcher dark"> applies the dispatcher palette in dark mode. Every .theme-x.dark combination defines a curated dark palette — not a generic inversion.
Composable Layering
@busflow/ui-core → useColorMode() Low-level .dark class manager
↓
@busflow/ui-domain → <ThemeProvider> Business-aware orchestrator
<ThemeToggle> UI toggle widget
useTheme() Context consumer
↓
apps/* → Wraps root in Passes theme name, done
<ThemeProvider>useColorMode()manages.darkon<html>, persists preference tolocalStorage, and listens toprefers-color-scheme.<ThemeProvider>consults the theme registry to determine if dark mode is supported, applies thetheme-*class, and provides context viainject.<ThemeToggle>reads the injected context and auto-hides when dark mode is unsupported (driver app).
Per-App Mode Rules
| App | Theme | Dark mode | Default |
|---|---|---|---|
workspace | theme-dispatcher | ✅ User toggleable | system |
passenger | theme-passenger | ✅ User toggleable | light |
driver | theme-driver | ❌ Forced dark | dark |
FOUC Prevention
Each app's index.html includes a synchronous inline <script> that reads the user's persisted preference from localStorage and applies .dark before the first paint. This prevents a flash of light mode for dark-mode users. The script mirrors useColorMode()'s resolution logic (including prefers-color-scheme fallback for 'system' mode).
Package Index
| Package | Content | Path |
|---|---|---|
config-tailwind | Design tokens (colors, typography, spacing, shadows), theme scopes, consumer setup | packages/config-tailwind/ |
ui-core | Generic presentational components (ShadCN/Tailwind), theme polymorphism, layout patterns | packages/ui-core/ |
ui-domain | Domain-specific components (seat map, PDF viewer, trip cards), consumer matrix | packages/ui-domain/ |
histoire | Visual component workshop, story authoring guide, regression testing | studio/histoire/ |