juan-all-hands's picture
Add OpenHands design assets from kosmonautical/openhands-index-paul
cd40c70 verified
# OpenHands UI Design System
## 1. Visual Theme & Atmosphere
OpenHands is a dark-first AI agent platform built on a near-black monochrome canvas. The entire experience lives on a `0 0% 5%` HSL background β€” effectively `#0d0d0d` β€” with `0 0% 98%` foreground text that reads as warm off-white. Every surface is a shade of neutral grey scaled in 2–5% lightness increments, creating depth through tonal variation rather than color. The only chromatic moments are semantic: green for success, red-orange for danger, amber for warnings, and blue for informational states.
Typography is carried by **Inter** (sans-serif) for all UI text and **JetBrains Mono** for code, terminals, and technical labels. The type system is weight-restrained β€” `font-medium` (500) is the workhorse, `font-semibold` (600) for headings and emphasis, and `font-normal` (400) for body. Bold (700) is rare and reserved for maximum emphasis.
The UI framework is **React + Tailwind CSS + Radix primitives** (shadcn/ui pattern). All colors flow through CSS custom properties declared in `:root` and consumed via `hsl(var(--token))` in the Tailwind config. This means every color in the system is overridable by changing a single HSL triplet.
**Key characteristics:**
- Near-black monochrome canvas (`#0d0d0d` background, `#fafafa` foreground)
- Neutral grey surface scale in 2–5% lightness increments (5% β†’ 7% β†’ 8% β†’ 12% β†’ 14% β†’ 18%)
- Inter + JetBrains Mono dual-font system
- HSL-based CSS custom property architecture for full theme overridability
- Tailwind utility-first styling with Radix UI headless primitives
- `transition-colors` as the dominant transition (958 uses) β€” UI feels responsive but not animated
- Dark-only primary mode; light and sepia modes exist as secondary via class-map theming
---
## 2. Color Palette & Roles
All colors are declared as HSL triplets (without the `hsl()` wrapper) in CSS custom properties. Tailwind maps them as `hsl(var(--token))`.
### Core Surfaces
| Token | HSL | Hex | Role |
|-------|-----|-----|------|
| `--background` | `0 0% 5%` | `#0d0d0d` | Page background, app shell |
| `--card` | `0 0% 7%` | `#121212` | Card surfaces, elevated containers |
| `--secondary` | `0 0% 8%` | `#141414` | Secondary surfaces, sidebar accent |
| `--popover` | `0 0% 7%` | `#121212` | Dropdown menus, popovers |
| `--muted` | `0 0% 12%` | `#1f1f1f` | Muted backgrounds, hover fills, badges, **tooltip surfaces** |
| `--border` / `--input` | `0 0% 14%` | `#242424` | Borders, input borders, dividers |
| `--muted-hover` | `0 0% 18%` | `#2e2e2e` | Hover state for muted surfaces |
| `--modal-background` | Inherits `--background` | `#0d0d0d` | Dialogs, sheets, modals (can diverge) |
### Core Text
| Token | HSL | Hex | Role |
|-------|-----|-----|------|
| `--foreground` | `0 0% 98%` | `#fafafa` | Primary text, headings |
| `--muted-foreground` | `0 0% 55%` | `#8c8c8c` | Secondary text, labels, placeholders, icons |
| `--primary` | `0 0% 100%` | `#ffffff` | Maximum emphasis text, primary buttons |
| `--primary-foreground` | `0 0% 0%` | `#000000` | Text on primary (white) surfaces |
| `--accent` | `0 0% 100%` | `#ffffff` | Accent elements (matches primary in dark) |
### Sidebar (inherits core but isolated for overridability)
| Token | HSL | Role |
|-------|-----|------|
| `--sidebar-background` | `0 0% 5%` | Sidebar background |
| `--sidebar-foreground` | `0 0% 98%` | Sidebar text |
| `--sidebar-accent` | `0 0% 8%` | Sidebar hover/active background |
| `--sidebar-border` | `0 0% 14%` | Sidebar dividers |
| `--sidebar-ring` | `0 0% 50%` | Sidebar focus ring |
### Semantic / Status
| Token | HSL | Hex | Role |
|-------|-----|-----|------|
| `--success` | `142 71% 45%` | `#22c55e` | Success states, running indicators |
| `--success-foreground` | `142 71% 76%` | `#86efac` | Success text on dark surfaces |
| `--warning` | `38 92% 50%` | `#f59e0b` | Warning states, caution badges |
| `--info` | `217 91% 60%` | `#3b82f6` | Informational states, links |
| `--destructive` | `0 72% 51%` | `#dc2626` | Error states, danger actions, delete |
| `--destructive-foreground` | `0 0% 98%` | `#fafafa` | Text on destructive surfaces |
| `--ring` | `0 0% 80%` | `#cccccc` | Focus rings (1px, keyboard-only via `focus-visible:`) |
### Gradients & Decorative
| Token | Value | Role |
|-------|-------|------|
| `--gradient-card-hover` | `linear-gradient(180deg, hsl(0 0% 9%) 0%, hsl(0 0% 7%) 100%)` | Subtle card hover gradient |
| `--shadow-card` | `0 1px 2px 0 hsl(0 0% 0% / 0.3)` | Default card shadow |
### Hover Backgrounds
| Surface | Hover Token | Use |
|---------|-------------|-----|
| Dark surfaces (cards, nav items, menus, rows) | `hover:bg-muted/60` | **Standard hover** β€” the single canonical dark-surface hover |
| White/primary buttons | `hover:bg-primary/85` | Light grey hover on white buttons (85% opacity white) |
**Canonical dark-surface hover: `hover:bg-muted/60`** β€” used consistently across the codebase. Do **not** mix `/40`, `/50`, `/70` variants.
**Canonical primary-button hover: `hover:bg-primary/85`** β€” never use `hover:bg-muted/60` on a `bg-primary`/`bg-white` button (causes dark flash).
---
## 3. Typography Rules
### Font Families
| Role | Family | CSS Variable | Tailwind Class | Fallbacks |
|------|--------|-------------|----------------|-----------|
| UI / Body | Inter | `--font-sans` | `font-sans` | `system-ui, sans-serif` |
| Code / Technical | JetBrains Mono | `--font-mono` | `font-mono` | `monospace` |
Fonts are loaded via Google Fonts `@import` in `index.css`.
### Type Scale
The app uses Tailwind's default type scale. These are the **canonical sizes** ordered by frequency of use:
| Tailwind Class | Size | Uses | Role |
|----------------|------|------|------|
| `text-sm` | 14px / 0.875rem | 711 | **Primary body text**, labels, button text, descriptions |
| `text-xs` | 12px / 0.75rem | 427 | **Secondary text**, metadata, badges, menu items, captions |
| `text-base` | 16px / 1rem | 52 | Larger body text, input text, chat messages |
| `text-lg` | 18px / 1.125rem | 67 | Section sub-headings, dialog titles |
| `text-xl` | 20px / 1.25rem | 28 | Page sub-headings |
| `text-2xl` | 24px / 1.5rem | 31 | Page headings, modal titles |
| `text-3xl` | 30px / 1.875rem | 12 | Hero headings, landing sections |
### Arbitrary Font Sizes (to normalize)
These arbitrary sizes appear frequently and should be migrated to the standard scale or formalized as tokens:
| Arbitrary | Count | Recommended Replacement |
|-----------|-------|------------------------|
| `text-[11px]` | 46 | `text-xs` (12px) β€” or formalize as `--text-2xs` if 11px is intentional |
| `text-[10px]` | 20 | `text-xs` (12px) β€” or formalize as `--text-2xs` |
| `text-[12px]` | 8 | `text-xs` (already 12px β€” use the utility) |
| `text-[40px]` | 5 | `text-4xl` (36px) or formalize as hero display size |
| `text-[28px]` | 3 | `text-3xl` (30px) or formalize |
| `text-[32px]` | 1 | `text-3xl` (30px) or `text-4xl` (36px) |
| `text-[8px]` | 1 | Likely a micro label β€” evaluate if needed |
### Font Weight Scale
| Tailwind Class | Weight | Uses | Role |
|----------------|--------|------|------|
| `font-medium` | 500 | 304 | Labels, nav items, badges (note: buttons use `font-normal`) |
| `font-semibold` | 600 | 229 | **Headings**, section titles, strong emphasis |
| `font-normal` | 400 | 106 | **Body text**, descriptions, long-form content |
| `font-bold` | 700 | 29 | Maximum emphasis (use sparingly) |
| `font-light` | 300 | 13 | De-emphasized text (use sparingly) |
### Line Height
| Tailwind Class | Uses | Role |
|----------------|------|------|
| `leading-4` | 38 | Tight β€” compact UI, badges |
| `leading-6` | 34 | Standard β€” body text |
| `leading-relaxed` | 33 | Comfortable β€” long-form, descriptions |
| `leading-5` | 28 | Medium β€” labels, short text |
| `leading-tight` | 27 | Condensed β€” headings |
| `leading-snug` | 17 | Slightly condensed |
| `leading-none` | 16 | No leading β€” single-line elements |
### Letter Spacing
| Tailwind Class | Uses | Role |
|----------------|------|------|
| `tracking-wide` | 28 | Uppercase labels, section headers |
| `tracking-wider` | 20 | Small-caps metadata |
| `tracking-tight` | 19 | Display headings |
### Canonical Patterns
**Body text:** `text-sm font-normal text-foreground`
**Label:** `text-sm font-medium text-foreground`
**Secondary text:** `text-sm text-muted-foreground`
**Metadata/caption:** `text-xs text-muted-foreground`
**Uppercase category:** `text-[11px] font-medium uppercase tracking-wide text-muted-foreground`
**Heading (page):** `text-2xl font-semibold text-foreground`
**Heading (section):** `text-lg font-semibold text-foreground`
**Code/mono:** `text-sm font-mono`
---
## 4. Component Stylings
### Buttons (`Button` component β€” `src/components/ui/button.tsx`)
**Base classes (all variants):**
`inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-normal ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 active:scale-[0.97] [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0`
| Variant | Background | Text | Border | Hover | Use |
|---------|-----------|------|--------|-------|-----|
| `default` | `bg-primary` | `text-primary-foreground` | β€” | `hover:bg-primary/85` | Primary CTA (white button, black text) |
| `destructive` | `bg-destructive` | `text-destructive-foreground` | β€” | `hover:bg-destructive/85` | Delete, danger actions |
| `outline` | `bg-background` | β€” | `border border-input` | `hover:bg-muted hover:text-foreground` | Secondary actions (most used β€” 53 instances) |
| `light` | `bg-primary` | `text-primary-foreground` | `border border-input` | `hover:bg-primary/85` | High-contrast primary on dark bg (token-based, no raw `bg-white`) |
| `secondary` | `bg-secondary` | `text-secondary-foreground` | β€” | `hover:bg-muted-hover` | Tertiary actions |
| `muted` | `bg-muted` | `text-muted-foreground` | β€” | `hover:bg-muted-hover hover:text-foreground` | Subdued actions |
| `ghost` | transparent | β€” | β€” | `hover:bg-muted hover:text-foreground` | Minimal chrome actions |
| `link` | transparent | `text-primary underline-offset-4` | β€” | `hover:underline` | Inline links |
**Primary button convention:** All white/primary buttons use `bg-primary text-primary-foreground hover:bg-primary/85`. Never use `bg-white text-black hover:bg-muted/60` inline β€” the dark hover on a white button is incorrect. Use the `Button` component or match its tokens.
| Size | Height | Padding | Font |
|------|--------|---------|------|
| `default` | `h-10` | `px-4 py-2` | `text-sm` |
| `sm` | `h-10` | `px-3` | `text-sm` |
| `xs` | `h-10` | `px-3` | `text-xs` |
| `lg` | `h-11` | `px-8` | `text-sm` |
| `icon` | `h-10 w-10` | β€” | β€” |
### Cards & Containers
There is no dedicated `Card` primitive β€” cards are composed with utilities.
**Standard card recipe:**
```
bg-card border border-border rounded-lg p-4
```
**Elevated card:**
```
bg-card border border-border rounded-xl p-6 shadow-lg
```
**Interactive card:**
```
bg-card border border-border rounded-lg p-4 transition-colors hover:border-white/30
```
**Glass / backdrop card:**
```
bg-card/70 border border-border/60 rounded-lg p-6 shadow-lg backdrop-blur-xl supports-[backdrop-filter]:bg-card/50
```
### Inputs (`Input` component β€” `src/components/ui/input.tsx`)
**Standard input:**
```
h-10 w-full rounded-md border border-border bg-muted/40 px-3 py-2 text-base md:text-sm
ring-offset-background focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring
focus-visible:ring-offset-2 focus-visible:bg-muted/60 hover:bg-muted/60
placeholder:text-muted-foreground
disabled:cursor-not-allowed disabled:opacity-50 disabled:bg-muted/30
```
**Canonical focus style (all inputs, textareas, selects must match):**
```
ring-offset-background focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:bg-muted/60
```
Key rules:
- Always use `focus-visible:` (keyboard-only), never `focus:` (fires on click too)
- Always include `ring-offset-background` and `focus-visible:ring-offset-2`
- Always include `focus-visible:bg-muted/60` for the subtle fill on focus
- Search inputs (`type="search"`) have `appearance: none` in global CSS to strip browser default focus chrome
**Size variants (via SearchInput wrapper):**
- `sm`: `h-9` + `pl-8 pr-8` (icon padding)
- `default`: `h-10` + `pl-9 pr-9`
- `lg`: `h-11` + `pl-10 pr-10`
### Dropdown Menus (`DropdownMenu` β€” Radix-based)
**Menu content:**
```
z-[100] min-w-[8rem] overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md
```
**Menu item:**
```
group relative flex cursor-default select-none items-center rounded-md px-2 py-1.5 text-sm
transition-colors focus:bg-muted/60 data-[highlighted]:bg-muted/60
```
**Icon treatment in menu items:**
- Default: `[&_svg]:text-muted-foreground` (grey)
- Hover/highlight: `group-hover:[&_svg]:!text-foreground` (white)
### Popover
**Content:**
```
z-50 max-h-[min(24rem,calc(100dvh-2rem))] shadow-md rounded-[12px] border border-border
bg-sidebar p-6 text-sidebar-foreground overflow-y-auto
```
### Navigation (LeftNav sidebar)
- Collapsed: 56px wide icon rail
- Expanded: 240px+ with text labels
- Items: `flex items-center gap-2 rounded-md px-3 py-1.5 text-xs transition-colors`
- Icons: `w-4 h-4 text-muted-foreground group-hover:text-white`
- Active: `bg-muted/60 text-foreground`
- Hover: `hover:bg-muted/60 hover:text-white`
### Scrollbar Variants
| Class | Width | Behavior | Use |
|-------|-------|----------|-----|
| `.dropdown-scroll` | 6px thin | Always visible | Menus, popovers |
| `.custom-scrollbar` | 8px thin | Always visible | Chat, main content |
| `.scrollbar-on-hover` | 8px thin | Visible on hover only | Chat threads |
| `.hide-scrollbar` | hidden | Hidden | Horizontal scroll areas |
All scrollbar thumbs: `hsl(var(--muted-foreground) / 0.5)` with hover at `0.7`.
### Tooltips
All tooltips use `bg-muted` for a lighter surface that visually separates from the dark page background.
**Standard tooltip (rounded-md):**
```
whitespace-nowrap rounded-md bg-muted px-2 py-1 text-xs text-foreground shadow-md
```
**Pill tooltip (rounded-full):**
```
bg-muted text-foreground text-xs rounded-full shadow-lg px-3 py-1
```
### Dialog Close Button
The dialog close "Γ—" button has no focus ring (focus ring removed to avoid visual noise on click):
```
absolute right-4 top-4 inline-flex h-7 w-7 items-center justify-center rounded-md opacity-70
ring-offset-background transition-colors hover:opacity-100 hover:bg-muted/60 focus:outline-none
```
---
## 5. Layout Principles
### Spacing System
The app uses Tailwind's default 4px-based spacing scale. These are the most common values by usage:
**Gaps (flex/grid):**
| Class | Px | Uses | Context |
|-------|-----|------|---------|
| `gap-2` | 8px | 414 | **Standard gap** β€” between items in rows, icon + label |
| `gap-3` | 12px | 144 | Comfortable gap β€” form groups, card content |
| `gap-4` | 16px | 110 | Generous gap β€” section spacing, grid layouts |
| `gap-1` | 4px | 86 | Tight gap β€” inline badges, compact lists |
| `gap-1.5` | 6px | 59 | Between tight and standard |
| `gap-6` | 24px | 50 | Large gap β€” major sections |
**Padding:**
| Class | Px | Uses | Context |
|-------|-----|------|---------|
| `px-4` | 16px | 216 | **Standard horizontal padding** β€” buttons, cards |
| `px-3` | 12px | 212 | Compact horizontal padding β€” menu items, inputs |
| `py-2` | 8px | 200 | **Standard vertical padding** β€” buttons, rows |
| `px-2` | 8px | 198 | Tight horizontal padding β€” badges, pills |
| `py-1` | 4px | 138 | Compact vertical padding |
| `py-1.5` | 6px | 76 | Slightly more than compact |
| `p-4` | 16px | 89 | Uniform card/container padding |
| `p-6` | 24px | 26 | Generous container/dialog padding |
### Grid & Container
- Max container width: `1400px` (via Tailwind `container` config with `2rem` padding)
- Primary layout: sidebar (56–240px) + main content area
- Settings layout: custom CSS vars for independent nav/main vertical inset
- `--settings-nav-padding-top/bottom`: `2rem`
- `--settings-main-padding-top/bottom`: `2rem`
### Whitespace Philosophy
- **Dense but breathable**: The app uses `text-sm` (14px) as the default with `gap-2` (8px) standard spacing β€” dense enough for a productivity tool, but never cramped.
- **Consistent rhythm**: Sections are separated by `border-t border-border` dividers with `my-3` (12px) vertical margin. No heavy horizontal rules.
- **Surface differentiation over spacing**: Rather than using large whitespace to separate areas, the app uses background color shifts (`bg-background` β†’ `bg-card` β†’ `bg-muted`) to create visual sections.
### Border Radius Scale
Defined via CSS custom properties and Tailwind mapping:
| Token | Value | Tailwind | Uses | Role |
|-------|-------|----------|------|------|
| `--radius` | `0.375rem` (6px) | `rounded-lg` | 112 | **Standard container radius** β€” cards, panels |
| `calc(--radius - 2px)` | `0.25rem` (4px) | `rounded-md` | 501 | **Default element radius** β€” buttons, inputs, menu items |
| `calc(--radius - 4px)` | `0.125rem` (2px) | `rounded-sm` | 12 | Subtle radius β€” small inline elements |
| `--radius-modal` | `0.75rem` (12px) | `rounded-modal` | β€” | Modal/dialog/popover radius |
| β€” | β€” | `rounded-xl` | 90 | Larger cards, featured containers |
| β€” | β€” | `rounded-2xl` | 23 | Hero elements, large cards |
| β€” | β€” | `rounded-full` | 185 | Avatars, pills, circular buttons, badges |
**Arbitrary radii to normalize:**
| Arbitrary | Count | Recommended |
|-----------|-------|-------------|
| `rounded-[6px]` | 20 | `rounded-lg` (already 6px via `--radius`) |
| `rounded-[100px]` | 15 | `rounded-full` (same visual effect) |
| `rounded-[12px]` | 8 | `rounded-modal` or `rounded-xl` (12px) |
| `rounded-[4px]` | 4 | `rounded-md` (already 4px) |
---
## 6. Depth & Elevation
### Shadow Scale
| Tailwind | Uses | Role |
|----------|------|------|
| `shadow-sm` | 21 | Subtle elevation β€” small cards, badges |
| `shadow` | 22 | Default β€” standalone cards |
| `shadow-md` | 31 | Medium β€” dropdown menus, popovers |
| `shadow-lg` | 49 | **Most used** β€” modals, dialogs, elevated panels |
| `shadow-xl` | 14 | High emphasis β€” floating panels |
| `shadow-2xl` | 5 | Maximum β€” overlay dialogs |
| `shadow-inner` | 8 | Inset β€” pressed buttons, input focus |
| `shadow-none` | 19 | Reset β€” flat elements |
### Custom Shadows
| Token | Value | Role |
|-------|-------|------|
| `--shadow-card` | `0 1px 2px 0 hsl(0 0% 0% / 0.3)` | Card resting shadow |
### Elevation Levels
| Level | Treatment | Use |
|-------|-----------|-----|
| 0 β€” Flat | No shadow, `bg-background` | Page background |
| 1 β€” Surface | `bg-card` + `border border-border` | Cards, content panels |
| 2 β€” Raised | `shadow-md` + `border` | Dropdown menus, popovers |
| 3 β€” Floating | `shadow-lg` + `border` | Modals, dialogs, sheets |
| 4 β€” Overlay | `shadow-xl` or `shadow-2xl` | Full-screen overlays, drawers |
### Border System
- **Standard border:** `border border-border` (1px solid `hsl(0 0% 14%)`)
- **Subtle border:** `border border-border/60` (reduced opacity)
- **Interactive hover:** `hover:border-white/30` or `hover:border-muted-foreground/30`
- **Section divider:** `border-t border-border` (horizontal rule) or `border-t border-sidebar-border` (in sidebar)
- **Focus ring:** `ring-1 ring-ring ring-offset-2 ring-offset-background` (1px, `focus-visible:` only)
---
## 7. Do's and Don'ts
### Colors
| Do | Don't |
|----|-------|
| Use `text-foreground` for primary text | Use `text-white` for primary text (278 instances to migrate) |
| Use `text-muted-foreground` for secondary text | Use `text-stone-400` or `text-gray-400` (raw palette) |
| Use `bg-background` for page surfaces | Use `bg-black` or hardcoded `bg-[#0d0d0d]` |
| Use `bg-card` for elevated surfaces | Use `bg-stone-800` or `bg-neutral-900` |
| Use `bg-muted` for subtle backgrounds | Use `bg-stone-700` or `bg-gray-800` |
| Use `border-border` for all borders | Use `border-stone-700` or `border-gray-700` |
| Use `text-success-foreground` for success text | Use `text-emerald-400` or `text-green-400` |
| Use `text-destructive` for error text | Use `text-red-500` or `text-rose-500` |
| Use `hover:text-foreground` for hover text brightening | Use `hover:text-white` except in sidebar context |
**Semantic status colors:** Use `text-success` / `bg-success`, `text-warning` / `bg-warning`, `text-info` / `bg-info`, `text-destructive` / `bg-destructive` β€” never raw chromatic palette classes like `text-green-500`, `bg-amber-400`, `text-blue-500`, etc.
**Current debt:** `themeAppClassMap.ts` and `NewUserExperienceFlowchart.tsx` still use raw `stone-*` / `rgb()` values (theme definition maps β€” intentionally deferred; requires per-theme CSS variable architecture). `ChatThread.tsx` `messageTypeColors` has 4 remaining raw palette colors (`orange-500`, `indigo-500`, `purple-500`, `pink-500`) for categorical distinctness β€” no semantic tokens defined for these yet.
### Typography
| Do | Don't |
|----|-------|
| Use `text-sm` (14px) as default body size | Use `text-[14px]` or arbitrary pixel values |
| Use `text-xs` (12px) for small/meta text | Use arbitrary pixel sizes for general text |
| Use Tailwind scale (`text-lg`, `text-xl`, `text-2xl`) | Use arbitrary sizes like `text-[28px]`, `text-[40px]` |
| Use `font-medium` as default weight | Use `font-bold` for general emphasis |
| Keep heading hierarchy: `2xl` β†’ `xl` β†’ `lg` β†’ `base` | Skip levels or invert the scale |
### Border Radius
| Do | Don't |
|----|-------|
| Use `rounded-md` (4px) for buttons, inputs, menu items | Use `rounded-[4px]` (same value, less maintainable) |
| Use `rounded-lg` (6px) for cards, containers | Use `rounded-[6px]` (use the token) |
| Use `rounded-xl` or `rounded-modal` for dialogs | Use `rounded-[12px]` (use the token) |
| Use `rounded-full` for pills and avatars | Use `rounded-[100px]` (use `rounded-full`) |
### Spacing
| Do | Don't |
|----|-------|
| Use `gap-2` (8px) as standard item gap | Use arbitrary gap values |
| Use `px-3`/`px-4` for horizontal padding | Mix `px-2.5` and `px-3.5` without reason |
| Use `p-4` for card padding, `p-6` for dialogs | Use `p-[24px]` (same as `p-6`) |
| Use `my-3` for section divider spacing | Use inconsistent vertical margins around dividers |
### Hover & Interaction
| Do | Don't |
|----|-------|
| Use `hover:bg-muted/60` as standard hover bg on dark surfaces | Mix `/40`, `/50`, `/60`, `/70` without hierarchy |
| Use `hover:bg-primary/85` for white/primary buttons | Use `hover:bg-muted/60` on white buttons (creates dark hover) |
| Use `transition-colors` for color-only changes | Use `transition-all` when only color changes |
| Use `duration-200` as standard transition speed | Mix `duration-150`, `duration-200`, `duration-300` randomly |
| Use `group` + `group-hover:` for parent-child hover | Apply hover to each child independently |
| Use `active:scale-[0.97]` for button press feedback | Use `active:scale-95` (inconsistent with Button component) |
### Icons
| Do | Don't |
|----|-------|
| Use `w-4 h-4` as standard icon size in menus/buttons | Use `w-3 h-3` or `w-5 h-5` without size hierarchy reason |
| Set icon color to `text-muted-foreground` by default | Leave icons inheriting parent text color (appears too bright) |
| Brighten on hover: `group-hover:text-foreground` or `group-hover:text-white` | Omit icon hover transitions |
| Use `shrink-0` on icons in flex layouts | Let icons squish when text wraps |
---
## 8. Responsive Behavior
### Breakpoints (Tailwind defaults)
| Prefix | Min Width | Key Changes |
|--------|-----------|-------------|
| (none) | 0px | Mobile-first base styles |
| `sm` | 640px | Wider cards, more padding |
| `md` | 768px | Multi-column layouts begin, `md:text-sm` on inputs |
| `lg` | 1024px | Full sidebar visible, expanded grid |
| `xl` | 1280px | Maximum content width, full feature layout |
| `2xl` | 1400px | Container max-width ceiling |
### Touch Targets
- Minimum interactive height: `h-10` (40px) for buttons and inputs
- Small variant: `h-9` (36px) for compact contexts
- Icon buttons: `h-10 w-10` (40Γ—40px)
- Menu items: `py-1.5` (6px) vertical padding at `text-sm` yields ~32px touch target
### Collapsing Strategy
- Sidebar: collapses from expanded (labels) to icon-only rail on narrow viewports
- Navigation menus: horizontal β†’ hamburger on mobile
- Grid layouts: multi-column β†’ single-column stacked
- Container padding: reduces from `p-6` β†’ `p-4` β†’ `p-3` at smaller breakpoints
---
## 9. Interaction & Motion
### Transitions
| Pattern | Uses | When |
|---------|------|------|
| `transition-colors` | 958 | **Default** β€” use for any color/bg/border change |
| `transition-opacity` | 96 | Fade in/out |
| `transition-all` | 96 | Multiple properties changing simultaneously |
| `transition-transform` | 59 | Scale/translate animations |
### Duration
| Duration | Uses | When |
|----------|------|------|
| `duration-200` | ~48 | **Standard** β€” local, small feedback: toggles, chevron rotation, sidebar width, card/row hovers, opacity on hover, dialogs |
| `duration-300` | ~34 | **Layout motion** β€” panel/drawer resize, sheet exit, canvas split, login/marketing card hover, grid row animations |
### Easing
| Easing | Uses | When |
|--------|------|------|
| `ease-in-out` | 111 | **Default** β€” smooth symmetrical transitions |
| `ease-out` | 52 | Enter animations β€” elements arriving |
### Framer Motion Patterns (23 files)
- `AnimatePresence` for mount/unmount transitions
- Standard enter: `initial={{ opacity: 0 }}` β†’ `animate={{ opacity: 1 }}`
- Standard exit: `exit={{ opacity: 0 }}`
- Duration: typically `0.2s`–`0.3s`
- Used for: panel reveals, notification toasts, drawer slides, loading states
### Interactive Feedback
- **Button press:** `active:scale-[0.97]` (slight shrink on click)
- **Card hover:** `hover:scale-[1.02]` (subtle grow, 12 uses)
- **Focus:** `focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-2` (1px ring, keyboard-only)
---
## 10. Agent Prompt Guide
### Quick Color Reference
- Page background: `bg-background` β†’ `hsl(0 0% 5%)` β†’ `#0d0d0d`
- Primary text: `text-foreground` β†’ `hsl(0 0% 98%)` β†’ `#fafafa`
- Secondary text: `text-muted-foreground` β†’ `hsl(0 0% 55%)` β†’ `#8c8c8c`
- Card surface: `bg-card` β†’ `hsl(0 0% 7%)` β†’ `#121212`
- Border: `border-border` β†’ `hsl(0 0% 14%)` β†’ `#242424`
- Hover background: `bg-muted/60` β†’ `hsl(0 0% 12% / 0.6)`
- Success: `text-success-foreground` β†’ `hsl(142 71% 76%)` β†’ `#86efac`
- Error: `text-destructive` β†’ `hsl(0 72% 51%)` β†’ `#dc2626`
### Example Component Prompts
- **"Create a settings card"**: `bg-card border border-border rounded-lg p-4`. Title at `text-lg font-semibold text-foreground`. Description at `text-sm text-muted-foreground`. Action button: `<Button variant="outline">`.
- **"Create a sidebar menu item"**: `group flex items-center gap-2 rounded-md px-3 py-1.5 text-xs text-sidebar-foreground hover:text-white hover:bg-muted/60 transition-colors`. Icon: `w-4 h-4 shrink-0 text-muted-foreground transition-colors group-hover:text-white`.
- **"Create a dropdown menu"**: Use `DropdownMenu` + `DropdownMenuTrigger` + `DropdownMenuContent` + `DropdownMenuItem` from `src/components/ui/dropdown-menu.tsx`. Icons auto-styled grey β†’ white on hover via the component's built-in `[&_svg]` selectors.
- **"Create a form field"**: Label at `text-sm font-medium text-foreground mb-1.5`. Use `<Input>` component (never inline raw `<input>` with custom focus styles). Help text at `text-xs text-muted-foreground mt-1`.
- **"Create a tooltip"**: `bg-muted text-foreground text-xs rounded-md px-2 py-1 shadow-md`. For pill-style: use `rounded-full` instead of `rounded-md`.
- **"Create a status badge"**: `inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium`. Success: `bg-success/10 text-success-foreground`. Error: `bg-destructive/10 text-destructive`.
### Iteration Guide
1. **Always use semantic color tokens** β€” never raw palette colors (`stone-*`, `gray-*`, `slate-*`). Every color should trace back to a `--css-variable`.
2. **`text-sm` is the default** β€” don't reach for `text-base` unless the context genuinely needs larger text (e.g., chat messages, hero content).
3. **`rounded-md` for elements, `rounded-lg` for containers** β€” this is the consistent radius hierarchy. Dialogs get `rounded-xl` or `rounded-modal`.
4. **`gap-2` is the standard** β€” 8px between items in any flex/grid layout. Use `gap-4` for major sections.
5. **Icons are always `text-muted-foreground`** by default and brighten to `text-foreground` or `text-white` on hover via `group` + `group-hover:`.
6. **`transition-colors duration-200`** is the standard animation. Don't add `transition-all` unless multiple property types are actually changing.
7. **`hover:bg-muted/60`** is the canonical hover background. Use it consistently across menus, nav items, and interactive rows.
8. **The `Button` component handles its own variants** β€” don't rebuild button styles from scratch. Use `variant="outline"` for most secondary actions.