Skip to content

Vibe Design System

A multi-tenant CSS design system and Vue component library built for enterprise B2B white-labeling.

Architecture

The design system uses CSS cascade layers for deterministic specificity ordering. One declaration in the root @vibe-labs/design package controls the entire cascade:

css
@layer vibe.reset, vibe.tokens, vibe.utilities, vibe.components, vibe.theme, vibe.accessibility;
LayerPriorityPurpose
vibe.resetLowestGlobal resets (box-sizing, form normalization, tap highlight removal)
vibe.tokensCSS custom properties on :root — the tunable knobs
vibe.utilitiesSingle-property utility classes (.flex, .p-4, .text-lg)
vibe.componentsMulti-property compositions (.btn, .card, .input, .modal)
vibe.themeReserved for theme-level overrides
vibe.accessibilityReduced motion, focus visibility, screen reader utilities
Unlayered CSSHighestTenant overrides — wins automatically without !important

White-Labeling

Tenant CSS is written outside any @layer block. Because unlayered CSS always beats layered CSS in the cascade, tenant overrides work with zero specificity gymnastics:

css
/* vibe-design/src/index.css — brand defaults (inside @layer vibe.tokens) */
@layer vibe.tokens {
  :root {
    --surface-base: #0f1115;
    --color-accent: #7c5cff;
  }
}

/* tenant.css — unlayered, wins automatically */
:root {
  --surface-base: #1a1a2e;
  --color-accent: #e94560;
}

Dark / Light / System Modes

  • Dark (default): Base :root values in @layer vibe.tokens
  • Light: :root[data-mode="light"] overrides
  • System: @media (prefers-color-scheme: light) for :root:not([data-mode]) and :root[data-mode="system"]

Zero Cross-Package Dependencies

CSS packages communicate exclusively through token naming conventions (e.g. --color-{name}-500, --surface-{semantic}). No @import between utility packages, no bundler dependency graph required. Any package can be loaded or dropped independently.

Packages

Design Entry Point

PackageDescription
@vibe-labs/designRoot package — declares layer order, imports all utility packages, sets dark/light/system mode tokens

Utility Packages

All ship with @vibe-labs/design. Each follows the same structure: src/index.css (tokens) + scripts/build.mjs (generates utility classes) → dist/ (tokens + .g.css generated utilities).

PackageTokensUtilities
@vibe-labs/design-colorsNeutral 0-1000 scale with extended dark precision (25, 50, 75, 750, 825, 850, 875, 950 stops), status colors (success, warning, danger, info), swappable neutral palettes (charcoal, sand, slate, stone, zinc)Color utilities for text, background, border
@vibe-labs/design-typographyFont sizes (xs → hero-xl), weights (light → black), tracking, leading, font families (sans, mono, display).text-{size}, .font-{weight}, .tracking-{value}, .leading-{value}, alignment, decoration, truncation, line-clamp
@vibe-labs/design-spacingSpace scale 0-24 (0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24).p-{n}, .px-{n}, .py-{n}, .pt/r/b/l-{n}, .m-{n} (same axes), .gap-{n}, .gap-x/y-{n}
@vibe-labs/design-bordersRadius scale (sm → full), border widths (1-4), semantic + scale colors.rounded-{size}, .border-{width}, .border-{color}, .divide-{axis}
@vibe-labs/design-surfacesOpacity, blur, overlays, semantic surfaces (background, base, elevated, subtle, overlay, modal).opacity-{n}, .backdrop-blur-{size}, .bg-{semantic}, .fg-{semantic}, .overlay-{type}, .bg-blend-{mode}
@vibe-labs/design-elevationBox shadows (xs → 2xl), inset shadows, focus ring system.shadow-{size}, .shadow-inset-{size}, .ring, .ring-{color}, .ring-offset-{n}
@vibe-labs/design-formsGlobal reset in vibe.reset layer, input/button sizing, disabled states, placeholder stylingInput heights (sm/md/lg), padding, focus ring integration
@vibe-labs/design-gradientsSemantic gradients (card, accent, shimmer, scroll fade, depth).gradient-{name}, .gradient-fade-{direction}
@vibe-labs/design-displayZ-index scale (base → toast → max).block, .flex, .grid, .hidden, .invisible, .visible, .overflow-{value}, .z-{level}, position utilities, inset utilities
@vibe-labs/design-flex.flex-{direction}, .flex-wrap, .flex-{grow/shrink}, .items-{align}, .justify-{content}, .self-{align}, .order-{n}
@vibe-labs/design-grids.grid, .grid-cols-{1-12}, .col-span-{n}, .row-span-{n}, .col-start/end-{n}
@vibe-labs/design-layoutsContainer breakpoints (sm → xl), safe area insets.container, .mx-auto, alignment primitives, --safe-top/bottom/left/right for notched devices
@vibe-labs/design-sizing.w-{value}, .h-{value}, .min-w/h-{value}, .max-w/h-{value}, .aspect-{ratio}, .object-{fit} (keyword + fractional sizing)
@vibe-labs/design-images.bg-{size}, .bg-{position}, .bg-{repeat}, .bg-{attachment}
@vibe-labs/design-interactions.cursor-{type}, .select-{value}, .pointer-events-{value}, .touch-{action}, .scroll-{behavior}, .snap-{type}
@vibe-labs/design-transitionsDuration (instant → lazy), easing (linear, spring, bounce, elastic, smooth, decel, accel, sharp).duration-{value}, .ease-{curve}, .transition-{property}, .delay-{value}, .animate-{name}, .motion-reduce, .motion-safe
@vibe-labs/design-responsiveContainer queries, viewport-based visibility, responsive layout helpers

