# Bruhs Design System — Agent Guide A framework-agnostic design system. OKLCH fruit-named colors, Monaspace all-mono typography, warm-dark neutrals, elevation by tone and ring (no drop shadows). Works anywhere Tailwind CSS v4 runs (React, Vue, Svelte, Astro, Solid, Leptos, Go templates, Rails, raw HTML), and also works zero-build via a precompiled CSS bundle. This document is self-contained. Read it once and implement. Do not invent tokens, scale values, or class names — use exactly what's listed here. --- ## 1. Adoption ### Option A — Tailwind v4 project (preferred) ```css /* your-app.css */ @import "tailwindcss"; @import "@bruhs/theme/styles/index.css"; ``` Ensure fonts are served at `/fonts/*` (the CSS references them via relative paths). Copy `@bruhs/theme/fonts/*` into your `public/fonts/` at build time, or link them in via your bundler. ### Option B — Zero-build / cross-language (Leptos, Go, Rails, plain HTML) ```html ``` All utilities are pre-compiled. Fonts load from the CDN too. Drop into any HTML document. ### Option C — Copy-paste Download `dist/bruhs.css` + the `fonts/` directory from the package. Serve them from your own host. --- ## 2. Core philosophy Four non-negotiable rules that define Bruhs: 1. **All-mono typography.** Every pixel of text is a Monaspace variant. No sans-serif, no system fonts. 2. **Warm-dark neutrals.** The canvas is `longan` (warm brown-black), not gray or pure black. 3. **No drop shadows.** Elevation is expressed by tone step + `inset-ring`. `shadow-xs` through `shadow-2xl` do not exist in Bruhs. 4. **Persimmon is the brand.** Reserved for primary actions, focus, and "pay attention" moments. Never use it as ambient chrome. --- ## 3. Color vocabulary Ten named scales. Every scale has 11 steps (`50`, `100`, `200`, …, `900`, `950`) in OKLCH. Use the semantic name, not a hex. | Token | Role | Tint | |-------|------|------| | `lychee` | Errors, destructive actions, critical alerts | Red | | `persimmon` | Primary actions, brand, focus, bloom | Orange | | `durian` | Warnings, attention highlights | Yellow | | `pandan` | Success, live indicators, positive states | Green | | `blueberry` | Informational, links, selection | Blue | | `mangosteen` | Decorative, secondary accents | Purple | | `dragonfruit` | Decorative, tertiary accents | Pink | | `rambutan` | Foreground text, UI surfaces | Warm gray | | `longan` | Background canvas, panels, cards | Warm brown-black | | `bruh-{base,eye,mouth}` | Mascot branding only | Custom | ### Utilities Any Tailwind color utility works with these names: - `bg-persimmon-500`, `text-rambutan-100`, `border-pandan-400` - `ring-persimmon-500`, `inset-ring-white/10`, `outline-persimmon-400` - `fill-lychee-400`, `stroke-durian-300`, `decoration-blueberry-500` - `divide-rambutan-100/10`, `accent-persimmon-500` Opacity modifiers on a color scale (`bg-persimmon-500/15`) are the primary way to produce soft/muted tints. **Do not** introduce new shades; use opacity. ### Canvas / panel / card tones | Surface | Background | Ring | |---------|------------|------| | Page canvas | `bg-longan-950` | — | | Panel / sidebar / nav | `bg-longan-900` | `inset-ring inset-ring-white/5` | | Card | `bg-longan-800` | `inset-ring inset-ring-white/10` | | Floating (popover, menu) | `bg-longan-700` | `inset-ring inset-ring-white/15` | | Modal | `bg-longan-800` on `bg-longan-950/70` scrim | `inset-ring inset-ring-white/20` | --- ## 4. Radius vocabulary Fruit-named scale. **Never** use `rounded-sm`, `rounded-md`, `rounded-lg`, `rounded-xl`, `rounded-2xl`, `rounded-3xl`, or `rounded-full` — those are not Bruhs vocabulary. | Class | Value | Use for | |-------|-------|---------| | `rounded-seed` | 4px | Tags, micro-chips, dense controls | | `rounded-grape` | 8px | Buttons, inputs, badges | | `rounded-lychee` | 14px | Cards, panels, sidebar items | | `rounded-mango` | 22px | Hero tiles, media containers | | `rounded-pomelo` | 36px | Marketing blocks, pull-quotes | | `rounded-orb` | full | Avatars, pills, toggles, dots | Directional variants work: `rounded-l-grape`, `rounded-tr-lychee`, `rounded-b-mango`, etc. ### Concentric nesting For nested rounded elements, enforce `inner = outer − padding`: ```html
`). They inherit from their block-level parent. For inline ``, apply only color (`text-rambutan-200`) — the parent's `text-body`/`text-body-sm` already sets the Neon mono font.
- **`text-code-*` utilities are for block code** (``, `` wrappers). For inline code, omit them.
---
## 6. Elevation
No `shadow-*` utilities. Elevation is a two-lever system:
| Step | Background | Ring |
|------|------------|------|
| Canvas | `bg-longan-950` | — |
| Panel | `bg-longan-900` | `inset-ring-white/5` |
| Card | `bg-longan-800` | `inset-ring-white/10` |
| Floating | `bg-longan-700` | `inset-ring-white/15` |
| Modal | `bg-longan-800` | `inset-ring-white/20` |
### Bloom
One exception: `--shadow-bloom` is a persimmon glow reserved for focus states, active triggers, and deliberate attention accents.
```html
```
Do not introduce your own glows in other colors.
---
## 7. Motion
Animation timing is tokenized so transitions feel coordinated across the system. Five durations, four easings — all exposed as CSS custom properties and as `duration-*` / `ease-*` Tailwind utilities.
### Durations
| Token | Value | Use for |
|-------|-------|---------|
| `duration-blink` | 80ms | Instant feedback — radio select, keyboard nav, typeahead |
| `duration-fast` | 150ms | Hover, button press, small state changes (default) |
| `duration-standard` | 250ms | Expand, fade, slide |
| `duration-slow` | 400ms | Page-level transitions, big panel moves |
| `duration-bloom` | 600ms | Deliberate attention moments, bloom accents |
`--default-transition-duration` is set to `var(--duration-fast)`, so a bare `class="transition"` uses 150ms automatically.
### Easings
| Token | Curve | Use for |
|-------|-------|---------|
| `ease-standard` | `cubic-bezier(0.2, 0, 0, 1)` | Default for most UI transitions |
| `ease-entrance` | `cubic-bezier(0, 0, 0, 1)` | Content entering view (slide-in, fade-in) |
| `ease-exit` | `cubic-bezier(0.4, 0, 1, 1)` | Content leaving view (slide-out, fade-out) |
| `ease-bloom` | `cubic-bezier(0.34, 1.56, 0.64, 1)` | Playful overshoot — reserved for bloom/attention accents |
### Rules
- **Bare `transition` gets the right default.** Don't reach for `duration-150` manually.
- **Pair durations with their intended easings.** `duration-standard ease-standard` for expand/fade; `duration-bloom ease-bloom` only for brand moments.
- **Respect `prefers-reduced-motion`.** Wrap non-essential motion in `motion-safe:` variants.
- **Never use durations > 1000ms** except for splash/onboarding moments.
---
## 8. Spacing
Tailwind's default 4px grid (`--spacing: 0.25rem`) is the only spacing rhythm. Every gap, padding, and margin should be a multiple of 4px.
### Common patterns
| Context | Value | Why |
|---------|-------|-----|
| Inline gap (button → icon, badge → text) | `gap-1.5` (6px) | Tight without touching |
| Form label → field | `gap-2` (8px) | Tightly bound |
| Form field → field | `gap-6` (24px) | Clear separation without feeling loose |
| Card padding (compact) | `p-4` (16px) | Dense apps |
| Card padding (default) | `p-6` (24px) | Standard |
| Section → section | `gap-12 md:gap-16` (48/64px) | Breathing room on long pages |
| Page padding | `p-6 md:p-8` (24/32px) | Mobile-first |
### Rules
- **Never use `mt-*`/`mb-*`/`ml-*`/`mr-*`/`mx-*`/`my-*` between flex or grid children** — use `gap-*` on the parent.
- **Prefer shorthand over split axes.** `p-4` not `px-4 py-4`; keep them split only when a variant overrides one axis (e.g. `p-6 md:px-8`).
- **Use `--spacing(…)` for arbitrary spacing values** — `--padding: --spacing(3)` not `12px` or `0.75rem`.
- **Never use `calc(var(--spacing)*…)`** — use `--spacing(…)`.
- **Never use `theme(spacing.…)`** — use `--spacing(…)`.
---
## 9. Responsive breakpoints
Mobile-first. Tailwind's default breakpoints apply: `sm:` 640px, `md:` 768px, `lg:` 1024px, `xl:` 1280px, `2xl:` 1536px.
### Layout shifts
| Shift | At breakpoint |
|-------|---------------|
| 1 → 2 column | `md:` (768px) |
| 2 → 3 column | `lg:` (1024px) |
| 3 → 4 column | `xl:` (1280px) |
| Off-canvas nav → in-flow sidebar | `lg:` (1024px) |
| Page padding 24px → 32px | `md:` (768px) |
| Section gap 48px → 64px | `md:` (768px) |
### Typography
- **Display and headings use `clamp()` internally** — don't add `md:text-*` overrides to them. The role utilities handle fluid scaling.
- **Body text is fixed** at its declared size. If a context needs `text-body-sm` at desktop density, bump it to mobile-base via `max-sm:text-base/6`.
### Rules
- **Always `min-h-dvh`**, never `min-h-screen` (deprecated).
- **Touch targets stay ≥44×44px** regardless of breakpoint. Use the absolute overlay pattern for small icon buttons.
- **Reconfigure dividers per breakpoint** when column count changes — see §10.19.
- **Checkboxes, radios, toggles are bigger on mobile** (`size-5 sm:size-4`, `w-11 sm:w-9`) for finger-friendliness.
---
## 10. Component patterns
All patterns below are copy-paste ready. Adapt to your framework's template syntax — the classes do not change.
### 10.1 Button — primary
```html
```
**Rules:** only one primary per page/dialog. Ring matches fill color exactly — never use reduced-opacity rings on a solid button.
### 10.2 Button — soft (secondary default)
```html
```
### 10.3 Button — outline
```html
```
### 10.4 Button — ghost
```html
```
### 10.5 Button — destructive
```html
```
Use the soft style for destructive actions unless the action is the primary purpose of the page/dialog.
### 10.6 Button sizes
Default (~36px): `px-3 py-2 text-button`.
Small (~28px): `px-2.5 py-1.5 text-button-sm`.
Use at most two sizes per surface.
### 10.7 Button with icon
Asymmetric padding — icon side padding equals vertical padding.
```html
```
### 10.8 Badge — soft
```html
Live
```
Pattern: any accent tone with `-500/15` bg, `-200` text, `-400/20` ring. Works for `lychee`, `persimmon`, `durian`, `pandan`, `blueberry`, `mangosteen`, `dragonfruit`, `rambutan`.
### 10.9 Badge — outline
```html
Draft
```
### 10.10 Badge with icon
```html
Live
```
### 10.11 Text input
```html
```
### 10.12 Textarea
Same classes as text input, on `