Skip to content

VIBE Design System

Design principles, component specifications, and implementation guidelines for a consistent visual language across the VIBE application.

See also:

  • vibe/static/foundation.css - Design token definitions
  • vibe/static/style.css - Component implementations
  • vibe/static/scaffold.css - Skeleton css file for customizing/overriding specific css classes with the UI Customization facilities

1. DESIGN PHILOSOPHY

1.1 Core Principles

VIBE follows a strict, professional aesthetic inspired by IBM Carbon Design System:

  • Sharp geometry: Square corners (radius: 0) for all interactive elements
  • Minimal decoration: No gradients, no shadows on buttons, no emoji
  • Clear hierarchy: Typography and spacing create visual order, not ornamentation
  • Functional icons: Material Symbols (Sharp, Unfilled) for all iconography
  • Consistent density: Uniform padding and spacing across components

Exception: Assistant chat bubbles use asymmetric rounded corners (3 of 4 rounded) to create a speech bubble effect that differentiates conversation from UI.

1.2 Visual Identity

The color palette derives from Kahn Pedersen brand guidelines with warm, earth-toned neutrals:

Role Token Value Usage
Primary --vibe-color-primary #182025 Text, filled buttons
Surface --vibe-color-surface #ffffff Cards, panels
Background --vibe-color-background #f8f6f3 Page background
Accent --vibe-color-accent #d6b890 Focus rings, highlights
Border --vibe-color-border #c8b7a5 Component borders

2. ICONOGRAPHY

2.1 Material Symbols Sharp

VIBE uses Google Material Symbols in the Sharp style with unfilled weight for all iconography.

Why Material Symbols Sharp:

  • Professional, geometric appearance aligns with IBM Carbon influence
  • Variable font supports weight/fill customization
  • Comprehensive icon set (3800+ symbols)
  • Self-hosted for privacy and performance

Configuration:

  • Style: Sharp (not Outlined or Rounded)
  • Fill: 0 (unfilled/outline only)
  • Weight: 400 (regular) or 500 (medium) for emphasis
  • Optical size: 20-24px for UI, 48px for display

2.2 Icon Font Setup

Self-hosted at vibe/static/fonts/material-symbols/. The @font-face declaration and .material-symbols utility class are defined in foundation.css.

2.3 Icon Usage

<span class="material-symbols" aria-hidden="true">save</span>

Add aria-hidden="true" to decorative icons. Add aria-label to icon-only buttons.

2.4 Standard Icon Mappings

Action Icon Name
Save save
Load folder_open
Reset refresh
Download download
Delete delete
Help help
Info info
Search search
Previous chevron_left
Next chevron_right
Expand expand_more
Collapse expand_less
Close close
Check check
Warning warning
Error error
AI Assist auto_awesome
Document description
Upload upload_file
Add/New add
Settings settings
Bookmark bookmark_add
Inventory inventory_2
Submit arrow_upward
Replay replay
Success check_circle
Map map

3. BUTTON SYSTEM

3.1 Button Hierarchy

All buttons use --vibe-radius-none (0) for sharp corners.

Variant Class Usage
Primary .vibe-button-primary Main actions (Submit, Run, Save)
Secondary .vibe-button-secondary Alternative actions (Cancel, Back)
Ghost .vibe-button-ghost Tertiary actions, navigation
Danger .vibe-button-danger Destructive actions
Icon .vibe-button-icon Icon-only buttons

3.2 Button Specifications

All button variants share the same structure: font-family: var(--vibe-font-ui), font-size: var(--vibe-text-sm), font-weight: var(--vibe-weight-medium), border-radius: var(--vibe-radius-none), padding: var(--vibe-space-2) var(--vibe-space-4).

Variant Background Border Text color
Primary --vibe-color-primary --vibe-color-primary --vibe-color-dark-text
Secondary transparent --vibe-color-border --vibe-color-text
Ghost transparent none --vibe-color-text

3.3 Button Sizes

Size Class Padding Font Size
Small .vibe-button-sm --vibe-space-1 --vibe-space-2 --vibe-text-xs
Default (none) --vibe-space-2 --vibe-space-4 --vibe-text-sm
Large .vibe-button-lg --vibe-space-3 --vibe-space-6 --vibe-text-base