Component Packages (CSS + TypeScript Types)

Each component package generates framework-agnostic CSS (in @layer vibe.components) and exports TypeScript type definitions. The CSS uses data-attribute selectors for variants (e.g. [data-size="sm"], [data-variant="accent"]), configurable between attr and flat modes.

PackageComponents
@vibe-labs/design-components-avatarsAvatar, avatar groups
@vibe-labs/design-components-badgesBadge variants (neutral, accent, success, warning, danger, info)
@vibe-labs/design-components-buttonsButton variants, sizes, icon buttons, button groups
@vibe-labs/design-components-cardsCard, card header/body/footer
@vibe-labs/design-components-coreShared base styles
@vibe-labs/design-components-dropdownsDropdown menu, items, sections, keyboard navigation styles
@vibe-labs/design-components-graphsGraph container, tooltip, legend, axis, annotations
@vibe-labs/design-components-imagesImage container, aspect ratio, loading states
@vibe-labs/design-components-inputsText input, textarea, select, field wrapper, labels, messages, validation states
@vibe-labs/design-components-menusVertical/horizontal menus, menu items
@vibe-labs/design-components-modalsModal, overlay, header/body/footer, animation states
@vibe-labs/design-components-paginationPagination bar, page items, ellipsis
@vibe-labs/design-components-progressProgress bar, progress ring
@vibe-labs/design-components-spinnersSpinner variants with shimmer animation
@vibe-labs/design-components-tabsTab list, tab triggers, tab panels
@vibe-labs/design-components-toastsToast container, toast item, status variants
@vibe-labs/design-components-togglesToggle switch, toggle field
@vibe-labs/design-components-uploadsUpload zone, upload item, upload indicator, progress overlay

Vue Component Library

Full Vue 3 bindings with TypeScript generics, composables, and accessibility support.

PackageExports
@vibe-labs/design-vue-avatarsVibeAvatar — auto-generates initials and background color from name
@vibe-labs/design-vue-buttonsVibeButton — variants, sizes, loading states, icon support
@vibe-labs/design-vue-cardsVibeCard, VibeCardHeader, VibeCardBody, VibeCardFooter
@vibe-labs/design-vue-dropdownsVibeDropdown, VibeDropdownItem, VibeDropdownSection — keyboard navigation, click-outside
@vibe-labs/design-vue-formsVibeForm, VibeInput, VibeTextarea, VibeSelect, VibeField + useVibeForm composable
@vibe-labs/design-vue-imagesVibeImage — loading states, error fallback, lazy loading
@vibe-labs/design-vue-modalsVibeModal, VibeModalHeader, VibeModalBody, VibeModalFooter + useModal composable
@vibe-labs/design-vue-paginationVibePagination + usePagination composable
@vibe-labs/design-vue-progressVibeProgressBar, VibeProgressRing + useProgress, useProgressTimer composables
@vibe-labs/design-vue-spinnersVibeSpinner — size variants
@vibe-labs/design-vue-tabsVibeTabs, VibeTabList, VibeTabTrigger, VibeTabPanel
@vibe-labs/design-vue-toastsVibeToast, VibeToastContainer + useToast composable
@vibe-labs/design-vue-togglesVibeToggle — switch with label positioning
@vibe-labs/design-vue-uploadsVibeUploadZone, VibeUploadItem, VibeUploadList, VibeUploadIndicator + useUploadQueue composable

Key Composables

