Vira Design System
Clean, minimal CI/CD application design system emphasizing clarity and functionality.
Design Principles
- KISS (Keep It Simple): Minimal, focused design without unnecessary decoration
- Flat Design: Clean layouts with selective elevation for hierarchy
- Technical Clarity: Clear visual communication over visual flourishes
Color Palette
Primary Colors
- Brand accent:
brand-600 (primary actions), brand-700 (hover states). The brand-* scale is declared in packages/vira/src/style.css as a @theme alias to Tailwind's cyan today — change those var(--color-cyan-*) aliases to shift the brand without touching widget code.
- Background:
gray-50 (page), white (cards), gray-200 (dividers)
- Dark mode:
gray-900 (page), gray-800 (cards), gray-700 (dividers)
Semantic Colors
- Success:
green-600 (success actions)
- Destructive:
red-600 (delete/dangerous actions)
- Warning:
yellow-500 (cautionary states)
- Secondary:
gray-100 (secondary actions)
- Dark mode:
gray-700 (secondary actions)
Neutrals
- Text:
gray-900 (primary), gray-600 (secondary), gray-500 (muted)
- Dark mode:
gray-100 (primary), gray-300 (secondary), gray-400 (muted)
- Borders:
gray-200 (default), gray-300 (interactive)
- Dark mode:
gray-700 (default), gray-600 (interactive)
- Backgrounds:
gray-50 (sections), gray-100 (subtle contrast)
- Dark mode:
gray-800 (sections), gray-700 (subtle contrast)
Typography
Font Families
- Sans-serif: Geist (variable font, 300-700 weights), system-ui fallbacks
- Monospace: Geist Mono (for logs, code, and technical values)
Tokens are declared as --font-sans / --font-mono in packages/vira/src/style.css (the Tailwind v4 input file). Generated static/tailwind.css exposes them via font-sans / font-mono utility classes — prefer those over hard-coded font-family declarations.
Size Scale
| Class |
Size |
Usage |
text-xs |
12px |
Badges, table headers (uppercase), tertiary metadata |
text-sm |
14px |
Body text, form labels, buttons, timestamps |
text-base |
16px |
Breadcrumbs, emphasized body content |
text-lg |
18px |
Card subheadings, section descriptions |
text-xl |
20px |
Section headers (h2, h3) |
text-2xl |
24px |
Page headers (h1) |
Weight Guidelines
| Weight |
Class |
Usage |
| 500 |
font-medium |
Badges, subtle emphasis, secondary labels |
| 600 |
font-semibold |
Buttons, tab labels, highlighted text |
| 700 |
font-bold |
All headings (h1-h3), important values |
Patterns
-- Page headers (h1)
h1_ [class_ "text-2xl font-bold text-gray-900 dark:text-gray-100"]
-- Section headers (h2)
h2_ [class_ "text-xl font-bold text-gray-900 dark:text-gray-100 mb-4"]
-- Buttons
button_ [class_ "text-sm font-semibold"]
-- Badges
span_ [class_ "text-xs font-medium"]
-- Table headers
th_ [class_ "text-xs font-medium uppercase tracking-wider"]
Spacing
Scale
| Class |
Size |
Common Usage |
1 |
4px |
Icon margins, tight gaps |
2 |
8px |
Badge padding, inline spacing |
3 |
12px |
Button padding (vertical) |
4 |
16px |
Card content margins, section gaps |
6 |
24px |
Section spacing, card padding |
8 |
32px |
Page section dividers |
Component Patterns
| Component |
Pattern |
| Cards |
p-6 (content), mb-6 (between cards) |
| Buttons |
px-6 py-3 (primary), px-4 py-2 (secondary) |
| Badges |
px-2 py-1 or px-3 py-1 |
| Form groups |
space-y-6 (between groups), mb-2 (label to input) |
| Page sections |
space-y-6 or mb-8 |
| Inline items |
space-x-2 (tight), space-x-4 (normal), gap-4 (flex) |
Border Radius
| Class |
Usage |
rounded |
Small elements, code blocks |
rounded-lg |
Default - buttons, cards, inputs |
rounded-full |
Badges, pills, avatars |
rounded-xl |
Large feature cards, modals |
Components
All components are defined in src/Vira/Widgets/*.hs with full documentation and usage examples.
Core Components
- Buttons:
viraButton_, viraButtonIcon_ (see Button.hs)
- Cards:
viraCard_ (flat), viraCardElevated_ (with shadow) (see Card.hs)
- Layout:
viraSection_, viraPageHeader_, viraDivider_ (see Layout.hs)
- Forms:
viraInput_, viraLabel_ (see Form.hs)
- Status:
viraStatusBadge_ (see Status.hs)
- Alerts:
viraAlert_ (see Alert.hs)
Usage
import Vira.Widgets qualified as W
W.viraButton_ W.ButtonPrimary [] $ do
W.viraButtonIcon_ $ toHtmlRaw Icon.plus
"Add Repository"
Icons
- Library: Tabler Icons (
Web.TablerIcons.Outline)
- Standard sizes:
w-4 h-4 (buttons), w-5 h-5 (UI), w-6 h-6 (headers), w-8 h-8 (sections), w-16 h-16 (hero)
- Centering: Always use
flex items-center justify-center
Visual Hierarchy
- Elevation: Flat by default,
shadow-sm only for important content
- Spacing: Consistent spacing scale using Tailwind (
space-y-*)
- Typography: Clear text hierarchy with
text-3xl, text-lg, etc.
- Color: Semantic colors communicate meaning, not decoration
Layout
- Responsive: Mobile-first with
md:, lg:, xl: breakpoints
- Grid:
grid gap-6 md:grid-cols-2 lg:grid-cols-3
- Container:
container mx-auto with responsive padding
Accessibility Guidelines
Color Contrast
- Primary text:
text-gray-900 (high contrast)
- Secondary text:
text-gray-600 (medium contrast)
- Muted text:
text-gray-500 (minimum contrast)
- Avoid:
text-gray-400 (insufficient contrast)
Interactive Elements
- Focus states: Clear ring indicators for keyboard navigation
- Button text: High contrast white on colored backgrounds
- Icon colors: Use
text-gray-500 minimum for functional icons
Small Text Guidelines
- Extra small text (
text-xs): Use text-gray-600 minimum
- Regular text: Can use
text-gray-500 for secondary information
- Interactive labels: Always use
text-gray-600 or darker
Dark Mode
Vira implements automatic dark mode using Tailwind's @media (prefers-color-scheme: dark) strategy. Dark mode activates based on system preferences without any JavaScript or configuration.
Dark Mode Guidelines
- Always add dark variants - When using gray colors, always include
dark: variants
- Use semantic opacity - Use opacity for tinted backgrounds (e.g.,
dark:bg-red-900/20)
- Maintain contrast - Ensure text remains readable in both modes
- Test both modes - Always verify styling in light and dark modes
Common Dark Mode Patterns
-- Backgrounds
"bg-white dark:bg-gray-800"
"bg-gray-50 dark:bg-gray-900"
"bg-gray-100 dark:bg-gray-700"
-- Borders
"border-gray-200 dark:border-gray-700"
"border-gray-300 dark:border-gray-600"
-- Text
"text-gray-900 dark:text-gray-100"
"text-gray-600 dark:text-gray-300"
"text-gray-500 dark:text-gray-400"
-- Semantic colors with opacity
"bg-red-50 dark:bg-red-900/20"
"bg-blue-50 dark:bg-blue-900/20"
Guidelines
- KISS Principle - Avoid unnecessary abstractions and complexity
- Accessibility First - Follow WCAG contrast requirements
- Dark Mode - Always include
dark: variants for gray colors
- Use existing widgets - Check
Widgets/*.hs first
- Follow naming -
vira[ComponentName]_ pattern
- Accept attributes -
[Attributes] parameter for extensibility
- Minimal shadows - Only use elevation when creating meaningful hierarchy
- No gradients - Prefer solid colors for clarity and performance
Refer to src/Vira/Widgets/*.hs for complete component documentation and implementation details.
1---2name: vira-design3description: Vira's design system — TailwindCSS conventions, color palette, typography, spacing, components, and accessibility rules. Use when working on UI components, styling, or layout in this project (Lucid HTML in Haskell sources under packages/vira).4---56# Vira Design System78Clean, minimal CI/CD application design system emphasizing clarity and functionality.910## Design Principles1112- **KISS (Keep It Simple)**: Minimal, focused design without unnecessary decoration13- **Flat Design**: Clean layouts with selective elevation for hierarchy14- **Technical Clarity**: Clear visual communication over visual flourishes1516## Color Palette1718### Primary Colors1920- **Brand accent**: `brand-600` (primary actions), `brand-700` (hover states). The `brand-*` scale is declared in `packages/vira/src/style.css` as a `@theme` alias to Tailwind's cyan today — change those `var(--color-cyan-*)` aliases to shift the brand without touching widget code.21- **Background**: `gray-50` (page), `white` (cards), `gray-200` (dividers)22 - **Dark mode**: `gray-900` (page), `gray-800` (cards), `gray-700` (dividers)2324### Semantic Colors2526- **Success**: `green-600` (success actions)27- **Destructive**: `red-600` (delete/dangerous actions)28- **Warning**: `yellow-500` (cautionary states)29- **Secondary**: `gray-100` (secondary actions)30 - **Dark mode**: `gray-700` (secondary actions)3132### Neutrals3334- **Text**: `gray-900` (primary), `gray-600` (secondary), `gray-500` (muted)35 - **Dark mode**: `gray-100` (primary), `gray-300` (secondary), `gray-400` (muted)36- **Borders**: `gray-200` (default), `gray-300` (interactive)37 - **Dark mode**: `gray-700` (default), `gray-600` (interactive)38- **Backgrounds**: `gray-50` (sections), `gray-100` (subtle contrast)39 - **Dark mode**: `gray-800` (sections), `gray-700` (subtle contrast)4041## Typography4243### Font Families4445- **Sans-serif**: Geist (variable font, 300-700 weights), system-ui fallbacks46- **Monospace**: Geist Mono (for logs, code, and technical values)4748Tokens are declared as `--font-sans` / `--font-mono` in `packages/vira/src/style.css` (the Tailwind v4 input file). Generated `static/tailwind.css` exposes them via `font-sans` / `font-mono` utility classes — prefer those over hard-coded `font-family` declarations.4950### Size Scale5152| Class | Size | Usage |53| ----------- | ---- | ---------------------------------------------------- |54| `text-xs` | 12px | Badges, table headers (uppercase), tertiary metadata |55| `text-sm` | 14px | Body text, form labels, buttons, timestamps |56| `text-base` | 16px | Breadcrumbs, emphasized body content |57| `text-lg` | 18px | Card subheadings, section descriptions |58| `text-xl` | 20px | Section headers (h2, h3) |59| `text-2xl` | 24px | Page headers (h1) |6061### Weight Guidelines6263| Weight | Class | Usage |64| ------ | --------------- | ----------------------------------------- |65| 500 | `font-medium` | Badges, subtle emphasis, secondary labels |66| 600 | `font-semibold` | Buttons, tab labels, highlighted text |67| 700 | `font-bold` | All headings (h1-h3), important values |6869### Patterns7071```haskell72-- Page headers (h1)73h1_ [class_ "text-2xl font-bold text-gray-900 dark:text-gray-100"]7475-- Section headers (h2)76h2_ [class_ "text-xl font-bold text-gray-900 dark:text-gray-100 mb-4"]7778-- Buttons79button_ [class_ "text-sm font-semibold"]8081-- Badges82span_ [class_ "text-xs font-medium"]8384-- Table headers85th_ [class_ "text-xs font-medium uppercase tracking-wider"]86```8788## Spacing8990### Scale9192| Class | Size | Common Usage |93| ----- | ---- | ---------------------------------- |94| `1` | 4px | Icon margins, tight gaps |95| `2` | 8px | Badge padding, inline spacing |96| `3` | 12px | Button padding (vertical) |97| `4` | 16px | Card content margins, section gaps |98| `6` | 24px | Section spacing, card padding |99| `8` | 32px | Page section dividers |100101### Component Patterns102103| Component | Pattern |104| ----------------- | --------------------------------------------------------- |105| **Cards** | `p-6` (content), `mb-6` (between cards) |106| **Buttons** | `px-6 py-3` (primary), `px-4 py-2` (secondary) |107| **Badges** | `px-2 py-1` or `px-3 py-1` |108| **Form groups** | `space-y-6` (between groups), `mb-2` (label to input) |109| **Page sections** | `space-y-6` or `mb-8` |110| **Inline items** | `space-x-2` (tight), `space-x-4` (normal), `gap-4` (flex) |111112### Border Radius113114| Class | Usage |115| -------------- | ------------------------------------ |116| `rounded` | Small elements, code blocks |117| `rounded-lg` | **Default** - buttons, cards, inputs |118| `rounded-full` | Badges, pills, avatars |119| `rounded-xl` | Large feature cards, modals |120121## Components122123**All components are defined in `src/Vira/Widgets/*.hs` with full documentation and usage examples.**124125### Core Components126127- **Buttons**: `viraButton_`, `viraButtonIcon_` (see `Button.hs`)128- **Cards**: `viraCard_` (flat), `viraCardElevated_` (with shadow) (see `Card.hs`)129- **Layout**: `viraSection_`, `viraPageHeader_`, `viraDivider_` (see `Layout.hs`)130- **Forms**: `viraInput_`, `viraLabel_` (see `Form.hs`)131- **Status**: `viraStatusBadge_` (see `Status.hs`)132- **Alerts**: `viraAlert_` (see `Alert.hs`)133134### Usage135136```haskell137import Vira.Widgets qualified as W138139W.viraButton_ W.ButtonPrimary [] $ do140 W.viraButtonIcon_ $ toHtmlRaw Icon.plus141 "Add Repository"142```143144## Icons145146- **Library**: Tabler Icons (`Web.TablerIcons.Outline`)147- **Standard sizes**: `w-4 h-4` (buttons), `w-5 h-5` (UI), `w-6 h-6` (headers), `w-8 h-8` (sections), `w-16 h-16` (hero)148- **Centering**: Always use `flex items-center justify-center`149150## Visual Hierarchy151152- **Elevation**: Flat by default, `shadow-sm` only for important content153- **Spacing**: Consistent spacing scale using Tailwind (`space-y-*`)154- **Typography**: Clear text hierarchy with `text-3xl`, `text-lg`, etc.155- **Color**: Semantic colors communicate meaning, not decoration156157## Layout158159- **Responsive**: Mobile-first with `md:`, `lg:`, `xl:` breakpoints160- **Grid**: `grid gap-6 md:grid-cols-2 lg:grid-cols-3`161- **Container**: `container mx-auto` with responsive padding162163## Accessibility Guidelines164165### Color Contrast166167- **Primary text**: `text-gray-900` (high contrast)168- **Secondary text**: `text-gray-600` (medium contrast)169- **Muted text**: `text-gray-500` (minimum contrast)170- **Avoid**: `text-gray-400` (insufficient contrast)171172### Interactive Elements173174- **Focus states**: Clear ring indicators for keyboard navigation175- **Button text**: High contrast white on colored backgrounds176- **Icon colors**: Use `text-gray-500` minimum for functional icons177178### Small Text Guidelines179180- **Extra small text** (`text-xs`): Use `text-gray-600` minimum181- **Regular text**: Can use `text-gray-500` for secondary information182- **Interactive labels**: Always use `text-gray-600` or darker183184## Dark Mode185186Vira implements automatic dark mode using Tailwind's `@media (prefers-color-scheme: dark)` strategy. Dark mode activates based on system preferences without any JavaScript or configuration.187188### Dark Mode Guidelines1891901. **Always add dark variants** - When using gray colors, always include `dark:` variants1912. **Use semantic opacity** - Use opacity for tinted backgrounds (e.g., `dark:bg-red-900/20`)1923. **Maintain contrast** - Ensure text remains readable in both modes1934. **Test both modes** - Always verify styling in light and dark modes194195### Common Dark Mode Patterns196197```haskell198-- Backgrounds199"bg-white dark:bg-gray-800"200"bg-gray-50 dark:bg-gray-900"201"bg-gray-100 dark:bg-gray-700"202203-- Borders204"border-gray-200 dark:border-gray-700"205"border-gray-300 dark:border-gray-600"206207-- Text208"text-gray-900 dark:text-gray-100"209"text-gray-600 dark:text-gray-300"210"text-gray-500 dark:text-gray-400"211212-- Semantic colors with opacity213"bg-red-50 dark:bg-red-900/20"214"bg-blue-50 dark:bg-blue-900/20"215```216217## Guidelines2182191. **KISS Principle** - Avoid unnecessary abstractions and complexity2202. **Accessibility First** - Follow WCAG contrast requirements2213. **Dark Mode** - Always include `dark:` variants for gray colors2224. **Use existing widgets** - Check `Widgets/*.hs` first2235. **Follow naming** - `vira[ComponentName]_` pattern2246. **Accept attributes** - `[Attributes]` parameter for extensibility2257. **Minimal shadows** - Only use elevation when creating meaningful hierarchy2268. **No gradients** - Prefer solid colors for clarity and performance227228---229230**Refer to `src/Vira/Widgets/*.hs` for complete component documentation and implementation details.**