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 definitionsvibe/static/style.css- Component implementationsvibe/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¶
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-1vertical,--vibe-space-2horizontal - 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-lineor.tabs-contained - Use HTML entities or Unicode arrows for scroll buttons — use Material Symbols
- Skip
data-tabs-shell/data-tabs-list/data-tabs-scrollattributes
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.