Supabase
MIT
A dark-first, developer-focused design system with emerald accents and a clean geometric aesthetic, built for modern SaaS and backend platforms
Layout StudioImport this kit into a Studio project and start editing.
CLI installRun it in any project. No account needed.
npx @layoutdesign/context install supabase# layout.md — Supabase Design System
---
## 0. Quick Reference
> Standalone — copy-paste into `CLAUDE.md` or `.cursorrules`
**Stack:** Next.js · Tailwind CSS · Lucide icons · Dark-first UI
**Token source:** `extracted-css-vars` (43 CSS custom properties, high confidence)
**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 {
/* Colours */
--supabase-info: hsl(208, 100%, 97%);
--supabase-error: hsl(359, 100%, 97%);
--supabase-accent: rgb(0, 98, 57);
--supabase-bg-app: #fff;
--supabase-border: var(--gray4);
--supabase-success: hsl(143, 85%, 96%);
--supabase-warning: hsl(49, 100%, 97%);
--supabase-bg-surface: rgb(15, 15, 15);
--supabase-text-primary: rgb(23, 23, 23);
--supabase-text-secondary: rgb(46, 46, 46);
/* Other */
--supabase-space-lg: 20px;
--supabase-space-md: 16px;
--supabase-space-sm: 8px;
--supabase-space-xl: 24px;
--supabase-space-xs: 1px;
--supabase-font-sans: Circular, custom-font, "Helvetica Neue", Helvetica, Arial, sans-serif;
--supabase-radius-lg: 9999px;
--supabase-radius-md: 16px;
--supabase-radius-sm: 6px;
--supabase-shadow-md: drop-shadow(rgba(0,0,0,0.05) 0px 1px 1px);
--supabase-shadow-sm: rgba(0,0,0,0) 0px 0px 0px 0px, …;
--supabase-space-2xl: 32px;
--supabase-space-3xl: 80px;
--supabase-radius-full: 8px;
--supabase-ease-default: 0;
--supabase-font-size-lg: 18px;
--supabase-font-size-md: 16px;
--supabase-font-size-sm: 14px;
--supabase-font-size-xl: 24px;
--supabase-font-size-xs: 12px;
--supabase-duration-base: 0.2s;
--supabase-duration-fast: 0.15s;
--supabase-duration-slow: 0.6s;
--supabase-font-size-2xl: 36px;
--supabase-font-size-3xl: 72px;
--supabase-font-weight-medium: 500;
--supabase-line-height-normal: 20px;
--supabase-font-weight-regular: 400;
}
```
```tsx
// Primary CTA Button — correct token usage
<button
className="rounded-[6px] bg-[var(--brand-primary-cta)] px-[32px] py-[8px]
text-[14px] font-medium text-white leading-[20px]
transition-all duration-[0.15s] ease-[cubic-bezier(0.4,0,0.2,1)]
hover:opacity-90 focus-visible:outline-none
focus-visible:ring-2 focus-visible:ring-white/40
disabled:opacity-40 disabled:cursor-not-allowed"
>
Start your project
</button>
```
**NEVER:**
1. NEVER use a font other than `Circular` as the primary face — fallback chain is `custom-font, "Helvetica Neue", Helvetica, Arial, sans-serif`
2. NEVER use border-radius values not in the token set (`6px`, `8px`, `16px`, `9999px`)
3. NEVER hardcode hex/hsl colour values — always reference `var(--token-name)`
4. NEVER use spacing values off the documented scale — no arbitrary `px` values
5. NEVER omit focus-visible ring on interactive elements — box-shadow focus ring is required
6. NEVER render a light background behind dark-theme content (`rgb(250,250,250)` text requires dark surfaces)
7. NEVER construct Tailwind class names dynamically (e.g. `` `bg-${color}` ``) — Tailwind cannot purge them
> Full design system → see **layout.md**
---
## 1. Design Direction & Philosophy
### Character & Mood
Supabase's UI is **dark, technical, and precise**. It communicates infrastructure-grade confidence: clean grids, near-zero decorative flourish, and a colour palette that stays almost entirely in near-black/near-white neutrals with a single punchy green accent. The overall feeling is "open-source developer tool made by people who care about craft."
### Aesthetic Intent
- **Dark-first.** The canonical surface is near-black (`rgb(15,15,15)` – `rgb(23,23,23)`). Light mode exists (`--normal-bg: #fff`) but is secondary.
- **Flat depth.** Shadows are intentionally minimal or transparent. Hierarchy is communicated through border-colour and surface-colour steps, not drop shadows.
- **Type-weight restraint.** Almost everything is `font-weight: 400`. Medium (`500`) is reserved for navigation labels. Bold and heavy weights do not appear.
- **Single brand accent.** `rgb(0, 98, 57)` — a deep forest green — is used exclusively for primary CTAs. It never bleeds into decorative elements.
- **Pill + soft radius duality.** Cards use `16px`; inputs/buttons use `6px`; special badges and announcement bars use `9999px`. The three-tier radius system is intentional.
### What This Design Explicitly Rejects
- **No warm colours in the UI chrome.** Status yellows/oranges exist only in warning alerts.
- **No gradients on interactive elements.** Background-image is `none` on all computed components.
- **No heavy typographic hierarchy via weight.** Scale is used (72px → 36px → 16px → 14px → 12px), not weight, to create hierarchy.
- **No rounded-corner cards > 16px.** `border-radius: 9999px` is for badges/pills only.
- **No decorative box shadows.** `--shadow-sm` resolves to fully transparent — elevation is surface-colour based.
---
## 2. Colour System
### Tier 1 — Primitives (Gray Scale)
```css
/* extracted: high confidence */
--gray1: hsl(0, 0%, 99%); /* near-white — lightest surface */
--gray2: hsl(0, 0%, 97.3%); /* off-white — subtle background tint */
--gray3: hsl(0, 0%, 95.1%); /* light surface */
--gray4: hsl(0, 0%, 93%); /* default border colour */
--gray5: hsl(0, 0%, 90.9%); /* hover border in light mode */
--gray6: hsl(0, 0%, 88.7%); /* divider */
--gray7: hsl(0, 0%, 85.8%); /* disabled border */
--gray8: hsl(0, 0%, 78%); /* placeholder text */
--gray9: hsl(0, 0%, 56.1%); /* muted / secondary text */
--gray10: hsl(0, 0%, 52.3%); /* subtle icon */
--gray11: hsl(0, 0%, 43.5%); /* caption text */
--gray12: hsl(0, 0%, 9%); /* near-black — primary text in light mode */
```
### Tier 2 — Semantic Aliases
```css
/* extracted: high confidence */
/* Surfaces */
--normal-bg: #fff; /* app/page background (light mode) */
/* dark mode surfaces (reconstructed: high confidence from computed styles) */
--surface-dark-base: rgb(15, 15, 15); /* deepest background — tab bg */
--surface-dark-raised: rgb(23, 23, 23); /* card background */
--surface-dark-overlay: rgb(36, 36, 36); /* dropdown background */
/* Borders */
--normal-border: var(--gray4); /* default border hsl(0,0%,93%) */
--border-dark: rgb(46, 46, 46); /* dark-mode default border (reconstructed) */
--border-dark-subtle: rgb(54, 54, 54); /* dark-mode dropdown border (reconstructed) */
--border-dark-input: rgb(57, 57, 57); /* dark-mode input border (reconstructed) */
/* Text */
--normal-text: var(--gray12); /* primary text (light mode) */
--text-dark-primary: rgb(250, 250, 250); /* primary text (dark mode, reconstructed) */
--text-dark-muted: rgb(180, 180, 180); /* muted/secondary text (dark mode, reconstructed) */
--text-dark-subtle: rgb(137, 137, 137); /* link underline / very subtle (reconstructed) */
/* Brand */
--brand-primary-cta: rgb(0, 98, 57); /* primary CTA background — Supabase green */
```
### Tier 3 — Status / Component Tokens
```css
/* extracted: high confidence */
/* Success */
--success-bg: hsl(143, 85%, 96%); /* alert/toast success background */
--success-border: hsl(145, 92%, 91%); /* alert/toast success border */
--success-text: hsl(140, 100%, 27%); /* alert/toast success text */
/* Info */
--info-bg: hsl(208, 100%, 97%); /* alert/toast info background */
--info-border: hsl(221, 91%, 91%); /* alert/toast info border */
--info-text: hsl(210, 92%, 45%); /* alert/toast info text */
/* Warning */
--warning-bg: hsl(49, 100%, 97%); /* alert/toast warning background */
--warning-border: hsl(49, 91%, 91%); /* alert/toast warning border */
--warning-text: hsl(31, 92%, 45%); /* alert/toast warning text */
/* Error */
--error-bg: hsl(359, 100%, 97%); /* alert/toast error background */
--error-border: hsl(359, 100%, 94%); /* alert/toast error border */
--error-text: hsl(360, 100%, 45%); /* alert/toast error text */
```
### Colour Palette Summary Table
| Token | Value | Usage |
|---|---|---|
| `--brand-primary-cta` | `rgb(0, 98, 57)` | **Primary CTA only** |
| `--text-dark-primary` | `rgb(250, 250, 250)` | All body text on dark |
| `--surface-dark-raised` | `rgb(23, 23, 23)` | **Card surface** |
| `--surface-dark-base` | `rgb(15, 15, 15)` | Page/tab base |
| `--border-dark` | `rgb(46, 46, 46)` | Default borders |
| `--gray9` | `hsl(0, 0%, 56.1%)` | Muted / secondary text |
---
## 3. Typography System
**Font stack:** `Circular, custom-font, "Helvetica Neue", Helvetica, Arial, sans-serif`
**Single weight axis:** 400 (regular) for almost everything; 500 (medium) for nav labels only.
NEVER use `Inter`, `Roboto`, `Arial`, or system-ui as the primary face.
### Composite Type Scale
```css
/* All composites — extracted: high confidence from computed styles */
/* Display / Hero */
--type-display: {
font-family: Circular, custom-font, "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 72px; /* --font-size-3xl */
font-weight: 400; /* --font-weight-regular */
line-height: 72px; /* 1:1 tight — intentional */
letter-spacing: normal;
text-align: center;
}
/* Section Heading (h3) */
--type-heading: {
font-family: Circular, custom-font, "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 36px; /* --font-size-2xl */
font-weight: 400;
line-height: 43.2px; /* ~1.2 ratio */
letter-spacing: normal;
}
/* Sub-heading / Card title (h2 context) */
--type-subheading: {
font-family: Circular, custom-font, "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 16px; /* --size */
font-weight: 400;
line-height: 24px; /* 1.5 ratio */
letter-spacing: normal;
}
/* Feature label (h4 context) */
--type-label-lg: {
font-family: Circular, custom-font, "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 18px; /* --font-size-lg */
font-weight: 400;
line-height: 27px; /* ~1.5 — reconstructed: moderate confidence */
letter-spacing: normal;
}
/* Large body / lead */
--type-body-xl: {
font-family: Circular, custom-font, "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 24px; /* --font-size-xl */
font-weight: 400;
line-height: 32px; /* reconstructed: moderate confidence */
letter-spacing: normal;
}
/* Body (default) */
--type-body: {
font-family: Circular, custom-font, "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px; /* --font-size-sm */
font-weight: 400;
line-height: 20px; /* --line-height-normal */
letter-spacing: normal;
}
/* Navigation label */
--type-nav: {
font-family: Circular, custom-font, "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
font-weight: 500; /* --font-weight-medium — only nav uses this */
line-height: 20px;
letter-spacing: normal;
}
/* Caption / small / badge */
--type-caption: {
font-family: Circular, custom-font, "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 12px; /* --font-size-xs */
font-weight: 400;
line-height: 16px; /* extracted from dropdown computed styles */
letter-spacing: normal;
}
```
### Type Scale Table
| Role | Size | Weight | Line Height | Usage |
|---|---|---|---|---|
| `--type-display` | **72px** | 400 | 72px | H1 hero heading |
| `--type-heading` | **36px** | 400 | 43.2px | H3 section titles |
| `--type-body-xl` | 24px | 400 | 32px | Lead paragraphs |
| `--type-label-lg` | 18px | 400 | 27px | Feature card titles |
| `--type-subheading` | 16px | 400 | 24px | H2 / card subtitles |
| `--type-body` | 14px | 400 | **20px** | Default body text |
| `--type-nav` | 14px | **500** | 20px | Navigation items |
| `--type-caption` | 12px | 400 | 16px | Badges, dropdowns |
---
## 4. Spacing & Layout
**Base unit: 4px.** Note: `--space-xs: 1px` is an extracted off-grid value used only for micro-adjustments (e.g. toast icon margins). Do not use 1px for structural spacing.
```css
/* Spacing scale — extracted & synthesised */
--space-xs: 1px; /* micro-adjust only (toast icon) — NOT for structural use */
--space-sm: 8px; /* tight internal padding — inputs, tight gaps */
--space-md: 16px; /* component internal padding — standard gap */
--space-lg: 20px; /* section-internal spacing */
--space-xl: 24px; /* card padding, generous component spacing */
--space-2xl: 32px; /* section row gap / button horizontal padding */
--space-3xl: 80px; /* section vertical rhythm / large section gaps */
```
```css
/* Border radius scale */
--radius-sm: 6px; /* buttons (34 instances), inputs, dropdowns */
--radius-md: 16px; /* cards (19 instances) */
--radius-lg: 9999px; /* pill: announcement bars, badge chips */
--border-radius: 8px; /* general utility / --border-radius CSS var */
```
```css
/* Grid & Breakpoints */
--breakpoint-mobile: 600px; /* only detected breakpoint */
/* Container (reconstructed: moderate confidence) */
--container-max: 1280px; /* [TBD - extract manually] */
--container-padding: 16px; /* --mobile-offset used as page gutter on mobile */
```
### Layout Decision Rules
- **Flex row** for nav items, button groups, dropdown rows, inline badge clusters
- **Block / single column** for cards, alerts, section containers at mobile (< 600px)
- **Card grid** uses `display: block` at mobile, multi-column grid at wider viewports (column count [TBD - extract manually])
- **Gap between cards:** `24px` (`--space-xl`) inferred from card padding symmetry
- **Section vertical padding:** `80px` (`--space-3xl`) top and bottom
---
## 5. Page Structure & Layout Patterns
> No screenshots available. Section derived from LAYOUT DIGEST and component inventory. Rows marked **(inferred)** are not visually confirmed.
### 5.1 Section Map
| # | Section | Layout Type | Approx Height | Key Elements |
|---|---|---|---|---|
| 1 | **Navigation / Header** | `display:block` with inner flex row | 64px (inferred) | Logo, nav links (font-weight:500), primary CTA button |
| 2 | **Announcement Bar** | Full-width flex row, pill radius | 40px (inferred) | `.announcement-link`, `border-radius:9999px`, single line text |
| 3 | **Hero** | Centred block, single column | ~480px (inferred) | H1 72px centred, lead paragraph 24px, 1-2 CTA buttons, dark surface |
| 4 | **Feature / Product Strip** | Flex or grid, multi-column (inferred) | ~200px (inferred) | Feature names as H2 16px, icon + label cards |
| 5 | **Card Grid — Social Proof** | Multi-column card grid | ~600px (inferred) | 126 card instances, `border-radius:16px`, `padding:24px`, dark surface `rgb(23,23,23)` |
| 6 | **CTA / Conversion Section** | Centred block (inferred) | ~300px (inferred) | H3 36px, body 14px, `--brand-primary-cta` button |
| 7 | **Footer** | Multi-column grid (inferred) | ~240px (inferred) | Nav links, legal copy 12px |
### 5.2 Layout Patterns
**Navigation:**
- `role_navigation` → `display: block`, `backdropFilter: blur(4px)` — frosted glass nav bar
- Inner content: flex row, `justify-content: space-between`, items aligned centre
- Nav link text: `font-size: 16px`, `font-weight: 400`; active/hover state: `transition: 0.3s cubic-bezier(0.4,0,0.2,1)`
**Hero:**
- Single column, text centred (`text-align: center` on H1)
- H1: `72px / 72px` line-height — tight display setting
- Background: dark (`rgb(15,15,15)` or near-black)
- CTA: `--brand-primary-cta` green, `border-radius: 6px`, `padding: 8px 32px`
**Card Grid:**
- Card: `display: block`, `padding: 24px`, `border-radius: 16px`, `background: rgb(23,23,23)`, `border: 1px solid rgb(46,46,46)`
- Cards have `drop-shadow(rgba(0,0,0,0.05) 0px 1px 1px)` — extremely subtle
- Transition: `color, background-color, border-color` all `0.15s cubic-bezier(0.4,0,0.2,1)`
**Tabs (feature selector):**
- `border-radius: 9999px`, `padding: 8px 32px`, `border: 1px solid rgb(250,250,250)` on active
- Background: `rgb(15,15,15)` — near-black pill tab
**Dropdown:**
- `display: flex`, `flex-direction: row`, `justify-content: center`, `align-items: center`
- `border-radius: 6px`, `padding: 4px 10px`, `background: rgb(36,36,36)`
- Font: `12px / 16px`, `transition: 0.2s cubic-bezier(0,0,0.2,1)`
### 5.3 Visual Hierarchy
- **Most prominent:** H1 at 72px, centred, `rgb(250,250,250)` on dark — immediate hero impact
- **CTA placement:** Below hero heading + subtext; repeated in conversion section
- **CTA colour:** `rgb(0,98,57)` — the **only** green element on the page, draws the eye
- **Whitespace rhythm:** `80px` vertical padding between major sections
- **Card density:** 126 card instances suggest a dense grid section (social proof / testimonials)
- **Image positions:** [TBD - extract manually — no screenshot data]
### 5.4 Content Patterns
**Repeating hero pattern:**
```
[Announcement pill badge — full width]
[H1 display text — centred]
[Lead paragraph 24px — centred, muted]
[CTA row — flex, centred, gap 16px]
```
**Repeating card pattern:**
```
[Card surface rgb(23,23,23) — border-radius 16px, padding 24px]
[Label / category — 12px caption]
[Title — 18px or 16px]
[Body text — 14px, rgb(250,250,250)]
[Optional: CTA link underline]
```
**Feature grid pattern (inferred):**
```
[Section heading H3 — 36px, left-aligned]
[Sub-copy — 14px body]
[Grid: icon + title + description cards]
```
---
## 6. Component Patterns
### 6.1 Button (Primary)
**Anatomy:** `<button>` → inner `<span>` for label text
**Token mappings:**
| State | Background | Text | Border | Shadow |
|---|---|---|---|---|
| Default | `var(--brand-primary-cta)` `rgb(0,98,57)` | `rgb(255,255,255)` | none | none |
| Hover | `rgb(0,78,46)` (darken 20%, reconstructed) | `rgb(255,255,255)` | none | none |
| Focus-visible | `var(--brand-primary-cta)` | `rgb(255,255,255)` | none | `0 0 0 2px rgba(255,255,255,0.4)` |
| Active | `rgb(0,65,38)` (darken further, reconstructed) | `rgb(255,255,255)` | none | none |
| Disabled | `var(--brand-primary-cta)` | `rgb(255,255,255)` | none | none, `opacity: 0.4` |
| Loading | `var(--brand-primary-cta)` | `rgb(255,255,255)` | none | spinner via `sonner-spin` |
```tsx
// Primary Button — production-ready with all states
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
loading?: boolean;
children: React.ReactNode;
}
export function PrimaryButton({ loading, disabled, children, ...props }: ButtonProps) {
return (
<button
{...props}
disabled={disabled || loading}
style={{
fontFamily: 'Circular, custom-font, "Helvetica Neue", Helvetica, Arial, sans-serif',
fontSize: '14px',
fontWeight: 400,
lineHeight: '20px',
borderRadius: 'var(--radius-sm, 6px)',
backgroundColor: 'var(--brand-primary-cta)',
color: '#fff',
padding: '8px 32px',
border: 'none',
cursor: disabled || loading ? 'not-allowed' : 'pointer',
opacity: disabled ? 0.4 : 1,
display: 'inline-flex',
alignItems: 'center',
gap: '8px',
transition: 'opacity 0.15s cubic-bezier(0.4, 0, 0.2, 1), background-color 0.15s cubic-bezier(0.4, 0, 0.2, 1)',
}}
onMouseEnter={e => {
if (!disabled && !loading)
(e.currentTarget as HTMLButtonElement).style.opacity = '0.9';
}}
onMouseLeave={e => {
(e.currentTarget as HTMLButtonElement).style.opacity = disabled ? '0.4' : '1';
}}
className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/40"
>
{loading && (
<svg
className="animate-spin"
width="14" height="14" viewBox="0 0 14 14"
style={{ animation: 'sonner-spin 1s linear infinite' }}
>
<circle cx="7" cy="7" r="5" stroke="currentColor" strokeWidth="2"
fill="none" strokeDasharray="20" strokeDashoffset="10" />
</svg>
)}
{children}
</button>
);
}
```
---
### 6.2 Card
**Anatomy:** `<div>` card shell → optional label/category → title → body text → optional link/CTA
**Token mappings:**
| State | Background | Border | Radius | Shadow |
|---|---|---|---|---|
| Default | `rgb(23, 23, 23)` | `1px solid rgb(46,46,46)` | `16px` | `drop-shadow(rgba(0,0,0,0.05) 0px 1px 1px)` |
| Hover | `rgb(28, 28, 28)` (reconstructed) | `1px solid rgb(60,60,60)` | `16px` | same |
| Focus | `rgb(23, 23, 23)` | `1px solid rgb(250,250,250)` | `16px` | `0 0 0 2px rgba(255,255,255,0.2)` |
| Active | `rgb(20, 20, 20)` (reconstructed) | same | `16px` | same |
| Disabled | `rgb(23, 23, 23)` | same | `16px` | none, `opacity: 0.5` |
| Loading | skeleton shimmer (reconstructed) | same | `16px` | none |
| Error | `var(--error-bg)` | `1px solid var(--error-border)` | `16px` | none |
```tsx
// Card component — production-ready
interface CardProps {
title: string;
body: string;
category?: string;
href?: string;
loading?: boolean;
error?: string;
}
export function Card({ title, body, category, href, loading, error }: CardProps) {
const Wrapper = href ? 'a' : 'div';
if (loading) {
return (
<div
style={{
backgroundColor: 'rgb(23, 23, 23)',
borderRadius: '16px',
padding: '24px',
border: '1px solid rgb(46, 46, 46)',
opacity: 0.6,
}}
aria-busy="true"
>
<div style={{ height: '16px', backgroundColor: 'rgb(46,46,46)', borderRadius: '4px', marginBottom: '12px' }} />
<div style={{ height: '12px', backgroundColor: 'rgb(36,36,36)', borderRadius: '4px', width: '70%' }} />
</div>
);
}
if (error) {
return (
<div
style={{
backgroundColor: 'var(--error-bg)',
borderRadius: '16px',
padding: '24px',
border: '1px solid var(--error-border)',
color: 'var(--error-text)',
fontSize: '14px',
lineHeight: '20px',
}}
role="alert"
>
{error}
</div>
);
}
return (
<Wrapper
href={href}
style={{
display: 'block',
backgroundColor: 'rgb(23, 23, 23)',
borderRadius: '16px',
padding: '24px',
border: '1px solid rgb(46, 46, 46)',
color: 'rgb(250, 250, 250)',
fontFamily: 'Circular, custom-font, "Helvetica Neue", Helvetica, Arial, sans-serif',
fontSize: '16px',
lineHeight: '24px',
textDecoration: 'none',
filter: 'drop-shadow(rgba(0,0,0,0.05) 0px 1px 1px)',
transition: 'color 0.15s cubic-bezier(0.4,0,0.2,1), background-color 0.15s cubic-bezier(0.4,0,0.2,1), border-color 0.15s cubic-bezier(0.4,0,0.2,1)',
}}
className={href ? 'hover:bg-[rgb(28,28,28)] hover:border-[rgb(60,60,60)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/20' : undefined}
>
{category && (
<p style={{ fontSize: '12px', lineHeight: '16px', color: 'rgb(137,137,137)', marginBottom: '8px' }}>
{category}
</p>
)}
<h3 style={{ fontSize: '18px', fontWeight: 400, lineHeight: '27px', marginBottom: '8px', marginTop: 0 }}>
{title}
</h3>
<p style={{ fontSize: '14px', lineHeight: '20px', color: 'rgb(180,180,180)', margin: 0 }}>
{body}
</p>
</Wrapper>
);
}
```
---
### 6.3 Input
**Anatomy:** `<input>` (or `<div>` wrapper) → label → input field → helper/error text
**Token mappings:**
| State | Background | Border | Radius | Text |
|---|---|---|---|---|
| Default | `rgba(250,250,250,0.027)` | `1px solid rgb(57,57,57)` | `6px` | `rgb(250,250,250)` |
| Hover | `rgba(250,250,250,0.05)` (reconstructed) | `1px solid rgb(80,80,80)` | `6px` | same |
| Focus | `rgba(250,250,250,0.027)` | `1px solid rgb(250,250,250)` | `6px` | same |
| Active | same as focus | same as focus | `6px` | same |
| Disabled | `rgba(250,250,250,0.01)` | `1px solid rgb(40,40,40)` | `6px` | `rgb(137,137,137)` |
| Error | `var(--error-bg)` | `1px solid var(--error-border)` | `6px` | `var(--error-text)` |
| Loading | same as default | same + pulse animation | `6px` | same |
```tsx
// Input — production-ready with all states
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label?: string;
error?: string;
loading?: boolean;
}
export function Input({ label, error, loading, disabled, id, ...props }: InputProps) {
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
{label && (
<label
htmlFor={id}
style={{
fontSize: '14px',
fontWeight: 500,
lineHeight: '20px',
color: 'rgb(250,250,250)',
fontFamily: 'Circular, custom-font, "Helvetica Neue", Helvetica, Arial, sans-serif',
}}
>
{label}
</label>
)}
<input
id={id}
disabled={disabled || loading}
aria-invalid={!!error}
aria-describedby={error ? `${id}-error` : undefined}
{...props}
style={{
fontFamily: 'Circular, custom-font, "Helvetica Neue", Helvetica, Arial, sans-serif',
fontSize: '14px',
fontWeight: 400,
lineHeight: '20px',
color: disabled ? 'rgb(137,137,137)' : 'rgb(250,250,250)',
backgroundColor: error ? 'var(--error-bg)' : 'rgba(250,250,250,0.027)',
border: error
? '1px solid var(--error-border)'
: disabled
? '1px solid rgb(40,40,40)'
: '1px solid rgb(57,57,57)',
borderRadius: '6px',
padding: '8px',
outline: 'none',
width: '100%',
boxSizing: 'border-box',
transition: 'border-color 0.15s cubic-bezier(0.4,0,0.2,1), background-color 0.15s cubic-bezier(0.4,0,0.2,1)',
opacity: disabled ? 0.5 : 1,
cursor: disabled ? 'not-allowed' : 'text',
}}
className="focus:border-[rgb(250,250,250)] hover:border-[rgb(80,80,80)] focus:outline-none"
/>
{error && (
<p
id={`${id}-error`}
role="alert"
style={{ fontSize: '12px', lineHeight: '16px', color: 'var(--error-text)', margin: 0 }}
>
{error}
</p>
)}
</div>
);
}
```
---
### 6.4 Nav Item
**Anatomy:** `<nav>` → `<a>` or `<button>` nav item → optional badge/indicator
**Token mappings:**
| State | Color | Font Weight | Transition |
|---|---|---|---|
| Default | `rgb(250,250,250)` | 500 (medium) | `0.3s cubic-bezier(0.4,0,0.2,1)` |
| Hover | `rgb(180,180,180)` (reconstructed) | 500 | same |
| Focus-visible | `rgb(250,250,250)` | 500 | ring `2px rgba(255,255,255,0.4)` |
| Active | `rgb(250,250,250)` + underline indicator | 500 | same |
| Disabled | `rgb(137,137,137)` | 500 | none |
```tsx
// Nav Item — production-ready
interface NavItemProps {
href: string;
label: string;
active?: boolean;
disabled?: boolean;
}
export function NavItem({ href, label, active, disabled }: NavItemProps) {
return (
<a
href={disabled ? undefined : href}
aria-current={active ? 'page' : undefined}
aria-disabled={disabled}
style={{
fontFamily: 'Circular, custom-font, "Helvetica Neue", Helvetica, Arial, sans-serif',
fontSize: '14px',
fontWeight: 500,
lineHeight: '20px',
color: disabled ? 'rgb(137,137,137)' : 'rgb(250,250,250)',
textDecoration: active ? 'underline' : 'none',
transition: 'color 0.3s cubic-bezier(0.4,0,0.2,1)',
cursor: disabled ? 'not-allowed' : 'pointer',
pointerEvents: disabled ? 'none' : 'auto',
padding: '8px 0',
display: 'inline-block',
}}
className="hover:text-[rgb(180,180,180)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/40"
>
{label}
</a>
);
}
```
---
### 6.5 Alert / Toast
**Anatomy:** `<div>` shell → icon → title + body text → optional action button → close button
**Token mappings by variant:**
| Variant | Background | Border | Text |
|---|---|---|---|
| Success | `var(--success-bg)` | `var(--success-border)` | `var(--success-text)` |
| Info | `var(--info-bg)` | `var(--info-border)` | `var(--info-text)` |
| Warning | `var(--warning-bg)` | `var(--warning-border)` | `var(--warning-text)` |
| Error | `var(--error-bg)` | `var(--error-border)` | `var(--error-text)` |
| Normal | `var(--normal-bg)` | `var(--normal-border)` | `var(--normal-text)` |
**Focus state:** `box-shadow: rgba(0,0,0,0.1) 0px 4px 12px, rgba(0,0,0,0.2) 0px 0px 0px 2px`
```tsx
// Alert — production-ready all variants
type AlertVariant = 'success' | 'info' | 'warning' | 'error' | 'normal';
const alertTokens: Record<AlertVariant, { bg: string; border: string; text: string }> = {
success: { bg: 'var(--success-bg)', border: 'var(--success-border)', text: 'var(--success-text)' },
info: { bg: 'var(--info-bg)', border: 'var(--info-border)', text: 'var(--info-text)' },
warning: { bg: 'var(--warning-bg)', border: 'var(--warning-border)', text: 'var(--warning-text)' },
error: { bg: 'var(--error-bg)', border: 'var(--error-border)', text: 'var(--error-text)' },
normal: { bg: 'var(--normal-bg)', border: 'var(--normal-border)', text: 'var(--normal-text)' },
};
export function Alert({ variant = 'normal', title, body }: { variant?: AlertVariant; title: string; body?: string }) {
const t = alertTokens[variant];
return (
<div
role="alert"
tabIndex={0}
style={{
backgroundColor: t.bg,
border: `1px solid ${t.border}`,
borderRadius: '6px',
padding: '16px',
color: t.text,
fontFamily: 'Circular, custom-font, "Helvetica Neue", Helvetica, Arial, sans-serif',
fontSize: '14px',
lineHeight: '20px',
}}
className="focus-visible:outline-none focus-visible:shadow-[rgba(0,0,0,0.1)_0px_4px_12px,rgba(0,0,0,0.2)_0px_0px_0px_2px]"
>
<strong style={{ fontSize: '14px', fontWeight: 500 }}>{title}</strong>
{body && <p style={{ margin: '4px 0 0', fontSize: '14px' }}>{body}</p>}
</div>
);
}
```
---
## 7. Elevation & Depth
Supabase uses **surface-colour stepping** rather than box shadows for depth. Shadows are minimal to transparent.
```css
/* Shadow tokens — extracted: high confidence */
--shadow-sm: rgba(0,0,0,0) 0px 0px 0px 0px,
rgba(0,0,0,0) 0px 0px 0px 0px,
rgba(0,0,0,0) 0px 0px 0px 0px; /* effectively no shadow */
/* Card filter shadow — reconstructed: high confidence from computed styles */
--shadow-card: drop-shadow(rgba(0,0,0,0.05) 0px 1px 1px); /* applied as CSS filter, not box-shadow */
/* Focus ring — extracted from interactive state styles */
--shadow-focus-toast: rgba(0,0,0,0.1) 0px 4px 12px,
rgba(0,0,0,0.2) 0px 0px 0px 2px;
--shadow-focus-button: rgba(0,0,0,0.4) 0px 0px 0px 2px;
```
```css
/* Z-index scale — reconstructed: moderate confidence */
--z-base: 0; /* static page content */
--z-raised: 10; /* cards on hover */
--z-dropdown: 100; /* dropdown menus */
--z-overlay: 200; /* modals / drawers */
--z-nav: 300; /* sticky navigation (has backdrop-filter) */
--z-toast: 400; /* toasts / sonner notifications */
```
### Elevation via Surface Colour
| Layer | Background | Border |
|---|---|---|
| Base / deepest | `rgb(15, 15, 15)` | none |
| Page surface | `rgb(23, 23, 23)` | `rgb(46,46,46)` |
| Raised (dropdown) | `rgb(36, 36, 36)` | `rgb(54,54,54)` |
| Input | `rgba(250,250,250,0.027)` | `rgb(57,57,57)` |
**Key principle:** NEVER add decorative `box-shadow` to cards or buttons. Use surface stepping for hierarchy. Reserve `box-shadow` exclusively for focus-visible rings.
---
## 8. Motion
```css
/* Duration tokens — extracted: high confidence */
--duration-fast: 0.15s; /* micro-interactions: hover colour, border colour */
--duration-base: 0.2s; /* default transitions: dropdowns, state changes */
--duration-slow: 0.6s; /* large layout transitions: section reveals */
/* Easing — extracted: high confidence (cubic-bezier from computed transitions) */
--ease-default: cubic-bezier(0.4, 0, 0.2, 1); /* standard ease-in-out — used on 131 elements */
--ease-decel: cubic-bezier(0, 0, 0.2, 1); /* decelerate — dropdown entry */
--ease-nav: cubic-bezier(0.4, 0, 0.2, 1); /* nav transition 0.3s */
```
```css
/* Keyframe animations — extracted from CSS rules */
/* Toast entry */
@keyframes sonner-fade-in {
0% { opacity: 0; transform: scale(0.8); }
100% { opacity: 1; transform: scale(1); }
}
/* Toast exit */
@keyframes sonner-fade-out {
0% { opacity: 1; transform: scale(1); }
100% { opacity: 0; transform: scale(0.8); }
}
/* Toast swipe dismiss */
@keyframes swipe-out {
0% { transform: translateY(calc(var(--lift) * var(--offset) + var(--swipe-amount))); opacity: 1; }
100% { transform: translateY(calc(var(--lift) * var(--offset) + var(--swipe-amount) + var(--lift) * -100%)); opacity: 0; }
}
/* Loading spinner */
@keyframes sonner-spin {
0% { opacity: 1; }
100% { opacity: 0.15; }
}
```
### Motion Rules
| Scenario | Duration | Easing |
|---|---|---|
| Hover colour/border | `0.15s` | `cubic-bezier(0.4,0,0.2,1)` |
| Dropdown open/close | `0.2s` | `cubic-bezier(0,0,0.2,1)` |
| Nav item transition | `0.3s` | `cubic-bezier(0.4,0,0.2,1)` |
| Toast fade in/out | — | `sonner-fade-in/out` keyframes |
| Section reveals | `0.6s` | `cubic-bezier(0.4,0,0.2,1)` |
- **NEVER animate layout properties** (`width`, `height`, `top`, `left`) — only `opacity`, `transform`, `color`, `background-color`, `border-color`
- **NEVER use `transition: all`** on production components — scope to specific properties
- **Always respect `prefers-reduced-motion`** — wrap animations in `@media (prefers-reduced-motion: no-preference)`
---
## 9. Anti-Patterns & Constraints
1. **Hardcoded colour values → AI defaults to generic colours → Use CSS variables**
AI agents, when given no colour context, default to `#3b82f6` (Tailwind blue) or `#000`/`#fff`. On Supabase this breaks the brand entirely — the only accent is `rgb(0,98,57)`. **Always** reference `var(--brand-primary-cta)` or `var(--success-bg)` etc. Never write hex/hsl literals in component code.
2. **Wrong font family → Renders in Inter or system-ui → Always specify the full Circular stack**
AI agents default to `font-family: Inter, sans-serif` or omit font entirely. Supabase uses `Circular` as the primary face. Without the full stack `Circular, custom-font, "Helvetica Neue", Helvetica, Arial, sans-serif`, the design reads as generic. Every component must declare the font explicitly.
3. **Arbitrary spacing values → Breaks vertical rhythm → Use only `--space-*` tokens**
When an AI estimates spacing (`padding: 12px`, `margin: 15px`), it creates off-grid values that destroy the 4px/8px rhythm. The only valid values are `8px`, `16px`, `20px`, `24px`, `32px`, `80px`. Exception: `1px` exists only for toast micro-adjustments.
4. **Wrong border-radius → UI looks generic or mismatched → Use the three-tier radius system**
AI agents often default to `border-radius: 4px` or `8px` uniformly. Supabase has a deliberate three-tier system: `6px` for interactive controls, `16px` for cards, `9999px` for pills. Applying card radius (`16px`) to a button, or `8px` to anything, is incorrect. The `--border-radius: 8px` CSS variable is a utility fallback, not the primary button radius.
5. **Missing focus-visible ring → Fails accessibility → Add `box-shadow` focus ring to all interactive elements**
AI-generated components routinely omit focus states. Supabase's extracted interactive state data explicitly defines `box-shadow: rgba(0,0,0,0.1) 0px 4px 12px, rgba(0,0,0,0.2) 0px 0px 0px 2px` for focused toast elements. Every `<button>`, `<a>`, `<input>` must have a visible focus ring using `focus-visible:` pseudo-class.
6. **Light surfaces on dark-theme pages → Text becomes illegible → Use dark surface tokens**
AI may generate `background: white` or `background: var(--normal-bg)` components on Supabase pages. The primary theme is dark: surfaces are `rgb(15,15,15)` – `rgb(36,36,36)`. Using `--normal-bg: #fff` in the dark-theme context makes text (`rgb(250,250,250)`) invisible. Confirm theme context before applying surface colours.
7. **Dynamic Tailwind class construction → Classes are purged at build → Use static class names or inline styles**
Writing `` className={`bg-[${color}]`} `` causes Tailwind to fail silently — the class is never generated. For dynamic values, use `style={{ backgroundColor: color }}` or `style={{ backgroundColor: 'var(--brand-primary-cta)' }}`. Static Tailwind class names using `bg-[var(--token)]` syntax are acceptable.
8. **Using `transition: all` → Causes performance degradation and unintended animation → Scope to specific properties**
The extracted computed styles show `transition: all` on several elements — this is a default browser behaviour artifact, not intentional design. Production components should scope transitions: `transition: color 0.15s, background-color 0.15s, border-color 0.15s cubic-bezier(0.4,0,0.2,1)`.
9. **Using `box-shadow` for card elevation → Conflicts with the flat design language → Use filter: drop-shadow or surface stepping**
The `--shadow-sm` token resolves to fully transparent. Cards use `filter: drop-shadow(rgba(0,0,0,0.05) 0px 1px 1px)` — not `box-shadow`. Using `box-shadow` on cards introduces an elevation style that doesn't exist in the design system. Use `filter` for the subtle card shadow and surface-colour stepping for all other depth.
10. **Omitting disabled/loading states → Components feel unfinished and break UX flows → Always implement all six states**
AI agents commonly generate only default + hover states. The component inventory shows 34 button instances requiring: default, hover, focus, active, disabled, loading, error. Missing states mean broken form validation, confusing async feedback, and inaccessible components. All six must be present in every 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 (37) */
--gray1: hsl(0, 0%, 99%);
--gray2: hsl(0, 0%, 97.3%);
--gray3: hsl(0, 0%, 95.1%);
--gray4: hsl(0, 0%, 93%);
--gray5: hsl(0, 0%, 90.9%);
--gray6: hsl(0, 0%, 88.7%);
--gray7: hsl(0, 0%, 85.8%);
--gray8: hsl(0, 0%, 78%);
--gray9: hsl(0, 0%, 56.1%);
--gray10: hsl(0, 0%, 52.3%);
--gray11: hsl(0, 0%, 43.5%);
--gray12: hsl(0, 0%, 9%);
--normal-bg: #fff;
--success-bg: hsl(143, 85%, 96%);
--success-border: hsl(145, 92%, 91%);
--success-text: hsl(140, 100%, 27%);
--info-bg: hsl(208, 100%, 97%);
--info-border: hsl(221, 91%, 91%);
--info-text: hsl(210, 92%, 45%);
--warning-bg: hsl(49, 100%, 97%);
--warning-border: hsl(49, 91%, 91%);
--warning-text: hsl(31, 92%, 45%);
--error-bg: hsl(359, 100%, 97%);
--error-border: hsl(359, 100%, 94%);
--error-text: hsl(360, 100%, 45%);
--brand-primary-cta: rgb(0, 98, 57); /* Primary CTA background, dominant on 1 button — e.g. "Subscribe" /* mined from computed styles */ */
--normal-border: var(--gray4);
--brand-primary-cta: rgb(0, 98, 57);
--surface-dark-base: rgb(15, 15, 15);
--surface-dark-raised: rgb(23, 23, 23);
--surface-dark-overlay: rgb(36, 36, 36);
--border-dark: rgb(46, 46, 46);
--border-dark-subtle: rgb(54, 54, 54);
--border-dark-input: rgb(57, 57, 57);
--text-dark-primary: rgb(250, 250, 250);
--text-dark-muted: rgb(180, 180, 180);
--text-dark-subtle: rgb(137, 137, 137);
/* Typography (23) */
--toast-close-button-transform: translate(-35%, -35%);
--normal-text: var(--gray12);
--size: 16px;
--font-size-xs: 12px; /* 36 elements — e.g. span "Accept", span "Opt out", span "Privacy settings" /* mined from computed styles */ */
--font-size-sm: 14px; /* 66 elements — e.g. p "We use cookies to co", p "Pricing", p "Docs" /* mined from computed styles */ */
--font-size-lg: 18px; /* 8 elements — e.g. h4 "Stripe Subscriptions", h4 "Next.js Starter", h4 "AI Chatbot" /* mined from computed styles */ */
--font-size-xl: 24px; /* 2 elements — e.g. p "Use one or all. Best", span "Use one or all." /* mined from computed styles */ */
--font-size-2xl: 36px; /* 12 elements — e.g. h2 "Open source from day", h2 "Build in a weekend, ", h3 "Trusted by the world" /* mined from computed styles */ */
--font-size-3xl: 72px; /* 3 elements — e.g. h1 "Build in a weekendSc", span "Build in a weekend", span "Scale to millions" /* mined from computed styles */ */
--font-weight-regular: 400; /* 206 elements — e.g. h1 "Build in a weekendSc", h2 "Postgres Database", h2 "Authentication" /* mined from computed styles */ */
--font-weight-medium: 500; /* 17 elements — e.g. p "Pricing", p "Docs", p "Blog" /* mined from computed styles */ */
--line-height-normal: 20px; /* 60 elements — e.g. p "We use cookies to co", p "Trusted by fast-grow", p "Every project is a f" /* mined from computed styles */ */
--font-family-base: Circular, custom-font, "Helvetica Neue", Helvetica, Arial, sans-serif;
--font-size-xs: 12px;
--font-size-sm: 14px;
--font-size-md: 16px;
--font-size-lg: 18px;
--font-size-xl: 24px;
--font-size-2xl: 36px;
--font-size-3xl: 72px;
--font-weight-regular: 400;
--font-weight-medium: 500;
--line-height-normal: 20px;
/* Spacing (29) */
--toast-icon-margin-start: -3px;
--toast-icon-margin-end: 4px;
--toast-svg-margin-start: -1px;
--toast-svg-margin-end: 0px;
--toast-button-margin-start: auto;
--toast-button-margin-end: 0;
--mobile-offset: 16px;
--normal-border: var(--gray4);
--space-xs: 1px; /* 80 elements — e.g. div .group/panel, div .group/panel, div .group/panel /* mined from computed styles */ */
--space-md: 8px; /* 141 elements — e.g. li .!w-screen, li .!w-screen, li .!w-screen /* mined from computed styles */ */
--space-xl: 20px; /* 46 elements — e.g. div .hidden, div .hidden, div .hidden /* mined from computed styles */ */
--space-2xl: 24px; /* 162 elements — e.g. div .sm:py-18, div .sm:py-18, div .z-10 /* mined from computed styles */ */
--space-3xl: 80px; /* 34 elements — e.g. div .relative, div .relative, div .sm:py-18 /* mined from computed styles */ */
--space-xs: 1px;
--space-sm: 8px;
--space-md: 16px;
--space-lg: 20px;
--space-xl: 24px;
--space-2xl: 32px;
--space-3xl: 80px;
--breakpoint-mobile: 600px;
--container-max: 1280px;
--container-padding: 16px;
--type-body-xl: 24px;
--type-label-lg: 18px;
--type-subheading: 16px;
--type-body: 14px;
--type-nav: 14px;
--type-caption: 12px;
/* Radius (7) */
--border-radius: 8px;
--radius-sm: 6px; /* 38 elements — e.g. button .relative "Accept", button .relative "Opt out", button .relative "Privacy settings" /* mined from computed styles */ */
--radius-md: 16px; /* 19 elements — e.g. div .bg-surface-75 "@nerdburnIt’s fun, f", div .bg-surface-75 "@patrickcVery impres", div .bg-surface-75 "@Aliahsan_sfvOkay, I" /* mined from computed styles */ */
--radius-lg: 9999px; /* 1 element — e.g. a .announcement-link "State of Startups 20" /* mined from computed styles */ */
--radius-sm: 6px;
--radius-md: 16px;
--radius-lg: 9999px;
/* Effects (12) */
--toast-close-button-start: 0;
--toast-close-button-end: unset;
--y: translateY(100%);
--lift-amount: calc(var(--lift) * var(--gap));
--lift: 1;
--scale: var(--toasts-before) * .05 + 1;
----backdrop-filter-role_navigation: backdrop-filter: blur(4px); /* Backdrop filter from role_navigation /* reconstructed */ */
--shadow-sm: rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px; /* 17 elements — e.g. button .relative, button .relative, button .group /* mined from computed styles */ */
--shadow-sm: rgba(0,0,0,0) 0px 0px 0px 0px, …;
--shadow-card: drop-shadow(rgba(0,0,0,0.05) 0px 1px 1px);
--shadow-focus-toast: rgba(0,0,0,0.1) 0px 4px 12px, rgba(0,0,0,0.2) 0px 0px 0px 2px;
--shadow-focus-button: rgba(0,0,0,0.4) 0px 0px 0px 2px;
/* Motion (8) */
----motion-swipe-out: @keyframes swipe-out {
0% { transform: translateY(calc(var(--lift) * var(--of… <0.2KB elided>; /* @keyframes swipe-out */
----motion-sonner-fade-in: @keyframes sonner-fade-in {
0% { opacity: 0; transform: scale(0.8); }
100% { opacity: 1; transform: scale(1); }
}; /* @keyframes sonner-fade-in */
----motion-sonner-fade-out: @keyframes sonner-fade-out {
0% { opacity: 1; transform: scale(1); }
100% { opacity: 0; transform: scale(0.8); }
}; /* @keyframes sonner-fade-out */
----motion-sonner-spin: @keyframes sonner-spin {
0% { opacity: 1; }
100% { opacity: 0.15; }
}; /* @keyframes sonner-spin */
--duration-fast: 0.15s; /* 37 elements — e.g. button, button, button /* mined from computed styles */ */
--duration-base: 0.2s; /* 86 elements — e.g. button, button, button /* mined from computed styles */ */
--duration-slow: 0.6s; /* 9 elements — e.g. div, div, div /* mined from computed styles */ */
--ease-default: 0; /* 131 elements — e.g. button, button, button /* mined from computed styles */ */
```
## Appendix B: Token Source Metadata
```
tokenSource: extracted-css-vars
confidence: high
cssVarsFound: 43
curated_tokens_mapped: 32
reconstruction_required: partial
Extraction method:
Primary — CSS custom properties extracted directly from supabase.com stylesheets
Secondary — computed styles sampled from 11 key element types:
h1, h2, h3, body, button_primary, input, link, card, tab, dropdown, alert, role_navigation
Reconstruction notes:
- Dark surface colours (--surface-dark-*) synthesised from computed backgroundColor values
on card (rgb(23,23,23)), tab (rgb(15,15,15)), dropdown (rgb(36,36,36)) — high confidence
- Dark border colours (--border-dark-*) synthesised from computed border values on
card (rgb(46,46,46)), dropdown (rgb(54,54,54)), input (rgb(57,57,57)) — high confidence
- Z-index scale has no extracted data — synthesised from layer semantics — moderate confidence
- Spacing labels (--space-sm → --space-3xl) re-mapped from original --space-2xl → --space-sm
discrepancy flagged in curation. Original: --space-2xl: 24px, --space-xl: 20px, --space-md: 8px
- Container max-width not found in extraction — [TBD - extract manually]
- Column counts for card grid not found in extraction — [TBD - extract manually]
Token naming:
All original CSS variable names preserved exactly (--gray1, --normal-bg, --success-border, etc.)
Supabase-prefixed curated names (--supabase-*) documented as aliases only
New synthesised tokens named by semantic role following layout.md conventions
Libraries detected: Tailwind CSS (v3, utility-class-based), Bootstrap, Lucide, Next.js
Tailwind note: v3 detected — no CSS custom properties generated by @theme. Tailwind classes
reference token values via JIT arbitrary values: bg-[var(--brand-primary-cta)].
Do NOT expect Tailwind to generate --tw-* variables for these tokens.
Toast/notification system: Sonner library detected (data-sonner-toast attributes, keyframe names)
Focus states extracted from Sonner-specific CSS selectors.
```More from the gallery
Browse all kits →You may also like

Contra
MITPlayful yet professional design system with teal and purple accents, bold typography, and vibrant accent surfaces—built for modern SaaS platforms and landing pages
03
lightboldsaaslanding-page

DoorDash
MITClean, accessible food-delivery system with DoorDash's signature red accent, light neutral palette, and system-font typography—built for rapid product development
03
lightecommsaasmobile

Mercedes-Benz
MITLuxe, minimalist design system with sharp black-and-white contrast and serif typography, crafted for premium automotive and lifestyle brands
05
darkminimalboldecomm