Busflow Docs

Internal documentation portal

Skip to content

@busflow/ui-core

Generic, framework-agnostic ShadCN/Tailwind components shared across all busflow applications. Strictly presentational — no API awareness, no store access, no route knowledge.

Reference Documents

  • [frontend-architecture.md](file:///Users/julianbruning/Projekte/busflow/busflow/docs/2-areas/architecture/frontend.md) — Package constraints and boundaries
  • [config-tailwind](file:///Users/julianbruning/Projekte/busflow/busflow/packages/config-tailwind/README.md) — Visual identity, design tokens
  • [guidelines.md](file:///Users/julianbruning/Projekte/busflow/busflow/docs/2-areas/process/guidelines.md) — Documentation standards

Package Rules

From [frontend-architecture.md](file:///Users/julianbruning/Projekte/busflow/busflow/docs/2-areas/architecture/frontend.md):

  • Exports raw .vue and .ts files via central index.ts
  • "sideEffects": false in package.json for tree-shaking
  • vue listed as peerDependency
  • No API calls, no store access, no route awareness — data via props and events only

Component Inventory

The following components are currently migrated to the new design system architecture (based on shadcn-vue/Reka UI):

| Component | Status | Description | |----------------_|--------|-------------| | Button | ✅ Ready | Standard button with standard variants (default, destructive, outline, secondary, ghost, link) and sizes (default, sm, lg, icon). | | Badge | ✅ Ready | Standard badge with variants (default, secondary, destructive, outline). | | Alert | ✅ Ready | Standard alert component containing Alert, AlertTitle, and AlertDescription. Variants: default, destructive. | | Card | ✅ Ready | Generic content container holding Card, CardHeader, CardTitle, CardDescription, CardContent, and CardFooter. | | Input | ✅ Ready | Standard text input field. | | SectionHeader | ✅ Ready | Custom composite component for section titles with label, title, and subtitle props. |

More components will follow as we migrate them from busflow-ui.


File Structure

text
packages/ui-core/
├── src/
│   ├── components/
│   │   ├── ui/                    # shadcn-vue components
│   │   │   ├── button/
│   │   │   │   ├── Button.vue
│   │   │   │   ├── Button.story.vue
│   │   │   │   └── index.ts
│   │   │   ├── card/
│   │   │   │   ├── Card.vue
│   │   │   │   ├── CardHeader.vue
│   │   │   │   ├── ...              # CardTitle, CardContent, etc.
│   │   │   │   └── index.ts
│   │   │   └── ...                  # alert/, badge/, input/
│   │   └── SectionHeader/           # Custom (non-shadcn) components
│   │       ├── SectionHeader.vue
│   │       └── index.ts
│   ├── lib/
│   │   └── utils.ts                 # cn() helper (clsx + tailwind-merge)
│   ├── style.css                    # Tailwind + busflow tokens + shadcn CSS vars
│   └── index.ts                     # Central barrel export
├── components.json                  # shadcn-vue CLI config
├── package.json
├── tsconfig.json
└── README.md

Authoring Conventions

Naming

  • Component files: PascalCase (Button.vue, SectionHeader.vue)
  • Component directories: lowercase for shadcn (ui/button/), PascalCase for custom (SectionHeader/)
  • Props: camelCase, typed with TypeScript interfaces
  • Events: kebab-case, emit('update:modelValue') for v-model
  • CSS: Tailwind semantic tokens only (bg-primary, text-foreground); no hardcoded hex in components

Component API

  • defineProps<T>() with TypeScript generics
  • defineEmits<T>() for typed events
  • cn() from @/lib/utils for class merging
  • CVA (class-variance-authority) for variant definitions
  • Slots for composition: default, header, footer, icon, actions
  • Variants follow shadcn conventions: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'

Accessibility

  • All interactive components: keyboard navigable
  • aria-* attributes, WCAG 2.1 AA contrast ratios
  • :focus-visible for keyboard-only focus rings
  • Form components: id/for label association

Theme Polymorphism

For the full architecture see design-system architecture. Theme scopes are defined in config-tailwind.

Components strictly use semantic tokens (bg-primary, text-foreground) — hardcoded hex values are forbidden. The parent .theme-* class swaps the entire palette automatically. Components may also adapt their physical shape per environment:

Component.theme-passenger.theme-dispatcher.theme-driver
Buttonshadow-soft-warm, 300ms hoverSharp 4px, ring-offset focusmin-h-14 (56px), active:translate-y-[1px]
Cardfont-serif header, warm shadow, no borderTight p-4, 1px border, no shadowp-6, dark gray bg, semantic left-borders
DialogCentered, glassmorphism backdropCentered, sharp, snappy animationsBottom-docked Action Sheet, 100% width

Color Resolution Rules

Components resolve colors through three mechanisms, listed in order of preference:

1. Semantic CSS Variables (primary mechanism)

All surfaces, text, and borders must use semantic Tailwind tokens that map to CSS variables defined in config-tailwind and style.css:

vue
<!-- ✅ Correct — automatically adapts to theme + mode -->
<div class="bg-primary text-primary-foreground border-border">

<!-- ❌ Forbidden — hardcoded values bypass the token system -->
<div class="bg-blue-500 text-white border-gray-200">
<div class="bg-[#2E8B6E] text-[#fff]">

These variables change automatically when .dark or .theme-* classes are applied at the layout level. Components never need to know which theme or mode is active.

2. currentColor (icon and SVG inheritance only)

Use currentColor exclusively for inline SVGs and icon strokes so they inherit the parent element's color. Lucide icons already follow this convention:

vue
<!-- ✅ Correct — icon matches surrounding text color -->
<svg stroke="currentColor" ...>

<!-- ✅ Correct — Tailwind shorthand -->
<Loader2 class="text-current" />

currentColor is not a general theming strategy — it answers "match my parent's text color" but cannot express backgrounds, borders, or semantic intent.

3. dark: Variant (last resort — structural adjustments only)

The dark: Tailwind prefix is restricted to cases where a structural property (opacity, visibility, ring width) genuinely differs between light and dark mode and the token system cannot express the difference:

ts
// ✅ Acceptable — opacity ratio changes by mode
"border-destructive/50 text-destructive dark:border-destructive"

// ❌ Forbidden — swapping colors belongs in the token layer
"bg-white dark:bg-gray-900 text-black dark:text-white"

IMPORTANT

If you find yourself reaching for dark: to swap a color, the correct fix is to add or adjust a semantic CSS variable in config-tailwind/theme.css and its .dark override. Components must remain mode-agnostic.

Summary

MechanismScopeExample
Semantic CSS varsAll colors, backgrounds, bordersbg-primary, text-foreground, border-border
currentColorIcons and SVG strokes onlystroke="currentColor", text-current
dark: prefixOpacity / visibility adjustments onlydark:border-destructive (removes /50)

Layout Patterns

Shared layout primitives used across apps:

Container

PropertyDesktopMobile (≤768px)
max-width1120px100%
padding-xspace-xl (32px)space-lg (24px)

Section

PropertyDesktopMobile (≤768px)
padding-yspace-5xl (112px)space-4xl (80px)

Common Grid Patterns

PatternColumnsGapUsage
Feature grid3-colspace-xlFeature cards, benefit lists
Content split2-col (60/40)space-2xlText + visual pairings
Card gridauto-fit, min 280pxspace-lgResponsive card layouts

Section Header Composition

text
SECTION LABEL  — xs, semibold, accent, uppercase, letter-spacing: 0.12em
Section Title  — 3xl, extrabold, letter-spacing: -0.025em
Subtitle text, constrained to max-width 620px — lg, secondary color

Animation Patterns

AnimationDurationUsage
fadeInUp600msSection content on scroll (IntersectionObserver)
fadeIn400msSubtle content appearance
Stagger delays100ms incrementsGrouped elements entering sequentially

Interaction Micro-Animations

  • Button hover: translateY(-1px) + shadow increase
  • Card hover: translateY(-2px) + shadow-md
  • Focus ring: 0 0 0 3px accent @ 0.2 opacity

Responsive Breakpoints

NameValueTarget
sm640pxSmall phones
md768pxLarge phones, small tablets
lg1024pxTablets, small laptops
xl1280pxDesktop

Usage

vue
<script setup lang="ts">
import { 
  Button, 
  Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter,
  Badge 
} from '@busflow/ui-core'
</script>

<template>
  <Card>
    <CardHeader>
      <div class="flex items-center justify-between">
        <CardTitle>Trip Details</CardTitle>
        <Badge variant="default">Active</Badge>
      </div>
      <CardDescription>Berlin → Munich via Nuremberg</CardDescription>
    </CardHeader>
    <CardContent>
      <p>Passenger manifest and route information.</p>
    </CardContent>
    <CardFooter>
      <Button variant="outline" size="sm">View Details</Button>
    </CardFooter>
  </Card>
</template>

Internal documentation — Busflow