Vetrix Docs

Vetrix UI Style Guide

The authoritative reference for the Vetrix design system. All new UI work should reference this document first.


Color System

All colors are defined in tailwind.config.ts as theme extensions. Use the vetrix-* token names everywhere — never hardcode hex values in component files.

Navigation

Token Hex Usage
vetrix-nav #0747A6 TopNav background
vetrix-nav-border #0052CC TopNav border, hover backgrounds in nav
vetrix-nav-text #B3D4FF Text and icons inside TopNav

Brand / Interactive

Token Hex Usage
vetrix-primary #0052CC Buttons, links, active states
vetrix-primary-hover #0747A6 Hover state for primary elements
vetrix-primary-light #DEEBFF Active sidebar item background
vetrix-info #0052CC Same as primary; use for info badges

Surfaces

Token Hex Usage
vetrix-canvas #F4F5F7 Page/app background (body)
vetrix-surface #FFFFFF Cards, panels, table rows
vetrix-surface-hover #F4F5F7 Row/item hover background

Borders

Token Hex Usage
vetrix-border #DFE1E6 Default borders on cards, tables, inputs
vetrix-border-strong #C1C7D0 Emphasized borders (focus rings, dividers)

Text

Token Hex Usage
vetrix-text-primary #172B4D Body text, headings, labels
vetrix-text-secondary #6B778C Secondary / helper text, timestamps
vetrix-text-disabled #A5ADBA Disabled inputs, placeholder text

Semantic / Status

Token Hex Usage
vetrix-success #00875A Success text, icons
vetrix-success-light #E3FCEF Success badge/alert background
vetrix-warning #FF991F Warning text, icons
vetrix-warning-light #FFFAE6 Warning badge/alert background, impersonation banner
vetrix-danger #DE350B Error text, destructive button fill, overdue indicators
vetrix-danger-light #FFEBE6 Danger badge/alert background

Typography

Font Families

Sans (default):  -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif
Mono:            ui-monospace, SFMono-Regular, "SF Mono", monospace

Apply mono to clone URLs, commit SHAs, code snippets, and diff content via font-mono.

Text Scale

Class Size Weight Usage
text-xs 12px varies Labels, badges, timestamps, table column headers
text-sm 14px normal Body text, table rows, nav items, form fields
text-base 16px normal Page body prose (rare)
text-lg 18px font-semibold Section headings (<h1> at page level)

Heading pattern: text-lg font-semibold text-vetrix-text-primary
Sub-heading pattern: text-sm font-semibold text-vetrix-text-primary
Label pattern: text-xs font-medium text-vetrix-text-primary (form labels) or text-xs font-semibold text-vetrix-text-secondary uppercase tracking-wide (group labels)


Spacing

Vetrix uses Tailwind's default spacing scale (1 unit = 4px). Standard values in use:

Value px Common context
0.5 2px Tight badge padding
1 4px Icon-to-icon gaps
1.5 6px Button vertical padding (sm), nav item vertical padding
2 8px Standard gap between items in a row
3 12px Horizontal padding in table cells, card padding (compact)
4 16px Standard page padding (p-4), horizontal nav padding
6 24px Content-area padding (p-6) in settings/dashboard
8 32px Large empty-state padding
12 48px Loading/empty state tall padding

Breakpoints

Vetrix uses Tailwind's default breakpoint values, with two custom breakpoints added (xs and nav-break):

Name Min-width Design intent
(none) 0px Mobile — single column, most secondary content hidden
xs: 480px Large phone landscape — between narrow mobile and small tablet
sm: 640px Small tablet — show secondary labels, hints, table columns
md: 768px Tablet — show tertiary table columns (dates/times), CI/CD nav link
nav-break: 800px Controls TopNav's search box width/visibility — below it the search box drops to a full-width row, at/above it search renders inline (300px) (TopNav.tsx's SearchForm, nav-break:block/nav-break:hidden)
lg: 1024px Desktop — show all content, full sidebar layout
xl: 1280px Wide — content area max-width cap (max-w-screen-xl)
2xl: 1536px Ultra-wide — not currently used