3.4 Button States

  • Hover: Slight background shift (not color change)
  • Focus: No visible indicator (outline: none)
  • Focus-visible: 2px outline for keyboard navigation only
  • Active: Pressed appearance (slightly darker)
  • Disabled: Reduced opacity (0.5), no pointer events

3.5 Icon + Text Buttons

Icon precedes text, gap --vibe-space-2, icon inherits button font size, vertically centered.

4. FORM CONTROLS

4.1 Input Fields

All inputs use sharp corners and consistent borders: border: var(--vibe-border-width) solid var(--vibe-color-border), padding: var(--vibe-space-2) var(--vibe-space-3), background: var(--vibe-color-input). On focus: border-color: var(--vibe-color-focus), box-shadow: var(--vibe-focus-ring).

4.2 Select/Dropdown

Dropdowns follow the same styling as inputs with a trailing chevron icon.

4.3 Checkboxes and Radios

Custom styled with sharp corners (checkboxes) and consistent size.

5. NAVIGATION PATTERNS

5.1 Pagination / Step Navigation

Previous/Next navigation uses ghost buttons with chevron icons (chevron_left / chevron_right) flanking a .vibe-pagination-status counter.

<nav class="vibe-pagination">
  <button class="vibe-button vibe-button-ghost" disabled>
    <span class="material-symbols">chevron_left</span>
    Previous
  </button>
  <span class="vibe-pagination-status">1 / 10</span>
  <button class="vibe-button vibe-button-ghost">
    Next
    <span class="material-symbols">chevron_right</span>
  </button>
</nav>

5.2 Toolbar

Toolbars use .toolbar-button class with consistent spacing:

<div class="unified-toolbar">
  <div class="toolbar-left">
    <a href="/save" class="toolbar-button">
      <span class="material-symbols" aria-hidden="true">save</span>
      <span class="text">Save</span>
    </a>
  </div>
</div>

Toolbar button specifications:

  • Icons: 18px Material Symbols
  • Text: --vibe-text-xs
  • Padding: --vibe-space-1 vertical, --vibe-space-2 horizontal
  • Gap between buttons: --vibe-space-2
  • Margin: 0 (explicit reset for button elements)

5.3 Dropdown Menus

Dropdown menus use inline Alpine.js state for CSP compliance. Do not use registered Alpine components for dropdowns — use inline x-data instead.

<div class="download-dropdown" x-data="{ open: false }" @click.away="open = false">
    <button class="toolbar-button dropdown-toggle"
            @click="open = !open" @keydown.escape="open = false"
            :aria-expanded="open">
        <span class="material-symbols" aria-hidden="true">download</span>
        <span class="text">Download</span>
        <span class="material-symbols arrow" aria-hidden="true">expand_more</span>
    </button>
    <div class="download-menu" x-show="open" x-cloak>
        <a href="/download/zip" class="download-option">Download All (.zip)</a>
        <a href="/download/pdf" class="download-option">Download PDF</a>        
    </div>
</div>

Required Alpine directives: x-data="{ open: false }" (inline state), @click.away (close on outside click), @keydown.escape (close on Escape), :aria-expanded (accessibility), x-show + x-cloak (visibility).

5.4 Help Icons

Simple icon buttons without background circles. Color --vibe-color-text-muted, hover: primary. Focus: outline only on :focus-visible.

<span class="help-icon" role="button" tabindex="0">
  <span class="material-symbols" aria-hidden="true">help</span>
</span>

5.5 Tabs

Two tab variants modelled after IBM Carbon. Both styled in style.css and exposed in scaffold.css.

Variant Class When to Use
Line .tabs-line Navigation between views or pages (review nav, grouped question panels)
Contained .tabs-contained Switching content within a panel (toolbar document/assistant tabs, workbench)

Line tabs show an underline indicator. Contained tabs show bordered segments with a primary-color top accent on the active tab.

Structure

Every tab strip uses a three-layer structure: .tabs-shell (wrapper + variant class) > .tab-scroll-button (overflow nav) + .tabs (scrollable row) > .tab-button items.

<div class="tabs-shell tabs-line" data-tabs-shell>
    <button class="tab-scroll-button" data-tabs-scroll="left" hidden>
        <span class="material-symbols" aria-hidden="true">chevron_left</span>
    </button>
    <div class="tabs" data-tabs-list role="tablist">
        <button class="tab-button active" role="tab">Tab Label</button>
    </div>
    <button class="tab-scroll-button" data-tabs-scroll="right" hidden>
        <span class="material-symbols" aria-hidden="true">chevron_right</span>
    </button>