ComposableWhat it does
useVibeFormReactive form state, validation (submit/blur/change), field registration via provide/inject, dirty tracking, typed field() helper for v-bind
useUploadQueueConcurrent upload management with configurable parallelism, AbortController cancellation, validation, retry, preview URL lifecycle
useFocusTrapKeyboard focus trapping within a container, Tab/Shift+Tab wrapping, restores previously focused element
useScrollLockBody scroll lock with reference counting for nested modals, scrollbar width compensation to prevent layout shift
useTooltipHover position tracking and datum management for graph components
useModalSimple reactive open/close/toggle state for modal control
usePaginationComputes visible page range with ellipsis, boundary pages, and windowed navigation
useProgressShared percentage/clamping/auto-color resolution for progress components
useProgressTimerETA estimation from progress samples with smoothed rate calculation

Graph Package

PackageDescription
@vibe-labs/design-vue-graphsd3-scale/d3-shape integration — VibeSparkline, VibeBarChart, VibeLineChart with tooltip, legend, and annotation support

Build System

Each package follows a consistent build pattern:

vibe-design-{name}/
├── package.json
├── src/
│   └── index.css          ← Tokens (CSS custom properties in @layer vibe.tokens)
├── scripts/
│   └── build.mjs          ← Generates utility classes
└── dist/
    ├── index.css           ← Copied from src
    └── {name}.g.css        ← Generated utilities (in @layer vibe.utilities)

Build command: rimraf dist && ncp src dist && node ./scripts/build.mjs

Generated files use the .g.css suffix to distinguish hand-written CSS from generated output.

Variant System

Component CSS uses data-attribute selectors by default, with a configurable SelectorMode:

html
<!-- attr mode (default) — self-documenting HTML -->
<button class="btn" data-variant="accent" data-size="lg">Click me</button>

<!-- flat mode — traditional classes -->
<button class="btn btn-accent btn-lg">Click me</button>

The build system generates both modes from the same source definitions.

Token Ownership

Each token family has a single owning package to prevent conflicts:

Token FamilyOwnerExamples
--color-*colors--color-neutral-900, --color-success-500
--text-*typography--text-primary, --text-muted, --text-lg
--font-*typography--font-sans, --font-bold
--space-*spacing--space-0 through --space-24
--surface-*surfaces--surface-base, --surface-elevated
--border-*, --radius-*borders--border-subtle, --radius-md
--shadow-*elevation--shadow-xs through --shadow-2xl
--duration-*, --ease-*transitions--duration-fast, --ease-spring
--z-*display--z-modal, --z-toast
--input-*, --btn-*forms--input-height-md, --btn-radius
--gradient-*gradients--gradient-card, --gradient-fade-b
--safe-*layouts--safe-top
--overlay-*, --blur-*surfaces--overlay-scrim, --blur-lg

Color System

Neutral Scale (Extended Dark Precision)

The neutral scale runs 0-1000 with additional stops in the dark range for subtle surface distinctions:

0 (white) → 25 → 50 → 75 → 100 → 200 → 300 → 400 → 450 → 500 →
550 → 600 → 650 → 700 → 750 → 800 → 825 → 850 → 875 → 900 → 950 → 1000 (black)

The 750-950 range maps directly to the surface hierarchy in dark mode:

SurfaceToken
--surface-background--color-neutral-950
--surface-base--color-neutral-900
--surface-subtle--color-neutral-875
--surface-elevated--color-neutral-850
--surface-overlay--color-neutral-800
--surface-modal--color-neutral-750

Swappable Neutral Palettes

Alternative neutral scales available: charcoal, sand, slate, stone, zinc. Import the desired palette to shift the entire system's neutral tones.

Status Colors

Four status families, each with a full 50-900 scale: success, warning, danger, info.

Browser Requirements

This design system targets modern browsers:

BrowserMinimum Version
Chrome99+
Edge99+
Safari15.4+
Firefox131+

Key features requiring modern browser support: CSS @layer, revert-layer, container queries, dynamic viewport units (dvh/dvw), :has() selector.

Design Principles

  1. Tokens are defaults, not mandates — every token has a sensible default; the design package overrides per mode; tenants override as needed
  2. No cross-package dependencies — packages communicate only through CSS variable naming conventions
  3. Utilities are single-property — multi-property combinations belong in the component layer
  4. Generated output is small — only utilities that matter, not every possible combination
  5. Mobile-first — dynamic viewport units, safe area insets, touch-action, tap-highlight removal built in from the start
  6. Framework-agnostic CSS — Vue bindings are a separate layer; the token and component CSS works with any framework