Responsive strategy

Vetrix follows mobile-first. Start with the mobile layout and add sm:/md:/lg: prefixes to expand. Prefer hidden sm:block over block sm:hidden for progressive disclosure.

Common progressive disclosure patterns:

Columns (table):     hidden sm:table-cell      (show at 640px+)
                     hidden md:table-cell      (show at 768px+)
                     hidden lg:table-cell      (show at 1024px+)
Nav labels:          hidden sm:block           (show text at 640px+)
Nav items:           hidden md:block           (show link at 768px+)

Layout System

Shell structure

<html>
  <body bg-vetrix-canvas>
    <AppShell>
      <TopNav />             fixed h-12 z-40 top-0 full-width
      <div flex-1 pt-12>    offset for fixed nav
        <Sidebar />          shrink-0 border-r  (48px collapsed / 224px expanded)
        <main flex-1>        overflow-auto bg-vetrix-canvas
          <page content>     p-4 (standard) or p-6 (settings/dashboard)
        </main>
      </div>
      <Footer />             h-10 border-t shrink-0
    </AppShell>
  </body>
</html>

Page container

Most pages use p-4 as the root container. Settings and dashboard use p-6. There is currently no global max-width on the content area — pages expand to fill all available horizontal space.

Sidebar widths

Context Collapsed Expanded
Main repo/settings sidebar w-12 (48px) w-56 (224px)
Dashboard repo list 280px (inline style)
Admin sidebar w-52 (208px)

Component Patterns

Buttons

Base:       inline-flex items-center justify-center gap-1.5 rounded font-medium transition-colors
sm size:    h-7 px-3 text-xs
md size:    h-8 px-4 text-sm

Variants:
  primary    bg-vetrix-primary text-white hover:bg-vetrix-primary-hover
  secondary  border border-vetrix-border bg-vetrix-surface text-vetrix-text-primary hover:bg-vetrix-surface-hover
  danger     bg-vetrix-danger text-white hover:bg-red-700
  ghost      text-vetrix-text-secondary hover:bg-vetrix-surface-hover hover:text-vetrix-text-primary

Form inputs

Container:  flex flex-col gap-1
Label:      text-xs font-medium text-vetrix-text-primary
Input:      h-8 w-full rounded border border-vetrix-border px-3 text-sm bg-vetrix-surface
            focus:ring-2 focus:ring-vetrix-primary focus:border-transparent
            disabled:bg-vetrix-canvas disabled:text-vetrix-text-disabled
Error text: text-xs text-vetrix-danger mt-0.5
Hint text:  text-xs text-vetrix-text-secondary mt-0.5

Cards / panels

Standard:   rounded border border-vetrix-border bg-vetrix-surface p-4
Compact:    rounded border border-vetrix-border bg-vetrix-surface p-3
Sectioned:  rounded border border-vetrix-border bg-vetrix-surface
            ├─ header: border-b px-4 py-3 bg-vetrix-canvas
            └─ body:   p-4

Tables

Outer:      overflow-hidden rounded border border-vetrix-border bg-vetrix-surface
Table:      w-full border-collapse text-sm
TH row:     border-b border-vetrix-border bg-vetrix-canvas
TH cell:    px-3 py-2 text-left text-xs font-semibold text-vetrix-text-secondary uppercase tracking-wide
TR:         border-b border-vetrix-border last:border-0 hover:bg-vetrix-surface-hover
TD cell:    px-3 py-2 text-sm text-vetrix-text-primary

For scrollable tables (wide content): wrap table in overflow-x-auto.

Badges

Base:       inline-flex items-center rounded font-medium
sm size:    text-xs px-1.5 py-0.5
md size:    text-sm px-2 py-0.5