</div>

Data attributes (required for scroll auto-show): data-tabs-shell on wrapper, data-tabs-list on .tabs, data-tabs-scroll="left|right" on scroll buttons.

State Management

Pattern When to Use Example
Alpine.store('tabs') Global tab state shared across components (toolbar) $store.tabs.setActive('main')
Alpine.data('groupTabs') Scoped state within a widget (grouped questions) @click="activate" with data-group-id
No Alpine Server-rendered navigation (review nav) .active class + aria-current="page"

Accessibility

Use role="tablist" on .tabs, role="tab" on buttons, role="tabpanel" on panels. Link with aria-controls / aria-selected. Scroll buttons need aria-label.

Do Not

  • Invent custom tab styling — use .tabs-line or .tabs-contained
  • Use HTML entities or Unicode arrows for scroll buttons — use Material Symbols
  • Skip data-tabs-shell / data-tabs-list / data-tabs-scroll attributes

5.6 Sidebar Navigation

Fixed sidebar panel with a vertical list of navigable items and optional progress indicators. Currently used for paged interview TOC and review assessment lists.

Structure

BEM block sidenav. All elements optional except the container and items.

.sidenav                          <aside>
  .sidenav__header                optional header row (title + action button)
  .sidenav__body                  scrollable content area
    .sidenav__group               optional collapsible section
      .sidenav__group-header      clickable heading (chevron + title + badge)
      .sidenav__group-items       container for grouped items
    .sidenav__item                nav link/button
      .sidenav__item-status       leading status indicator
      .sidenav__item-label        text
      .sidenav__item-badge        trailing badge (counter or icon)

Modifiers

Modifier Effect
.sidenav__item--active Accent background, white text — current item
.sidenav__item--disabled Muted text, pointer-events: none
.sidenav__group--collapsed Hides .sidenav__group-items

Example

Shows header, a collapsible group with counter badge, and items with status icons. For a simple paged TOC, omit .sidenav__header and .sidenav__group — put items directly in .sidenav__body.

<aside class="sidenav">
  <div class="sidenav__header">
    <span class="sidenav__title">Assessment</span>
    <button class="vibe-button-primary vibe-button-sm">Run AI</button>
  </div>
  <nav class="sidenav__body">
    <div class="sidenav__group" x-data="{ open: true }">
      <div class="sidenav__group-header" @click="open = !open">
        <span class="material-symbols sidenav__group-chevron">chevron_right</span>
        <span class="sidenav__group-title">Security Requirements</span>
        <span class="sidenav__group-badge">2/5</span>
      </div>
      <div class="sidenav__group-items" x-show="open">
        <a class="sidenav__item sidenav__item--active">
          <span class="sidenav__item-status sidenav__item-status--success">
            <span class="material-symbols">check_circle</span>
          </span>
          <span class="sidenav__item-label">Authentication required</span>
        </a>
        <!-- more items... -->
      </div>
    </div>
  </nav>
</aside>

Status indicators

Leading icon in .sidenav__item-status. Always Material Symbols (not CSS circles or Unicode).

Modifier Color token Icon
--pending --vibe-color-text-muted radio_button_unchecked
--success --vibe-color-success check_circle
--error --vibe-color-error cancel
--partial --vibe-color-warning error

Badges

.sidenav__item-badge and .sidenav__group-badge hold a trailing indicator: plain-text counter (3/5) or a single Material Symbol. Right-aligned via flex margin-left: auto.

Layout

position: fixed, left --vibe-space-4, top var(--header-height). Right edge aligns with main content start. .sidenav__body scrolls vertically. Hidden entirely on narrow viewports.

Collapsible groups

Inline Alpine.js state (x-data="{ open: true }") or a registered component for richer behavior. The .sidenav__group-chevron rotates 90deg when expanded.

6. CARDS AND PANELS

6.1 Card Styling

Cards use sharp corners with subtle borders: background: var(--vibe-color-surface), border: var(--vibe-border-width) solid var(--vibe-color-border), padding: var(--vibe-space-4). Panel headers add a bottom border with --vibe-space-3 padding below.

7. CHAT BUBBLES (EXCEPTION)

The Assistant interface uses rounded corners to create conversational feel. User messages round 3 corners except bottom-right; assistant messages round 3 except bottom-left. This is the only place where rounded corners are used.