Bonsai
MIT
Minimal, light design system with warm orange and indigo accents, built on CSS custom properties for SaaS and marketing sites
Layout StudioImport this kit into a Studio project and start editing.
CLI installRun it in any project. No account needed.
npx @layoutdesign/context install bonsaiUI themeReskin shadcn or Layout UI components to match Bonsai.
npx shadcn add https://layout.design/r/bonsai/theme.json# layout.md — Hello Bonsai Design System
---
## 0. Quick Reference
**Stack:** Webflow-built marketing site · CSS custom properties (30 vars extracted, high confidence) · No Tailwind/CSS-in-JS — use `var(--token)` directly.
**How to apply:** Use as `var(--token-name)` in CSS, `style={{ prop: 'var(--token-name)' }}` in JSX, or `bg-[var(--token-name)]` in Tailwind.
```css
/* ── Core Tokens ── */
:root {
/* Colour */
--colors-interface--dark: #000; /* Primary text, buttons bg */
--colors-interface--white: white; /* On-dark text, card bg */
--colors-interface--neutral-1: #f6f4f1; /* Section backgrounds, hover states */
--colors-interface--neutral-2: #e8e6e3; /* Tab/accordion hover bg */
--colors-interface--border-dark: #0000001a; /* Subtle borders on light bg */
--colors-brand--gradient-1: #ff8308; /* Brand accent — orange */
--colors-brand--gradient-2: #ff5043; /* Brand accent hover — red-orange */
--colors-brand--gradient-3: #392bd5; /* Brand accent cool — indigo */
--colors-interface--colors-text--body-text: #0009; /* Body text, 60% black */
--colors-text--faded-headings: #0006; /* Muted/secondary headings */
--green: #22ad01; /* Success state */
--beige: #f2f0ed; /* Warm neutral surface */
/* Typography */
--fonts--body-font: Geist, Arial, sans-serif;
--font-size-xs: 12px; --font-size-sm: 14px;
--font-size-md: 17px; --font-size-lg: 20px;
--font-size-xl: 50px; --font-size-2xl: 56px;
--font-weight-regular: 400; --font-weight-medium: 500; --font-weight-semibold: 700;
--line-height-tight: 19.6px; --line-height-normal: 23.8px; --line-height-loose: 26px;
/* Spacing */
--space-xs: 6px; --space-sm: 18px; --space-md: 24px;
--space-lg: 36px; --space-3xl: 96px;
--section-padding--small: 48px; --section-padding--default: 72px; --section-padding--large: 120px;
--navbar-height: 60px;
--content-width--container: 1428px; --content-width--large: 896px;
--content-width--medium: 654px; --content-width--small: 412px;
/* Radius — pill-shaped CTAs */
--gaps-radius--default-border-radius: 6px; /* cards, chips, tabs */
--radius-lg: 200px; /* primary buttons — PILL SHAPE */
/* Shadow / Motion */
--shadow-sm: rgba(0,0,0,0.06) 0px 2px 6px 0px;
--duration-fast: 0.2s; --ease-default: ease;
}
```
```tsx
// Primary button — correct token usage
<button style={{
fontFamily: 'var(--fonts--body-font)', fontSize: 'var(--font-size-md)',
fontWeight: 'var(--font-weight-medium)', lineHeight: 'var(--line-height-normal)',
color: 'var(--colors-interface--white)', backgroundColor: 'var(--colors-interface--dark)',
borderRadius: 'var(--radius-lg)', padding: '12px var(--space-sm)',
border: 'none', display: 'flex', alignItems: 'center', gap: 'var(--space-xs)',
transition: `background-color var(--duration-fast) var(--ease-default)`,
}}>Get started</button>
```
**NEVER rules:**
- NEVER use `border-radius` < 64px on primary CTA buttons — they are **pill-shaped (200px)**
- NEVER hardcode `#ff8308`, `#ff5043`, `#392bd5` — use `--colors-brand--gradient-*`
- NEVER use font families other than `Geist, Arial, sans-serif`
- NEVER use spacing values not in the `--space-*` or `--section-padding--*` token set
- NEVER omit hover states — opacity `0.5` on nav links, `--colors-interface--dark` bg on buttons
- NEVER use `font-weight: 600` — the scale is 400 / 500 / 700 only
**Full design system → see layout.md**
---
## 1. Design Direction & Philosophy
### Character & Mood
Hello Bonsai is a **professional SaaS platform for freelancers** — the aesthetic is clean, minimal, and quietly confident. The design communicates trustworthiness and clarity without corporate stiffness. Think: editorial restraint with warm undertones.
### Palette personality
The base palette is near-monochromatic (black/white/neutral beige) punctuated by a **warm orange (#ff8308) → red-orange (#ff5043) gradient** for brand energy, and a **cool indigo (#392bd5)** as a counterpoint accent. The warmth comes from beige neutrals (`#f6f4f1`, `#f2f0ed`) rather than warm tones in the primary palette.
### What this design explicitly rejects
- **Rounded corners on UI containers** — cards use 6px radius. Only CTAs are pill-shaped.
- **Decorative gradients on surfaces** — gradients are for brand accent marks and highlights, not backgrounds.
- **Heavy typography** — headings are `font-weight: 500` (medium), not bold. The system only uses 700 on one element (cookie banner button).
- **Coloured text** — body and heading text is near-black (`#000`, `#0009`). Colour is reserved for interactive/accent elements.
- **Dense layouts** — generous section padding (72px default, up to 120px) signals breathing room.
- **Drop shadows everywhere** — only one shadow token (`--shadow-sm`) exists. Elevation is used sparingly.
---
## 2. Colour System
### Tier 1 — Primitive values (original CSS variable names preserved)
```css
:root {
/* Blacks & Whites */
--colors-interface--dark: #000; /* Pure black — text, button bg */
--colors-interface--white: white; /* Pure white — on-dark text, backgrounds */
--colors-interface--element-bg-white: #ffffff26; /* White at 15% opacity — glassmorphism bg */
--colors-interface--element-bg-whiteelement-bg-dark: #0000004d; /* Black at 30% opacity — dark overlays */
/* Neutrals */
--colors-interface--neutral-1: #f6f4f1; /* Warm light grey — hover bg, section bg */
--colors-interface--neutral-2: #e8e6e3; /* Warm mid grey — tab hover, accordion hover */
--beige: #f2f0ed; /* Warm beige — decorative surface */
/* Borders */
--colors-interface--border-dark: #0000001a; /* Black at 10% — borders on light bg */
--colors-interface--border-white: #fff3; /* White at 20% — borders on dark bg */
/* Brand Gradient */
--colors-brand--gradient-1: #ff8308; /* Orange — gradient start, primary accent */
--colors-brand--gradient-2: #ff5043; /* Red-orange — gradient end, hover accent */
--colors-brand--gradient-3: #392bd5; /* Indigo — cool counterpoint accent */
/* Text */
--colors-interface--colors-text--body-text: #0009; /* Black 60% — body copy */
--colors-interface--colors-text--body-text-white: #fff9; /* White 60% — body copy on dark */
--colors-text--faded-headings: #0006; /* Black 40% — secondary/muted headings */
/* Status */
--green: #22ad01; /* Success green */
}
```
### Tier 2 — Semantic aliases (curated roles)
```css
:root {
/* Surface */
--hellobonsai-bg-app: var(--colors-interface--element-bg-white); /* App-level bg, glass panels */
--hellobonsai-bg-surface: var(--colors-interface--colors-text--body-text-white); /* Surface overlay on dark */
/* Text */
--hellobonsai-text-primary: var(--colors-interface--dark); /* Primary text colour */
/* Border */
--hellobonsai-border: var(--colors-interface--border-dark); /* Default border */
/* Accent */
--hellobonsai-accent: var(--colors-brand--gradient-1); /* #ff8308 — primary CTA accent */
--hellobonsai-accent-hover: var(--colors-brand--gradient-2); /* #ff5043 — accent on hover */
--hellobonsai-accent-cool: var(--colors-brand--gradient-3); /* #392bd5 — cool accent */
/* Status */
--hellobonsai-success: var(--green); /* #22ad01 — success states */
}
```
### Tier 3 — Component tokens
```css
:root {
/* Button — primary (default black pill) */
--button-primary-bg: var(--colors-interface--dark);
--button-primary-text: var(--colors-interface--white);
--button-primary-bg-hover: var(--colors-interface--dark); /* stays dark, border clears */
--button-radius: 200px; /* PILL — non-negotiable */
/* Card */
--card-bg: var(--colors-interface--white);
--card-border-radius: var(--gaps-radius--default-border-radius); /* 6px */
--card-shadow: var(--shadow-sm);
/* Chip / Badge border */
--chip-border: var(--colors-interface--border-dark); /* 1px solid rgba(0,0,0,0.1) */
--chip-radius: var(--gaps-radius--default-border-radius); /* 6px */
/* Input focus */
--input-focus-border: rgb(56, 152, 236); /* Webflow default — extracted from CSS rules */
}
```
### Colour Palette Table
| Token | Value | Role |
|---|---|---|
| `--colors-interface--dark` | `#000` | Primary text, button fill |
| `--colors-interface--white` | `#fff` | On-dark text, card bg |
| `--colors-interface--neutral-1` | `#f6f4f1` | Hover bg, section bg |
| `--colors-interface--neutral-2` | `#e8e6e3` | Tab/accordion hover |
| `--colors-interface--border-dark` | `#0000001a` | Borders on light bg |
| `--colors-brand--gradient-1` | `#ff8308` | Brand accent — orange |
| `--colors-brand--gradient-2` | `#ff5043` | Brand accent — red-orange |
| `--colors-brand--gradient-3` | `#392bd5` | Brand accent — indigo |
| `--colors-interface--colors-text--body-text` | `#0009` | Body copy (60% black) |
| `--colors-text--faded-headings` | `#0006` | Muted headings (40% black) |
| `--green` | `#22ad01` | Success |
| `--beige` | `#f2f0ed` | Warm decorative surface |
---
## 3. Typography System
**Single typeface:** `Geist, Arial, sans-serif` across headings, body, badges, and navigation. No secondary face.
### Composite Type Tokens
```css
/* ── Display / H1 ── */
.type-display {
font-family: var(--fonts--body-font); /* Geist, Arial, sans-serif */
font-size: var(--font-size-2xl); /* 56px */
font-weight: var(--font-weight-medium); /* 500 — NOT bold */
line-height: 61.6px; /* 1.1 ratio — tight for display */
letter-spacing: -1.12px; /* Negative tracking on display text */
text-align: center; /* H1 is centred on homepage */
}
/* ── Heading H2 ── */
.type-h2 {
font-family: var(--fonts--body-font);
font-size: var(--font-size-xl); /* 50px */
font-weight: var(--font-weight-medium); /* 500 */
line-height: 57.5px; /* 1.15 ratio */
letter-spacing: -1px;
text-align: start; /* H2 is left-aligned in sections */
}
/* ── Heading H3 ── */
.type-h3 {
font-family: var(--fonts--body-font);
font-size: var(--font-size-lg); /* 20px */
font-weight: var(--font-weight-medium); /* 500 */
line-height: var(--line-height-loose); /* 26px, 1.3 ratio */
letter-spacing: normal;
}
/* ── Body / Default ── */
.type-body {
font-family: var(--fonts--body-font);
font-size: var(--font-size-md); /* 17px */
font-weight: var(--font-weight-regular); /* 400 */
line-height: var(--line-height-normal); /* 23.8px, 1.4 ratio */
letter-spacing: normal;
color: var(--colors-interface--colors-text--body-text); /* #0009 */
}
/* ── Body Small / Link / Chip ── */
.type-small {
font-family: var(--fonts--body-font);
font-size: var(--font-size-sm); /* 14px */
font-weight: var(--font-weight-medium); /* 500 — small text is medium weight */
line-height: var(--line-height-tight); /* 19.6px, 1.4 ratio */
letter-spacing: normal;
}
/* ── Caption / Badge ── */
.type-caption {
font-family: var(--fonts--body-font);
font-size: var(--font-size-xs); /* 12px */
font-weight: var(--font-weight-semibold);/* 700 — only used at smallest size */
line-height: var(--line-height-tight); /* 19.6px */
letter-spacing: normal;
}
```
### Font Weight Scale
| Token | Value | Usage |
|---|---|---|
| `--font-weight-regular` | `400` | Body copy, card text, muted labels |
| `--font-weight-medium` | `500` | Headings (all levels), buttons, nav links, small interactive text |
| `--font-weight-semibold` | `700` | Caption/badge only — **avoid elsewhere** |
### Pairing Rules
- **H1 → Body:** 56px medium heading paired with 17px regular body. Never use bold for headings.
- **H2 → H3:** Section heading (50px) introduces card grid headings (20px). The jump is intentional.
- **Small text is heavier:** 14px text uses weight 500 (not 400) to remain legible at small sizes.
- NEVER use `font-weight: 600` — it is not in the Geist font loading and will trigger faux-bold rendering.
---
## 4. Spacing & Layout
```css
:root {
/* ── Spacing Scale ── */
--space-xs: 6px; /* Inline gaps: button icon gap, chip gap, bento gap */
--space-sm: 18px; /* Button horizontal padding, small component padding */
--space-md: 24px; /* Component internal padding */
--space-lg: 36px; /* Between-component spacing */
--space-3xl: 96px; /* Major section gaps */
/* ── Section Padding (vertical rhythm) ── */
--section-padding--small: 48px; /* Compact sections, footers */
--section-padding--default: 72px; /* Standard section top/bottom padding */
--section-padding--large: 120px; /* Hero and landmark sections */
/* ── Navigation ── */
--navbar-height: 60px; /* Fixed navbar height */
/* ── Content Widths ── */
--content-width--container: 1428px; /* Outer page container max-width */
--content-width--x-large: 1138px; /* Wide content columns */
--content-width--large: 896px; /* Standard content column */
--content-width--medium: 654px; /* Narrow content / prose */
--content-width--small: 412px; /* Tight column / single-col card */
/* ── Bento Grid ── */
--gaps-radius--bento-gap: 6px; /* Gap between bento grid cells */
--gaps-radius--default-border-radius: 6px; /* Default corner radius for containers */
}
```
### Grid & Breakpoints
| Breakpoint | Width | Behaviour |
|---|---|---|
| xs | 300px | Minimum supported width |
| sm | 480px | Single-column layout |
| md | 640px | Two-column begins |
| lg | 768px | Tablet breakpoint |
| xl | 992px | Desktop layout |
| 2xl | 1428px | Container max (`--content-width--container`) |
**Paired breakpoints** (min/max pairs detected): 479/480px, 639/640px, 767/768px, 991/992px — Webflow uses paired breakpoints for mobile-first overrides.
### Layout Decisions
- **Navigation:** `display: flex; flex-direction: row` — horizontal nav row.
- **Cards:** `display: flex; flex-direction: column; justify-content: space-between` — content stacks vertically, footer pinned to bottom.
- **Buttons:** `display: flex; flex-direction: row; justify-content: center; align-items: center; gap: var(--space-xs)` — icon + label row.
- **Bento grids** use `--gaps-radius--bento-gap: 6px` between cells.
- Prefer **CSS Grid** for section-level layouts (feature grids, card grids). Use **Flexbox** for component internals (buttons, nav items, badges).
---
## 5. Page Structure & Layout Patterns
### 5.1 Section Map
| # | Section | Layout type | Approx. height | Key elements | Notes |
|---|---|---|---|---|---|
| 1 | Navbar | `flex row` | 60px | Logo, nav links (dropdown), CTA button | Fixed height via `--navbar-height`; links fade to 0.5 opacity on hover |
| 2 | Hero | Full-width, centered column | ~560px | H1 (56px, centred), body text, 2× CTA buttons | `--section-padding--large: 120px` top/bottom (inferred) |
| 3 | Social proof / logos | Flex row, centred | ~120px | Partner/client logo strip | Muted, neutral-1 bg (inferred) |
| 4 | Feature — CRM / Leads | 2-col: text left + product screenshot right | ~600px | H2, body, feature list, CTA link | `--section-padding--default: 72px` (inferred) |
| 5 | Feature — Projects | 2-col: product screenshot left + text right | ~600px | H2, body, feature list, CTA link | Alternating layout (inferred) |
| 6 | Feature — Finance | 2-col: text left + screenshot right | ~600px | H2, body, feature list, CTA link | (inferred) |
| 7 | Bento / Card Grid | CSS grid, bento pattern | ~700px | 4–6 cards, `--gaps-radius--bento-gap: 6px`, cards with `--shadow-sm` | 29 card instances detected |
| 8 | Testimonials / Social proof | Flex row or slider | ~400px | Avatar (border-radius: 50%), quote text, slider buttons | 81 badge instances; slider-button hover uses `--neutral-1` (inferred) |
| 9 | Pricing | 3-col grid | ~700px | Pricing cards, toggle (monthly/annual), CTA buttons | Toggle uses `opacity 0.2s` transition |
| 10 | FAQ / Accordion | Single column, `--content-width--large: 896px` | ~500px | Accordion items — hover bg `--neutral-2` | `.accordion-item.padded:hover` confirmed in CSS |
| 11 | Final CTA / Bottom hero | Full-width, centred | ~320px | H2, body, primary button | Dark background variant likely (inferred) |
| 12 | Footer | 4–5 col grid | ~300px | Logo, nav link columns, social links, legal | `--section-padding--small: 48px`; links opacity 1 on hover |
### 5.2 Layout Patterns
**Navbar:**
```
display: flex | flex-direction: row | align-items: center
height: var(--navbar-height) /* 60px */
padding: 0 var(--space-md)
max-width: var(--content-width--container) /* 1428px, centred */
```
**Hero (centred column):**
```
display: flex | flex-direction: column | align-items: center
padding: var(--section-padding--large) 0 /* 120px */
max-width: var(--content-width--medium) /* 654px — prose width */
margin: 0 auto
text-align: center
```
**Feature section (2-column):**
```
display: grid | grid-template-columns: 1fr 1fr
gap: var(--space-3xl) /* 96px */
padding: var(--section-padding--default) 0 /* 72px */
max-width: var(--content-width--x-large) /* 1138px */
margin: 0 auto
```
**Card / Bento grid:**
```
display: grid | grid-template-columns: repeat(auto-fit, minmax(300px, 1fr))
gap: var(--gaps-radius--bento-gap) /* 6px — tight bento cells */
max-width: var(--content-width--container) /* 1428px */
```
**Card internal:**
```
display: flex | flex-direction: column | justify-content: space-between
border-radius: var(--gaps-radius--default-border-radius) /* 6px */
box-shadow: var(--shadow-sm)
background: var(--colors-interface--white)
```
### 5.3 Visual Hierarchy
- **H1** is the loudest element (56px, centred, medium weight, -1.12px tracking). Nothing competes.
- **Primary CTA buttons** are **pill-shaped** (200px radius), black fill, white text — highly distinctive against white backgrounds.
- **Secondary CTAs** are outlined or ghost variants, same pill radius.
- **Section headings (H2)** are left-aligned at 50px — they anchor section content visually.
- **Cards** are low-elevation (`--shadow-sm: rgba(0,0,0,0.06) 0px 2px 6px`) — subtle, not dramatic.
- **Brand accent (#ff8308)** appears in gradient marks, highlights, and feature icons — never as button backgrounds on primary CTAs.
- **Whitespace is a primary design element** — section padding of 72–120px creates a spacious, premium feel.
### 5.4 Content Patterns
**Feature row:** H2 (50px) → body copy (17px, `#0009`) → feature list with chip/badge items → CTA link (14px medium, with arrow icon). Repeats 3× with alternating image/text sides.
**Card pattern:** Icon or screenshot → H3 (20px, medium) → body (17px, regular) → optional CTA link pinned to bottom (via `justify-content: space-between`).
**CTA pattern:** Black pill button (`border-radius: 200px`) + optional ghost/outline pill button side by side. Gap between buttons: `var(--space-sm)` (18px).
**Badge/chip pattern:** `display: flex; align-items: center; gap: var(--space-xs)` — icon + label, `border: 1px solid var(--colors-interface--border-dark)`, `border-radius: 6px`.
---
## 6. Component Patterns
### 6.1 Primary Button
**Anatomy:** `[icon?] [label]` — flex row, centred, pill-shaped.
| State | bg | text | border | opacity |
|---|---|---|---|---|
| default | `--colors-interface--dark` (#000) | `--colors-interface--white` | none | 1 |
| hover | `--colors-interface--dark` | `--colors-interface--white` | transparent | 1 |
| focus | `--colors-interface--dark` | `--colors-interface--white` | `rgb(56,152,236)` outline | 1 |
| active | `--colors-interface--dark` | `--colors-interface--white` | none | 0.85 |
| disabled | `--colors-interface--neutral-2` | `--colors-text--faded-headings` | none | 0.5 |
**Token mappings:** `border-radius: var(--radius-lg)` [200px], `padding: 12px var(--space-sm)` [18px], `gap: var(--space-xs)` [6px], `font-size: var(--font-size-md)` [17px], `font-weight: var(--font-weight-medium)` [500].
```tsx
// Primary Button — full state coverage
interface ButtonProps {
children: React.ReactNode;
disabled?: boolean;
loading?: boolean;
icon?: React.ReactNode;
onClick?: () => void;
}
export const PrimaryButton = ({ children, disabled, loading, icon, onClick }: ButtonProps) => (
<button
onClick={onClick}
disabled={disabled || loading}
style={{
// Layout
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
gap: 'var(--space-xs)', /* 6px — icon to label gap */
padding: '12px var(--space-sm)', /* 12px 18px */
// Typography
fontFamily: 'var(--fonts--body-font)',
fontSize: 'var(--font-size-md)', /* 17px */
fontWeight: 'var(--font-weight-medium)', /* 500 */
lineHeight: 'var(--line-height-normal)',
textDecoration: 'none',
// Colour
color: 'var(--colors-interface--white)',
backgroundColor: (disabled || loading)
? 'var(--colors-interface--neutral-2)'
: 'var(--colors-interface--dark)',
// Shape
borderRadius: '200px', /* --radius-lg: PILL — never reduce */
border: 'none',
// Interaction
cursor: (disabled || loading) ? 'not-allowed' : 'pointer',
opacity: (disabled || loading) ? 0.5 : 1,
transition: `background-color var(--duration-fast) var(--ease-default),
opacity var(--duration-fast) var(--ease-default)`,
}}
onMouseEnter={e => {
if (!disabled && !loading) {
(e.target as HTMLButtonElement).style.opacity = '0.9';
}
}}
onMouseLeave={e => {
(e.target as HTMLButtonElement).style.opacity = '1';
}}
>
{loading ? <span className="spin">⟳</span> : icon}
{children}
</button>
);
```
---
### 6.2 Ghost / Outline Button
| State | bg | text | border |
|---|---|---|---|
| default | transparent | `--colors-interface--dark` | `1px solid --colors-interface--border-dark` |
| hover | `--colors-interface--dark` | `--colors-interface--white` | transparent |
| focus | transparent | `--colors-interface--dark` | `rgb(56,152,236)` |
| disabled | transparent | `--colors-text--faded-headings` | `--colors-interface--border-dark` |
Same pill radius (200px), same typography. Background transitions to dark on hover per extracted CSS rule `.button:hover:where(.w-variant-*)`.
---
### 6.3 Card
**Anatomy:** `[media/image?] [content: [H3] [body]] [footer: CTA link?]`
| State | shadow | bg | border |
|---|---|---|---|
| default | `--shadow-sm` | `--colors-interface--white` | none |
| hover | `--shadow-sm` (unchanged) | `--colors-interface--white` | none |
| focus | `--shadow-sm` | `--colors-interface--white` | `rgb(56,152,236)` outline |
```tsx
export const Card = ({ title, body, cta }: { title: string; body: string; cta?: React.ReactNode }) => (
<div style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
borderRadius: 'var(--gaps-radius--default-border-radius)', /* 6px — NOT pill */
boxShadow: 'var(--shadow-sm)', /* rgba(0,0,0,0.06) 0px 2px 6px */
backgroundColor: 'var(--colors-interface--white)',
padding: 'var(--space-md)', /* 24px */
fontFamily: 'var(--fonts--body-font)',
color: 'var(--colors-interface--dark)',
transition: `box-shadow var(--duration-fast) var(--ease-default)`,
}}>
<div>
<h3 style={{
fontSize: 'var(--font-size-lg)', /* 20px */
fontWeight: 'var(--font-weight-medium)',/* 500 */
lineHeight: 'var(--line-height-loose)', /* 26px */
margin: '0 0 var(--space-xs)',
}}>{title}</h3>
<p style={{
fontSize: 'var(--font-size-md)', /* 17px */
fontWeight: 'var(--font-weight-regular)',/* 400 */
lineHeight: 'var(--line-height-normal)', /* 23.8px */
color: 'var(--colors-interface--colors-text--body-text)', /* #0009 */
margin: 0,
}}>{body}</p>
</div>
{cta && <div style={{ marginTop: 'var(--space-sm)' }}>{cta}</div>}
</div>
);
```
---
### 6.4 Chip / Filter Tag
**Anatomy:** `[icon?] [label]` — flex row, 6px radius (NOT pill), bordered.
| State | bg | border | text |
|---|---|---|---|
| default | transparent | `1px solid rgba(0,0,0,0.1)` | `--colors-interface--dark` |
| hover | `--colors-interface--neutral-1` | transparent | `--colors-interface--dark` |
| active/selected | `--colors-interface--dark` | transparent | `--colors-interface--white` |
```tsx
export const Chip = ({ label, icon, selected }: { label: string; icon?: React.ReactNode; selected?: boolean }) => (
<span style={{
display: 'flex', flexDirection: 'row', alignItems: 'center',
gap: 'var(--space-xs)', /* 6px */
padding: '0 10px',
height: '32px',
fontFamily: 'var(--fonts--body-font)',
fontSize: 'var(--font-size-sm)', /* 14px */
fontWeight: 'var(--font-weight-medium)', /* 500 */
lineHeight: 'var(--line-height-tight)', /* 19.6px */
borderRadius: 'var(--gaps-radius--default-border-radius)', /* 6px — NOT pill */
border: selected ? 'none' : '1px solid var(--colors-interface--border-dark)',
backgroundColor: selected ? 'var(--colors-interface--dark)' : 'transparent',
color: selected ? 'var(--colors-interface--white)' : 'var(--colors-interface--dark)',
cursor: 'pointer',
transition: `color var(--duration-fast) var(--ease-default),
border-color var(--duration-fast) var(--ease-default),
background-color var(--duration-fast) var(--ease-default)`,
}}>
{icon}{label}
</span>
);
```
---
### 6.5 Nav Link
**Anatomy:** `[label]` with optional dropdown caret.
| State | opacity | bg |
|---|---|---|
| default | 1 | transparent |
| hover | **0.5** | transparent |
| active (dropdown open) | 1 | transparent |
| focus | 1 | transparent (outline: none per extracted CSS) |
```tsx
export const NavLink = ({ label, href }: { label: string; href: string }) => (
<a href={href} style={{
fontFamily: 'var(--fonts--body-font)',
fontSize: 'var(--font-size-md)', /* 17px */
fontWeight: 'var(--font-weight-regular)', /* 400 */
color: 'var(--colors-interface--dark)',
textDecoration: 'none',
transition: `opacity var(--duration-fast) var(--ease-default)`,
padding: '0 var(--space-xs)',
}}
onMouseEnter={e => ((e.target as HTMLElement).style.opacity = '0.5')}
onMouseLeave={e => ((e.target as HTMLElement).style.opacity = '1')}
>{label}</a>
);
```
---
### 6.6 Dropdown Menu
| State | bg | padding-left | transition |
|---|---|---|---|
| default | transparent | inherited | — |
| hover (item) | `--colors-interface--neutral-1` | + 6px shift | `background-color 0.2s` |
| hover (CTA item) | `--colors-interface--neutral-1` | unchanged | `background-color 0.2s` |
Dropdown wrapper: `display: flex; flex-direction: row; align-items: stretch`. Items animate background and indent on hover per extracted CSS.
---
### 6.7 Accordion Item
| State | bg |
|---|---|
| default | transparent |
| hover (padded variant) | `var(--colors-interface--neutral-2)` (#e8e6e3) |
| expanded | transparent |
---
### 6.8 Pricing Toggle
```
display: flex | flex-direction: row | align-items: center
gap: 3px | padding: 0 16px
font-size: 14px | font-weight: 400 | color: rgb(34,34,34)
transition: opacity 0.2s ease
```
Active state switches opacity. Inactive option fades to 0.5 opacity.
---
### 6.9 Avatar
```css
.avatar {
border-radius: 50%; /* Full circle — always */
display: block;
/* Dimensions set per context — no fixed token */
}
```
---
### 6.10 Input Field
| State | border |
|---|---|
| default | `1px solid var(--colors-interface--border-dark)` |
| focus | `1px solid rgb(56, 152, 236)` — Webflow's default focus ring |
| error | [TBD — extract manually] |
| disabled | [TBD — extract manually] |
---
## 7. Elevation & Depth
```css
:root {
/* ── Shadow Scale ── */
--shadow-sm: rgba(0, 0, 0, 0.06) 0px 2px 6px 0px; /* Cards — sole shadow token. Extremely subtle. */
/* --shadow-md: [TBD] — not extracted, not in system */
/* --shadow-lg: [TBD] — not extracted, not in system */
/* ── Overlay ── */
--colors-interface--element-bg-white: #ffffff26; /* 15% white — glassmorphism panels */
--colors-interface--element-bg-whiteelement-bg-dark: #0000004d; /* 30% black — modal overlays, dark panels */
/* ── Border as depth signal ── */
--colors-interface--border-dark: #0000001a; /* 10% black — separates white surfaces */
--colors-interface--border-white: #fff3; /* 20% white — separates on dark surfaces */
/* ── Z-index Scale (inferred from Webflow patterns) ── */
--z-base: 0; /* Page content */
--z-raised: 10; /* Cards, popovers */
--z-dropdown: 100; /* Dropdown menus */
--z-navbar: 200; /* Fixed navbar */
--z-modal: 300; /* Modal overlays */
--z-toast: 400; /* Notifications above modal */
}
```
**Elevation philosophy:** The system is near-flat. **One shadow token only** (`--shadow-sm`). Depth is communicated through colour (neutral-1 vs white backgrounds), borders, and whitespace — not through stacked shadows. NEVER add `box-shadow` values outside this token.
---
## 8. Motion
```css
:root {
/* ── Duration ── */
--duration-fast: 0.2s; /* Default transition duration — used on 93 elements */
/* No slow/medium duration tokens extracted — 0.2s is the system standard */
/* ── Easing ── */
--ease-default: ease; /* Browser default ease — used on 264 elements */
/* ── Composed transitions (use these) ── */
--transition-default: all var(--duration-fast) var(--ease-default);
--transition-color: color var(--duration-fast) var(--ease-default);
--transition-bg: background-color var(--duration-fast) var(--ease-default);
--transition-opacity: opacity var(--duration-fast) var(--ease-default);
--transition-chip: color var(--duration-fast) var(--ease-default),
border-color var(--duration-fast) var(--ease-default),
background-color var(--duration-fast) var(--ease-default);
/* ── Keyframes ── */
/* @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } */
/* — used for loading spinners */
}
```
### When to animate
- **DO:** Colour transitions on hover (buttons, links, chips, dropdowns) — `0.2s ease`
- **DO:** Opacity changes (nav link hover, toggle states, inactive states) — `0.2s ease`
- **DO:** Background colour on accordion/tab hover — `0.2s ease`
- **DO:** `transform: scale(1.1)` on video play button hover — `0.2s ease`
- **DO:** `padding-left` shift on dropdown link hover (6px indent) — `0.2s ease`
### When NOT to animate
- NEVER animate layout properties (`width`, `height`, `display`) — not in the extracted system
- NEVER use durations > 0.2s for hover transitions — the system is deliberately snappy
- NEVER animate on page load without user interaction
- NEVER use `transition: all` on elements with many properties — be explicit (use `--transition-color`, `--transition-opacity`)
---
## 9. Anti-Patterns & Constraints
1. **Hardcoding button `border-radius` as 8px or 12px → Why it fails → What to do instead.**
AI agents default to "reasonable" rounded corners (8–16px) for buttons. The Hello Bonsai system uses **pill-shaped primary CTAs at exactly 200px**. An 8px radius produces a completely wrong button shape that misrepresents the brand. Use `border-radius: 200px` (or `var(--radius-lg)`) on all primary and ghost buttons. Use `6px` (`var(--gaps-radius--default-border-radius)`) only on chips, cards, and tab items.
2. **Using `font-weight: 700` (bold) on headings → Why it fails → What to do instead.**
AI agents trained on general websites default to bold headings. All H1–H3 on Hello Bonsai use `font-weight: 500` (medium). Using 700 makes headings visually heavy and breaks the editorial, restrained aesthetic. Use `var(--font-weight-medium)` (500) for all headings. Reserve 700 for caption/xs text only.
3. **Using Inter, Roboto, or system-ui as the font → Why it fails → What to do instead.**
AI agents frequently inject Inter as a "modern default." This site loads Geist from a CDN. Inter will cause a visual mismatch in letter forms and spacing, especially at large display sizes where -1.12px letter spacing on Geist is calibrated for Geist's metrics. Always declare `font-family: Geist, Arial, sans-serif` and reference `var(--fonts--body-font)`.
4. **Inventing spacing values not in the token set (e.g. 8px, 16px, 32px) → Why it fails → What to do instead.**
The spacing scale does NOT follow the standard 8px grid. Bonsai uses: 6, 18, 24, 36, 96px. AI agents trained on 8px-grid systems will insert 8px, 16px, 32px gaps that look subtly wrong next to the actual components. Always use `--space-xs` (6px), `--space-sm` (18px), `--space-md` (24px), `--space-lg` (36px), or `--space-3xl` (96px).
5. **Applying the brand orange (#ff8308) as a button background colour → Why it fails → What to do instead.**
`--colors-brand--gradient-1` (#ff8308) is an accent/highlight colour extracted from a gradient definition. Primary CTA buttons are **black fill / white text**. Applying orange as a button background creates brand inconsistency. Use `--colors-interface--dark` (#000) for primary button fill. Use orange only for gradient accents, highlights, or decorative brand marks.
6. **Omitting hover opacity on nav links → Why it fails → What to do instead.**
AI agents often style nav links with colour changes on hover. Bonsai's nav links use `opacity: 0.5` on hover — the colour stays black, the element fades. Generating a colour-change hover will look wrong. Use `transition: opacity var(--duration-fast) var(--ease-default)` and set `opacity: 0.5` on `:hover`.
7. **Using `box-shadow` values beyond `--shadow-sm` → Why it fails → What to do instead.**
Only one shadow token exists. AI agents routinely layer multiple shadow values or use `0 4px 12px rgba(0,0,0,0.15)` (a common default) to create "visual depth." On a near-flat design system, any shadow heavier than `rgba(0,0,0,0.06) 0px 2px 6px` creates a jarring lift effect. Use only `var(--shadow-sm)` — or no shadow at all.
8. **Constructing dynamic Tailwind class names for colour or spacing → Why it fails → What to do instead.**
Even though Tailwind is detected as a library, the site's colour and spacing system lives in CSS custom properties, not Tailwind utilities. Dynamic class construction like `bg-${color}` or `p-${size}` will produce purged/missing styles at build time. Use `style={{ backgroundColor: 'var(--colors-interface--dark)' }}` in JSX, or define static utility classes that reference the CSS vars. Never construct `bg-[#ff8308]` — use `bg-[var(--colors-brand--gradient-1)]` if using Tailwind arbitrary values.
9. **Setting `font-weight: 600` → Why it fails → What to do instead.**
The Geist font is only loaded at weights 400 and 500. Font weight 700 is used once (on a cookie banner button). Weight 600 is not loaded and will produce browser faux-bold (stroke thickening) on Geist, breaking the typeface's intended character. Use only `400`, `500`, or `700` via the `--font-weight-*` tokens.
10. **Using absolute positioning for section layouts → Why it fails → What to do instead.**
The structural layout uses `flex` and (inferred) `grid` for all major sections. Absolute positioning for section-level layout breaks out of flow and causes reflow issues at responsive breakpoints (the site has 9 breakpoints: 300–992px). Use `display: flex` for navigation and component internals, `display: grid` for card grids and feature sections.
11. **Missing disabled state styling → Why it fails → What to do instead.**
AI agents commonly skip disabled states, leaving buttons at full opacity with pointer cursor on disabled. On Bonsai, disabled interactive elements should use `opacity: 0.5`, `cursor: not-allowed`, and `--colors-interface--neutral-2` as background. Always implement disabled state before shipping any interactive component.
---
## Appendix A: Complete Token Reference
Every token extracted from the source. §0 CORE TOKENS is the primary AI signal; this appendix is reference material an AI can cross-check against when a curated role is missing.
```css
/* Colours (26) */
--colors-interface--dark: #000;
--colors-interface--border-dark: #0000001a;
--colors-interface--colors-text--body-text: #0009;
--colors-interface--white: white;
--colors-interface--neutral-1: #f6f4f1;
--colors-interface--element-bg-white: #ffffff26;
--green: #22ad01;
--colors-interface--border-white: #fff3;
--colors-brand--gradient-1: #ff8308;
--colors-brand--gradient-2: #ff5043;
--colors-brand--gradient-3: #392bd5;
--colors-interface--colors-text--body-text-white: #fff9;
--colors-interface--element-bg-whiteelement-bg-dark: #0000004d;
--colors-text--faded-headings: #0006;
--colors-interface--neutral-2: #e8e6e3;
--beige: #f2f0ed;
--hellobonsai-bg-app: --colors-interface--element-bg-white;
--hellobonsai-bg-surface: --colors-interface--colors-text--body-text-white;
--hellobonsai-border: --colors-interface--border-dark;
--button-primary-bg: var(--colors-interface--dark);
--button-primary-bg-hover: var(--colors-interface--dark);
--card-bg: var(--colors-interface--white);
--chip-border: var(--colors-interface--border-dark);
--input-focus-border: rgb(56, 152, 236);
--transition-color: color var(--duration-fast) var(--ease-default);
--transition-bg: background-color var(--duration-fast) var(--ease-default);
/* Typography (36) */
--fonts--body-font: Geist, Arial, sans-serif;
--font-size-xs: 12px; /* 1 element — e.g. button "Learn more and custo" /* mined from computed styles */ */
--font-size-sm: 14px; /* 26 elements — e.g. span "Learn more about Bon", span "© 2026 Bonsai Techno", span "Your privacy choices" /* mined from computed styles */ */
--font-size-md: 17px; /* 51 elements — e.g. span "Manage leads and aut", span "Organize and monitor", span "Get financial clarit" /* mined from computed styles */ */
--font-size-lg: 20px; /* 13 elements — e.g. h3 "Automations", h3 "Reports", span "– from data migratio" /* mined from computed styles */ */
--font-size-xl: 50px; /* 10 elements — e.g. h2 "Streamline your clie", h2 "Deliver projects on ", h2 "Track revenue & simp" /* mined from computed styles */ */
--font-size-2xl: 56px; /* 1 element — e.g. h1 "The unified platform" /* mined from computed styles */ */
--font-weight-regular: 400; /* 32 elements — e.g. span "Learn more about Bon", span "© 2026 Bonsai Techno", span "Bonsai partners with" /* mined from computed styles */ */
--font-weight-medium: 500; /* 69 elements — e.g. h1 "The unified platform", h2 "Streamline your clie", h2 "Deliver projects on " /* mined from computed styles */ */
--font-weight-semibold: 700; /* 1 element — e.g. button "Learn more and custo" /* mined from computed styles */ */
--line-height-tight: 19.6px; /* 25 elements — e.g. span "Learn more about Bon", span "© 2026 Bonsai Techno", span "Bonsai partners with" /* mined from computed styles */ */
--line-height-normal: 23.8px; /* 52 elements — e.g. span "Manage leads and aut", span "Organize and monitor", span "Get financial clarit" /* mined from computed styles */ */
--line-height-loose: 26px; /* 13 elements — e.g. h3 "Automations", h3 "Reports", span "– from data migratio" /* mined from computed styles */ */
--font-size-xs: 12px;
--font-size-sm: 14px;
--font-size-md: 17px;
--font-size-lg: 20px;
--font-size-xl: 50px;
--font-size-2xl: 56px;
--font-weight-regular: 400;
--font-weight-medium: 500;
--font-weight-semibold: 700;
--line-height-tight: 19.6px;
--line-height-normal: 23.8px;
--line-height-loose: 26px;
--hellobonsai-text-primary: --colors-interface--dark;
--button-primary-text: var(--colors-interface--white);
--fonts--heading-font: Geist, Arial, sans-serif;
--fonts--badge-font: Geist, Arial, sans-serif;
--hellobonsai-font-size-xs: --font-size-xs;
--hellobonsai-font-size-sm: --font-size-sm;
--hellobonsai-font-size-md: --font-size-md;
--hellobonsai-font-size-lg: --font-size-lg;
--hellobonsai-font-size-xl: --font-size-xl;
--hellobonsai-font-size-2xl: --font-size-2xl;
--hellobonsai-font-sans: --fonts--body-font;
/* Spacing (24) */
--section-padding--default: 72px;
--section-padding--large: 120px;
--section-padding--small: 48px;
--navbar-height: 60px;
--content-width--container: 1428px;
--content-width--x-large: 1138px;
--content-width--small: 412px;
--content-width--medium: 654px;
--content-width--large: 896px;
--space-xs: 6px; /* 58 elements — e.g. div .review-grid-wrapper, div .review-grid-wrapper, div .review-grid-wrapper /* mined from computed styles */ */
--space-sm: 18px; /* 57 elements — e.g. div .app-card-body, div .app-card-body, div .app-card-body /* mined from computed styles */ */
--space-md: 24px; /* 68 elements — e.g. div .container, div .container, div .container /* mined from computed styles */ */
--space-lg: 36px; /* 33 elements — e.g. div .nav-menu-wrapper, div .nav-menu-wrapper, div .nav-menu-wrapper /* mined from computed styles */ */
--space-3xl: 96px; /* 19 elements — e.g. section .section, section .section, section .section /* mined from computed styles */ */
--space-xs: 6px;
--space-sm: 18px;
--space-md: 24px;
--space-lg: 36px;
--space-3xl: 96px;
--hellobonsai-space-xs: --space-xs;
--hellobonsai-space-sm: --space-sm;
--hellobonsai-space-md: --space-md;
--hellobonsai-space-lg: --space-lg;
--hellobonsai-space-3xl: --space-3xl;
/* Radius (11) */
--gaps-radius--default-border-radius: 6px;
--radius-sm: 4px; /* 3 elements — e.g. button .iubenda-cs-close-btn "×", button .iubenda-cs-close-btn "×", button .iubenda-cs-close-btn "×" /* mined from computed styles */ */
--radius-md: 64px; /* 9 elements — e.g. button .iubenda-cs-customize-btn "Learn more and custo", button .iubenda-cs-reject-btn "Reject", button .iubenda-cs-accept-btn "Accept" /* mined from computed styles */ */
--radius-lg: 200px; /* 4 elements — e.g. a .button "Book a demoBook a de", a .button "Get startedGet start", a .button "Try Bonsai for freeT" /* mined from computed styles */ */
--radius-lg: 200px;
--button-radius: 200px;
--card-border-radius: var(--gaps-radius--default-border-radius);
--chip-radius: var(--gaps-radius--default-border-radius);
--gaps-radius--bento-gap: 6px;
--hellobonsai-radius-lg: 200px;
--hellobonsai-radius-full: --gaps-radius--default-border-radius;
/* Effects (6) */
--shadow-sm: rgba(0, 0, 0, 0.06) 0px 2px 6px 0px; /* 9 elements — e.g. div .app-card, div .app-card, div .app-card /* mined from computed styles */ */
--shadow-sm: rgba(0,0,0,0.06) 0px 2px 6px 0px;
--card-shadow: var(--shadow-sm);
--shadow-md: [TBD] — not extracted, not in system */
/* --shadow-lg: [TBD] — not extracted, not in system */
/* ── Overlay ── */
--colors-interface--element-bg-white: #ffffff26;
--transition-opacity: opacity var(--duration-fast) var(--ease-default);
--hellobonsai-shadow-sm: --shadow-sm;
/* Motion (3) */
----motion-spin: @keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}; /* @keyframes spin */
--duration-fast: 0.2s; /* 93 elements — e.g. a, a, a /* mined from computed styles */ */
--ease-default: ease; /* 264 elements — e.g. button, button, button /* mined from computed styles */ */
```
## Appendix B: Token Source Metadata
```
tokenSource: extracted-css-vars
Confidence: HIGH — 30 CSS custom properties extracted directly from hellobonsai.com
Clustering method: Direct extraction; no reconstruction required for primary tokens.
```
**Extraction details:**
- **30 CSS custom properties** extracted directly from the site's stylesheet — these are authoritative.
- **Computed styles** extracted for 15 element types (h1, h2, h3, body, button_primary, link, card, toggle, modal, tab, badge, avatar, dropdown, role_tab, role_navigation, cls_chip) — used to supplement missing values and verify token application.
- **Interactive states** extracted from 40+ CSS rules — hover, focus, and active states documented from actual stylesheet, not inferred.
- **Border-radius census:** Pill (200px) confirmed on 4 main brand elements (`.button` links). 64px radius traced to iubenda cookie banner — excluded from brand token system. 6px is the canonical UI container radius.
- **Font loading:** Geist loaded at weights 400 and 500 only. Weight 700 present in extraction but NOT loaded as a Geist font file — applies to a single cookie banner button that may use faux-bold or a different rendering path.
- **Platform:** Webflow. CSS variable names follow Webflow's internal naming convention (`--colors-interface--*`, `--fonts--*`). Curated `--hellobonsai-*` aliases are semantic overlays for agent consumption — both naming systems are valid.
- **Libraries detected:** Tailwind and Bootstrap detected; however, the token system is CSS-custom-property-based, not utility-class-based. Tailwind arbitrary values (`bg-[var(--token)]`) are valid but prefer direct `var()` usage.More from the gallery
Browse all kits →You may also like

atlassian.com
MITEnterprise design system for Atlassian's marketing site, featuring bold brand colours, custom typography, and structured spacing tokens built for large-scale SaaS platforms
06
boldsaaslanding-pagelight

github.com
MITGitHub's production design system with 568 CSS custom properties, featuring clean typography, accessible colour tokens, and motion primitives built for developer platforms and collaborative workflows
04
darkdeveloper-toolsaas

Customer.io
MITDark SaaS design system with mint-green accents and clean typography, built for product dashboards and developer-focused platforms
011
darksaasdeveloper-tooldashboard