Supabase
MIT
A dark-first, developer-focused design system with emerald accents and a clean geometric aesthetic, built for modern SaaS and backend platforms
Colour (46)
color.gray1hsl(0, 0%, 99%)
color.gray2hsl(0, 0%, 97.3%)
color.gray3hsl(0, 0%, 95.1%)
color.gray4hsl(0, 0%, 93%)
color.gray5hsl(0, 0%, 90.9%)
color.gray6hsl(0, 0%, 88.7%)
color.gray7hsl(0, 0%, 85.8%)
color.gray8hsl(0, 0%, 78%)
color.gray9hsl(0, 0%, 56.1%)
color.gray10hsl(0, 0%, 52.3%)
color.gray11hsl(0, 0%, 43.5%)
color.gray12hsl(0, 0%, 9%)
color.infobghsl(208, 100%, 97%)
color.errorbghsl(359, 100%, 97%)
color.infotexthsl(210, 92%, 45%)
color.normalbg#fff
color.errortexthsl(360, 100%, 45%)
color.successbghsl(143, 85%, 96%)
color.warningbghsl(49, 100%, 97%)
color.borderdarkrgb(46, 46, 46)
color.infoborderhsl(221, 91%, 91%)
color.errorborderhsl(359, 100%, 94%)
color.successtexthsl(140, 100%, 27%)
color.warningtexthsl(31, 92%, 45%)
color.normalbordervar(--gray4)
color.supabaseinfohsl(208, 100%, 97%)
color.successborderhsl(145, 92%, 91%)
color.supabasebgapp#fff
color.supabaseerrorhsl(359, 100%, 97%)
color.textdarkmutedrgb(180, 180, 180)
color.warningborderhsl(49, 91%, 91%)
color.supabaseaccentrgb(0, 98, 57)
color.supabasebordervar(--gray4)
color.textdarksubtlergb(137, 137, 137)
color.borderdarkinputrgb(57, 57, 57)
color.brandprimaryctargb(0, 98, 57)
color.supabasesuccesshsl(143, 85%, 96%)
color.supabasewarninghsl(49, 100%, 97%)
color.surfacedarkbasergb(15, 15, 15)
color.textdarkprimaryrgb(250, 250, 250)
color.borderdarksubtlergb(54, 54, 54)
color.supabasebgsurfacergb(15, 15, 15)
color.surfacedarkraisedrgb(23, 23, 23)
color.surfacedarkoverlayrgb(36, 36, 36)
color.supabasetextprimaryrgb(23, 23, 23)
color.supabasetextsecondaryrgb(46, 46, 46)
Spacing (32)
spacing.toasticonmarginstart-3px
spacing.toastsvgmarginstart-1px
spacing.toastsvgmarginend0px
spacing.toastbuttonmarginend0
spacing.toastbuttonmarginstartauto
spacing.spacexs1px
spacing.supabasespacexs1px
spacing.normalbordervar(--gray4)
spacing.toasticonmarginend4px
spacing.spacemd8px
spacing.spacesm8px
spacing.supabasespacesm8px
spacing.typecaption12px
spacing.typenav14px
spacing.typebody14px
spacing.size16px
spacing.mobileoffset16px
spacing.typesubheading16px
spacing.supabasespacemd16px
spacing.containerpadding16px
spacing.typelabellg18px
spacing.spacelg20px
spacing.spacexl20px
spacing.supabasespacelg20px
spacing.space2xl24px
spacing.typebodyxl24px
spacing.supabasespacexl24px
spacing.supabasespace2xl32px
spacing.space3xl80px
spacing.supabasespace3xl80px
spacing.breakpointmobile600px
spacing.containermax1280px
Radius (8)
radiussm6px
supabaseradiussm6px
borderradius8px
supabaseradiusfull8px
radiusmd16px
supabaseradiusmd16px
radiuslg9999px
supabaseradiuslg9999px
Shadow (13)
effect.ytranslateY(100%)
effect.lift1
effect.scalevar(--toasts-before) * .05 + 1
effect.shadowsmrgba(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
effect.liftamountcalc(var(--lift) * var(--gap))
effect.shadowcarddrop-shadow(rgba(0,0,0,0.05) 0px 1px 1px)
effect.shadowfocustoastrgba(0,0,0,0.1) 0px 4px 12px, rgba(0,0,0,0.2) 0px 0px 0px 2px
effect.supabaseshadowmddrop-shadow(rgba(0,0,0,0.05) 0px 1px 1px)
effect.supabaseshadowsmrgba(0,0,0,0) 0px 0px 0px 0px, …
effect.shadowfocusbuttonrgba(0,0,0,0.4) 0px 0px 0px 2px
effect.toastclosebuttonendunset
effect.toastclosebuttonstart0
effect.backdropfilterrolenavigationbackdrop-filter: blur(4px)
# 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

Vercel
MITClean, minimal developer-focused design system built on Geist typography with light surfaces, precise spacing, and fast interactions for modern web applications
00
lightminimaldeveloper-toolsaas

Sentry
MITDark, minimal design system for developer tools with a sophisticated purple palette and refined typography, tailored for error monitoring and performance platforms
00
darkminimaldeveloper-toolsaas

OpenAI
MITClean, minimal design system for AI-native products built on Next.js and Tailwind, featuring a light monochromatic palette with precise token-based spacing and typography
02
lightminimalsaasdeveloper-tool