Variants:
  success   bg-vetrix-success-light text-vetrix-success
  warning   bg-vetrix-warning-light text-vetrix-warning
  danger    bg-vetrix-danger-light  text-vetrix-danger
  info      bg-vetrix-primary-light text-vetrix-primary
  neutral   bg-vetrix-canvas        text-vetrix-text-secondary

Modals

Backdrop:   fixed inset-0 bg-black/30
Positioner: fixed inset-0 flex items-center justify-center p-4
Dialog:     w-full rounded-lg bg-vetrix-surface shadow-xl
            + max-w-sm / max-w-md (default) / max-w-lg / max-w-2xl
Header:     flex items-center justify-between border-b px-4 py-3
Title:      text-sm font-semibold text-vetrix-text-primary
Body:       p-4
Footer:     flex justify-end gap-2 border-t px-4 py-3

Empty states

Container:  flex flex-col items-center justify-center py-12 text-center
Icon:       text-4xl or h-12 w-12 text-vetrix-text-secondary mb-4
Heading:    text-sm font-semibold text-vetrix-text-primary
Description: text-sm text-vetrix-text-secondary max-w-sm mt-1
Action:     mt-4 (Button or Link)

Icon Usage

Vetrix uses Heroicons (@heroicons/react).

  • 20/solid — for inline icons inside buttons, badges, table cells, and compact UI
  • 24/outline — for nav icons, notification bell, and any standalone icon larger than a button

Size reference: h-3.5 w-3.5 (button icons) · h-4 w-4 (standard) · h-5 w-5 (nav/prominent) · h-6 w-6 (logo/avatar)

Always add aria-hidden="true" on decorative icons.


Z-Index Scale

Value Context
z-10 Dropdowns and popovers
z-20 Select/Combobox dropdown lists
z-40 TopNav fixed header
z-50 Modals and overlays

Animation / Transitions

Vetrix uses Tailwind's built-in transition-colors for interactive hover states and transition-all duration-200 for the sidebar collapse animation. No custom keyframe animations are defined. Use these sparingly and consistently.


Server Components vs Client Components

Decision Flowchart

Use a Server Component (default — no 'use client') when the component:

  • Fetches data and renders it (read-heavy pages)
  • Has no user interactivity (no useState, useEffect, event handlers)
  • Does not use browser-only APIs (window, document, localStorage)

Use a Client Component ('use client') when the component:

  • Manages interactive state (useState, useReducer)
  • Uses effects or refs (useEffect, useRef)
  • Handles user events (onClick, onChange, onSubmit)
  • Depends on browser APIs or React context (useContext, useQuery)

Architecture Pattern

page.tsx (Server Component — async, fetches data)
  └── Suspense boundary (streaming fallback)
       └── PageContent.tsx ('use client' — receives data as props)
            └── Interactive children (forms, dropdowns, pagination)

Server-Side Data Fetching

Use serverFetch() from @/lib/api/server for data in Server Components:

import { serverFetch } from '@/lib/api/server'
const data = await serverFetch<User>(`/api/v1/users/${owner}`)
if (!data) notFound()

Passing Server Data to Client Components

Use initialData in useQuery for reactive updates after server-side fetch:

// Server Component
const data = await serverFetch<Items>('/api/v1/items?page=1')
return <ItemList initialData={data} />

// Client Component
const { data } = useQuery({
  queryKey: ['items', page],
  queryFn: () => api.list({ page }),
  initialData: page === 1 ? initialData : undefined,
})

Rules

  1. Server Components cannot use hooks — no useState, useEffect, useQuery.
  2. Server Components CAN import Client Components (AppShell, Button, etc.).
  3. Client Components cannot import Server Components — but can receive them as children.
  4. AppShell stays client-side — it uses auth hooks and notification polling.
  5. Suspense boundaries are required around async Server Components for streaming HTML.
  6. Use await in Server Components; use() in Client Components for promise resolution.