Stripe
MIT
Clean, developer-focused design system with vibrant purple accent, refined typography, and precise spacing—built for fintech and SaaS 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 stripe# layout.md — Stripe Design System
*Reconstructed from computed styles. All tokens synthetic unless noted. See Appendix B for confidence levels.*
---
## 0. Quick Reference
**Stack:** Next.js + Bootstrap scaffolding · Custom CSS (zero native CSS vars found) · Token source: reconstructed-from-computed · **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 */
--stripe-accent: rgb(83, 58, 253);
--stripe-bg-app: #ffffff;
--stripe-border: none;
--stripe-success: #81b81a;
--stripe-bg-hover: var(--color-primary-hover);
--stripe-bg-surface: #ffffff;
--stripe-text-muted: #64748d;
--stripe-bg-selected: #2d16cc;
--stripe-accent-hover: #3d22e8;
--stripe-text-primary: #061b31;
--stripe-text-secondary: #533afd;
--stripe-text-placeholder: #ffffff;
/* Other */
--stripe-space-lg: 32px;
--stripe-space-md: 16px;
--stripe-space-sm: 12px;
--stripe-space-xl: 40px;
--stripe-space-xs: 8px;
--stripe-radius-lg: 6px;
--stripe-radius-md: 5px;
--stripe-radius-sm: 4px;
--stripe-shadow-md: none;
--stripe-space-2xl: 64px;
--stripe-space-3xl: 96px;
--stripe-ease-default: ease;
--stripe-font-size-lg: 22px;
--stripe-font-size-md: 18px;
--stripe-font-size-sm: 16px;
--stripe-font-size-xl: 26px;
--stripe-font-size-xs: 14px;
--stripe-duration-base: 0.25s;
--stripe-duration-fast: 0.1s;
--stripe-duration-slow: 0.3s;
--stripe-font-size-2xl: 32px;
--stripe-font-size-3xl: 48px;
--stripe-line-height-loose: 29.12px;
--stripe-font-weight-medium: 400;
--stripe-line-height-normal: 22.4px;
--stripe-font-weight-regular: 300;
--stripe-font-weight-semibold: 500;
}
```
```tsx
// Primary Button — production-ready, all states
const PrimaryButton = ({ children, disabled, loading }: ButtonProps) => (
<button
disabled={disabled || loading}
style={{
fontFamily: 'sohne-var, "SF Pro Display", sans-serif',
fontSize: 'var(--font-size-sm)', // 16px
fontWeight: 400,
lineHeight: 1,
color: disabled ? 'var(--color-text-muted)' : 'var(--color-text-inverse)',
backgroundColor: disabled ? 'var(--color-primary-soft)' : 'var(--color-primary)',
borderRadius: 'var(--radius-sm)',
padding: '15.5px var(--space-5) 16.5px',
border: 'none',
display: 'flex', alignItems: 'center', gap: 'var(--space-2)',
transition: `background-color var(--duration-base) var(--ease-cta),
color var(--duration-base) var(--ease-cta)`,
cursor: disabled ? 'not-allowed' : 'pointer',
opacity: loading ? 0.7 : 1,
}}
>
{loading ? <Spinner /> : children}
</button>
);
```
**NEVER rules:**
- **NEVER** use Inter, Roboto, or Arial — font is `sohne-var` with `"SF Pro Display"` fallback only
- **NEVER** use border-radius > 6px on any component — the brand cap is `--radius-lg: 6px`
- **NEVER** hardcode `#533afd` — always use `var(--color-primary)`
- **NEVER** use font-weight 700 (bold) — Stripe's heaviest weight is 500 (semibold, cookie banners only)
- **NEVER** use `letter-spacing: 0` on headings — all headings carry negative tracking (–0.26px to –0.88px)
- **NEVER** build layout with absolute positioning — use flex with the gap/padding values from tokens
*Full design system → see layout.md*
---
## 1. Design Direction & Philosophy
### Character & Mood
Stripe's visual language is **precision-engineered minimalism with latent energy**. The typographic scale is consistently light-weight (300) at headline level, creating an airy, confident authority that says "we don't need to shout." The single high-chroma violet (`#533afd`) lands as a deliberate interrupt against the muted navy-and-slate palette — one colour does all the work.
### Aesthetic Intent
- **Technical elegance:** Tight negative letter-spacing on all headings signals precision and density without visual noise.
- **Restrained palette:** Near-monochromatic with one accent. No gradients on interactive elements — flat violet only.
- **Spatial generosity:** Large type at low weight creates perceived whitespace even without extra padding.
- **Motion as signal:** Transitions are functional (0.3s on interactions) and expressive (0.8s easing on reveals) — never decorative.
### What This Design Explicitly Rejects
- ❌ Rounded pill buttons (max radius is 6px — **sharp, rectangular CTAs only**)
- ❌ Multiple brand colours competing for attention
- ❌ Heavy or bold typography on body or headings (max weight is 400 for UI, 300 for editorial)
- ❌ Drop shadows on cards or primary buttons (elevation via colour/spacing, not shadows)
- ❌ Warm hues anywhere in the core UI (navy + violet + slate — all cool)
- ❌ Dense, cluttered layouts — whitespace is a primary design element
---
## 2. Colour System
### Tier 1: Primitive Values
```css
:root {
/* Blues / Navies */
--primitive-navy-900: #061b31; /* reconstructed — darkest text/heading navy */
--primitive-slate-500: #64748d; /* reconstructed — muted body text */
--primitive-white: #ffffff; /* reconstructed — inverse text, surfaces */
/* Violets */
--primitive-violet-600: #533afd; /* reconstructed — Stripe's primary violet */
--primitive-violet-100: #e2e4ff; /* reconstructed — violet tint for secondary CTA */
/* Accent */
--primitive-green-500: #81b81a; /* reconstructed — brand green, H1 accent only */
}
```
### Tier 2: Semantic Aliases
```css
:root {
/* Surfaces */
--color-surface: var(--primitive-white); /* reconstructed — page/card background */
--color-surface-overlay: rgba(0, 0, 0, 0); /* reconstructed — transparent card bg */
/* Text */
--color-text-heading: var(--primitive-navy-900); /* reconstructed — h2, h3 colour */
--color-text-body: var(--primitive-slate-500); /* reconstructed — body, tab, secondary */
--color-text-inverse: var(--primitive-white); /* reconstructed — text on dark/violet bg */
--color-text-accent: var(--primitive-green-500); /* reconstructed — h1 highlight only */
/* Actions */
--color-primary: var(--primitive-violet-600); /* reconstructed — primary CTA bg */
--color-primary-soft: var(--primitive-violet-100); /* reconstructed — secondary CTA bg */
--color-primary-text: var(--primitive-violet-600); /* reconstructed — toggle/link colour */
/* Interactive states (inferred from transition tokens) */
--color-primary-hover: #3d22e8; /* reconstructed, moderate confidence — darkened violet ~15% */
--color-primary-active: #2d16cc; /* reconstructed, moderate confidence — pressed state */
--color-primary-disabled: var(--primitive-violet-100); /* reconstructed — disabled bg */
--color-text-disabled: var(--primitive-slate-500); /* reconstructed — disabled label */
}
```
### Tier 3: Component Tokens
```css
:root {
/* Button primary */
--btn-primary-bg: var(--color-primary);
--btn-primary-bg-hover: var(--color-primary-hover);
--btn-primary-bg-disabled: var(--color-primary-disabled);
--btn-primary-text: var(--color-text-inverse);
--btn-secondary-bg: var(--color-primary-soft);
--btn-secondary-text: var(--color-primary-text);
/* Navigation */
--nav-text: var(--primitive-navy-900); /* reconstructed */
--nav-bg: transparent; /* reconstructed */
/* Toggle */
--toggle-text: var(--color-primary-text);
--toggle-bg: transparent;
}
```
### Colour Usage Table
| Token | Value | Used On |
|---|---|---|
| `--color-primary` | `#533afd` | Primary CTA buttons (8 instances) |
| `--color-primary-soft` | `#e2e4ff` | Secondary CTA buttons (3 instances) |
| `--color-text-heading` | `#061b31` | H2, H3 |
| `--color-text-body` | `#64748d` | Body text, tabs, muted labels |
| `--color-text-accent` | `#81b81a` | H1 only (decorative accent) |
| `--color-text-inverse` | `#ffffff` | Button labels on violet bg |
---
## 3. Typography System
**Font stack:** `sohne-var, "SF Pro Display", sans-serif` — used on every element without exception.
### Composite Type Groups
```css
:root {
/* ── Display / H1 ── */
--type-display: {
font-family: 'sohne-var, "SF Pro Display", sans-serif'; /* reconstructed */
font-size: 44px; /* reconstructed — extracted from h1 */
font-weight: 300; /* reconstructed — Stripe's editorial weight */
line-height: 50.6px; /* reconstructed — 1.15 ratio */
letter-spacing: -0.88px; /* reconstructed — tightest tracking */
color: var(--color-text-accent); /* #81b81a — h1 accent */
}
/* ── Heading / H2 ── */
--type-heading: {
font-family: 'sohne-var, "SF Pro Display", sans-serif'; /* reconstructed */
font-size: 32px; /* reconstructed */
font-weight: 300; /* reconstructed */
line-height: 35.2px; /* reconstructed — 1.1 ratio */
letter-spacing: -0.64px; /* reconstructed */
color: var(--color-text-heading);
}
/* ── Subheading / H3 ── */
--type-subheading: {
font-family: 'sohne-var, "SF Pro Display", sans-serif'; /* reconstructed */
font-size: 26px; /* reconstructed */
font-weight: 300; /* reconstructed */
line-height: 29.12px; /* reconstructed — 1.12 ratio */
letter-spacing: -0.26px; /* reconstructed */
color: var(--color-text-heading);
}
/* ── Section Label / H4 UI ── */
--type-label-lg: {
font-family: 'sohne-var, "SF Pro Display", sans-serif'; /* reconstructed */
font-size: 22px; /* reconstructed — h3 card titles */
font-weight: 300; /* reconstructed */
line-height: 29.12px; /* reconstructed */
letter-spacing: -0.26px; /* reconstructed, moderate confidence */
}
/* ── Body / Default ── */
--type-body: {
font-family: 'sohne-var, "SF Pro Display", sans-serif'; /* reconstructed */
font-size: 16px; /* reconstructed — dominant at 126 elements */
font-weight: 400; /* reconstructed */
line-height: 22.4px; /* reconstructed — 1.4 ratio */
letter-spacing: normal; /* reconstructed */
color: var(--color-text-body);
}
/* ── Body Emphasis / UI Labels ── */
--type-body-md: {
font-family: 'sohne-var, "SF Pro Display", sans-serif'; /* reconstructed */
font-size: 18px; /* reconstructed — 7 elements, event callouts */
font-weight: 400; /* reconstructed */
line-height: 22.4px; /* reconstructed */
letter-spacing: normal; /* reconstructed */
}
/* ── Stat / Large Number ── */
--type-stat: {
font-family: 'sohne-var, "SF Pro Display", sans-serif'; /* reconstructed */
font-size: 48px; /* reconstructed — h4 "500 Mio.+" */
font-weight: 400; /* reconstructed */
line-height: 1.1; /* reconstructed, moderate confidence */
letter-spacing: normal; /* reconstructed */
}
/* ── Tab / Small UI ── */
--type-tab: {
font-family: 'sohne-var, "SF Pro Display", sans-serif'; /* reconstructed */
font-size: 14px; /* reconstructed */
font-weight: 300; /* reconstructed */
line-height: normal; /* reconstructed */
letter-spacing: -0.42px; /* reconstructed */
color: var(--color-text-body);
}
/* ── Button / CTA ── */
--type-button: {
font-family: 'sohne-var, "SF Pro Display", sans-serif'; /* reconstructed */
font-size: 16px; /* reconstructed */
font-weight: 400; /* reconstructed */
line-height: 16px; /* reconstructed — 1:1, single line */
letter-spacing: normal; /* reconstructed */
}
}
```
### Font Weight Scale
| Weight | Value | Usage |
|---|---|---|
| Light | 300 | All headings (h1–h3), tabs, body copy |
| Regular | 400 | Buttons, UI labels, body (h4), links |
| Medium | 500 | Cookie banner buttons only — do not use elsewhere |
### Pairing Rules
- **NEVER** mix a 400-weight body with a 700-weight heading — weight contrast is achieved by size, not boldness
- H1 always uses `--color-text-accent` (#81b81a); H2/H3 always use `--color-text-heading` (#061b31)
- Negative `letter-spacing` is **mandatory** on all headings — do not remove it
---
## 4. Spacing & Layout
```css
:root {
/* ── Base unit: 4px — scale in 4px increments ── */
--space-1: 4px; /* reconstructed — micro gap, hairline separation */
--space-2: 8px; /* reconstructed — icon-to-label gap (button, toggle) */
--space-3: 12px; /* reconstructed — tight internal padding */
--space-4: 16px; /* reconstructed — dropdown padding, toggle margin */
--space-5: 24px; /* reconstructed — button horizontal padding */
--space-6: 28px; /* reconstructed — nav item gap */
--space-7: 32px; /* reconstructed — section sub-gap */
--space-8: 48px; /* reconstructed — section vertical rhythm */
--space-9: 64px; /* reconstructed, moderate confidence — section margin */
--space-10: 80px; /* reconstructed, moderate confidence — hero padding */
/* ── Border Radius ── */
--radius-sm: 4px; /* reconstructed — buttons, tags (30 elements — dominant) */
--radius-md: 5px; /* reconstructed — bento card content (1 element) */
--radius-lg: 6px; /* reconstructed — nav items, UI chrome (6 elements) */
/* NEVER use border-radius > 6px */
/* ── Container widths (moderate confidence — inferred from Next.js/Bootstrap patterns) ── */
--container-max: 1200px; /* reconstructed, moderate confidence */
--container-md: 960px; /* reconstructed, moderate confidence */
--container-sm: 720px; /* reconstructed, moderate confidence */
/* ── Breakpoints (Bootstrap 5 defaults — detected library) ── */
--bp-sm: 576px;
--bp-md: 768px;
--bp-lg: 992px;
--bp-xl: 1200px;
--bp-xxl: 1400px;
}
```
### Grid & Flex Decisions
| Pattern | Rule |
|---|---|
| Navigation | `display: flex; flex-direction: row; gap: 28px; align-items: center; padding: 10px 16px` |
| Button internals | `display: flex; flex-direction: row; gap: 8px; align-items: center` |
| Card layout | `display: flex; flex-direction: row` (no gap on outer wrapper — spacing is internal) |
| Section columns | Inferred: 12-col Bootstrap grid with 2–4 col feature cards |
| Flex vs Grid | **Flex** for nav, buttons, toggles; **Grid** for card layouts and feature sections |
---
## 5. Page Structure & Layout Patterns
*Source: layout digest + component inventory. Visually unconfirmed rows marked (inferred).*
### 5.1 Section Map
| # | Section | Layout Type | Key Elements | Confirmed? |
|---|---|---|---|---|
| 1 | Global Navigation | Flex row, sticky | Logo, nav items (gap:28px), primary CTA button | Confirmed (role_navigation computed style) |
| 2 | Hero | Full-width, constrained content | H1 (44px, green), H2 subhead, primary + secondary CTA pair | (inferred) |
| 3 | Feature / Bento Card Grid | Flex/Grid multi-column | Cards (256 instances), H3 titles, body text | Confirmed (card inventory) |
| 4 | Social Proof / Stats | Centered, full-width | H4 stat numbers (48px), supporting body text | (inferred — 48px stat type extracted) |
| 5 | Testimonials / Case Studies | Card grid or carousel | Cards with H3 22px titles, body copy | (inferred — h3 22px + card inventory) |
| 6 | Product Tab Section | Tabbed interface | Tabs (14px, 58 badge instances), content panels | Confirmed (tab + badge inventory) |
| 7 | CTA Banner | Full-width, centered | H2 heading, primary CTA button | (inferred) |
| 8 | Footer | Multi-column grid | Nav links, legal text (14px) | (inferred) |
### 5.2 Layout Patterns
**Navigation:** `display: flex; flex-direction: row; align-items: center; gap: 28px; padding: 10px 16px; border-radius: 6px` — items are horizontally spaced at exactly 28px. CTA button sits at far-right of the flex container.
**Hero:** Two CTAs side-by-side — primary (`background: #533afd`) and secondary (`background: #e2e4ff`). Both use `border-radius: 4px` and `padding: 15.5px 24px 16.5px`. Gap between them inferred at `--space-2` (8px) based on button gap token.
**Feature Card Grid:** `display: flex; flex-direction: row` at card container level. 256 card instances indicate dense repeated grid — likely 3–4 columns at desktop. Card outer padding is 0px; internal spacing is handled by card content.
**Bento card variant** has `border-radius: 5px` on content area and `border-radius: 6px` on border/chrome — a subtle layered depth treatment.
**Stat section:** H4 elements at 48px weight-400 are the visual anchors. Text alignment likely `text-align: center`. (inferred)
### 5.3 Visual Hierarchy
- **Most prominent:** H1 at 44px/weight-300 in `#81b81a` (green) — the only warm/bright colour on the page; draws the eye immediately
- **Second tier:** H2 at 32px/weight-300 in `#061b31` — authority without weight
- **CTA priority:** Violet `#533afd` buttons are the only saturated blue-violet on the page; secondary CTAs in `#e2e4ff` create a clear primary/secondary hierarchy
- **Whitespace rhythm:** Large type at low weight creates visual breathing room — do not add extra `margin-top` to headings; the letter-spacing and size already provide separation
- **Badge count (58):** Badges are heavily used across the product tab section — likely status/category labels in body size (16px)
### 5.4 Content Patterns
- **H3 + body copy + link:** Repeated card pattern. H3 at 26px (or 22px for smaller cards) + body at 16px/`#64748d` + a violet text-link or ghost CTA
- **Stat + descriptor:** 48px number above 16px label — stacked, centered (inferred from stat inventory)
- **Tab → content panel:** 14px tab label triggers content swap; active tab uses `#533afd` colour (from toggle computed colour `#533afd`)
- **CTA pairs:** Primary violet button always paired with secondary soft-violet button — never a standalone CTA block
---
## 6. Component Patterns
### 6.1 Primary Button
**Anatomy:** `<button>` → [optional icon] + label text
**Token mappings:**
| State | bg | color | opacity | cursor |
|---|---|---|---|---|
| Default | `#533afd` | `#ffffff` | 1 | pointer |
| Hover | `#3d22e8` | `#ffffff` | 1 | pointer |
| Active | `#2d16cc` | `#ffffff` | 1 | pointer |
| Focus | `#533afd` + 2px outline | `#ffffff` | 1 | pointer |
| Disabled | `#e2e4ff` | `#64748d` | 1 | not-allowed |
| Loading | `#533afd` | `#ffffff` | 0.7 | wait |
| Error | `#533afd` (unchanged — error shown adjacently) | `#ffffff` | 1 | pointer |
```tsx
import { ButtonHTMLAttributes, ReactNode } from 'react';
interface StripeButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary';
loading?: boolean;
icon?: ReactNode;
children: ReactNode;
}
export const StripeButton = ({
variant = 'primary',
loading = false,
disabled = false,
icon,
children,
...props
}: StripeButtonProps) => {
const isPrimary = variant === 'primary';
const isDisabled = disabled || loading;
return (
<button
disabled={isDisabled}
{...props}
style={{
/* Type */
fontFamily: 'sohne-var, "SF Pro Display", sans-serif',
fontSize: '16px',
fontWeight: 400,
lineHeight: '16px',
letterSpacing: 'normal',
textDecoration: 'none',
/* Layout */
display: 'inline-flex',
flexDirection: 'row',
alignItems: 'center',
gap: '8px', /* --space-2 */
/* Spacing */
padding: '15.5px 24px 16.5px', /* extracted — asymmetric top/bottom is intentional */
margin: 0,
/* Colour */
color: isDisabled && isPrimary
? '#64748d' /* --color-text-disabled */
: isPrimary
? '#ffffff' /* --color-text-inverse */
: '#533afd', /* --color-primary-text */
backgroundColor: isDisabled && isPrimary
? '#e2e4ff' /* --color-primary-disabled */
: isPrimary
? '#533afd' /* --color-primary */
: '#e2e4ff', /* --color-primary-soft */
/* Shape */
border: 'none',
borderRadius: '4px', /* --radius-sm */
/* Motion */
transition:
'background-color 0.3s cubic-bezier(0.25, 1, 0.5, 1), ' +
'color 0.3s cubic-bezier(0.25, 1, 0.5, 1)',
cursor: isDisabled ? 'not-allowed' : 'pointer',
opacity: loading ? 0.7 : 1,
/* Focus ring */
outline: 'none',
}}
onMouseEnter={e => {
if (isDisabled) return;
(e.currentTarget as HTMLButtonElement).style.backgroundColor =
isPrimary ? '#3d22e8' : '#cdd0ff'; /* --color-primary-hover */
}}
onMouseLeave={e => {
if (isDisabled) return;
(e.currentTarget as HTMLButtonElement).style.backgroundColor =
isPrimary ? '#533afd' : '#e2e4ff';
}}
onFocus={e => {
e.currentTarget.style.outline = '2px solid #533afd';
e.currentTarget.style.outlineOffset = '2px';
}}
onBlur={e => {
e.currentTarget.style.outline = 'none';
}}
>
{loading ? (
<svg
width="16" height="16" viewBox="0 0 16 16"
style={{ animation: 'spin 0.8s linear infinite' }}
aria-hidden="true"
>
<circle cx="8" cy="8" r="6" fill="none"
stroke="currentColor" strokeWidth="2"
strokeDasharray="25 10" />
</svg>
) : icon}
{children}
</button>
);
};
```
---
### 6.2 Navigation Item
**Anatomy:** `<nav>` (flex row, gap:28px) → `<a>` nav items
| State | color | bg | Notes |
|---|---|---|---|
| Default | `#061b31` | transparent | weight 400 |
| Hover | `#533afd` | transparent | (inferred — colour shift on hover) |
| Focus | `#533afd` | transparent | + outline |
| Active | `#533afd` | transparent | (inferred) |
| Disabled | `#64748d` | transparent | (inferred) |
```tsx
export const NavItem = ({ href, children, active }: { href: string; children: ReactNode; active?: boolean }) => (
<a
href={href}
style={{
fontFamily: 'sohne-var, "SF Pro Display", sans-serif',
fontSize: '16px',
fontWeight: 400,
lineHeight: 'normal',
letterSpacing: 'normal',
color: active ? '#533afd' : '#061b31',
textDecoration: 'none',
borderRadius: '6px', /* --radius-lg — nav chrome */
padding: '10px 16px', /* role_navigation extracted padding */
transition: 'color 0.3s cubic-bezier(0.25, 1, 0.5, 1)',
display: 'inline-flex',
alignItems: 'center',
}}
>
{children}
</a>
);
export const Nav = ({ children }: { children: ReactNode }) => (
<nav
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
gap: '28px', /* --space-6 — extracted from role_navigation */
}}
>
{children}
</nav>
);
```
---
### 6.3 Card
**Anatomy:** Card wrapper (flex row) → [Card border overlay] → Card content area
| State | bg | shadow | border-radius | Notes |
|---|---|---|---|---|
| Default | transparent | none | 0px (outer) / 5px (content) / 6px (border) | Layered radii |
| Hover | (inferred: slight bg tint) | none | unchanged | z-index step via `0.1s steps(1)` |
| Focus | outline visible | none | unchanged | Keyboard accessible |
| Active | (inferred: scale transform) | none | unchanged | |
| Disabled | `#64748d` text | none | unchanged | (inferred) |
```tsx
export const StripeCard = ({ children }: { children: ReactNode }) => (
<div
style={{
display: 'flex',
flexDirection: 'row',
fontFamily: 'sohne-var, "SF Pro Display", sans-serif',
fontSize: '16px',
fontWeight: 400,
transition: 'z-index 0.1s steps(1)',
borderRadius: '6px', /* --radius-lg */
overflow: 'hidden',
backgroundColor: 'transparent',
}}
>
<div
style={{
borderRadius: '5px', /* --radius-md — inner content */
padding: '24px', /* --space-5 — inferred internal padding */
flex: 1,
}}
>
{children}
</div>
</div>
);
```
---
### 6.4 Toggle
**Anatomy:** `<button>` inline-flex → indicator + label
| State | color | bg | Notes |
|---|---|---|---|
| Default | `#533afd` | transparent | Violet label |
| Hover | `#3d22e8` | transparent | (inferred) |
| Active (selected) | `#ffffff` | `#533afd` | Selected state fills bg |
| Focus | `#533afd` + outline | transparent | |
```tsx
export const Toggle = ({ label, active, onClick }: { label: string; active: boolean; onClick: () => void }) => (
<button
onClick={onClick}
style={{
fontFamily: 'sohne-var, "SF Pro Display", sans-serif',
fontSize: '16px',
fontWeight: 400,
lineHeight: '16px',
letterSpacing: 'normal',
color: active ? '#ffffff' : '#533afd',
backgroundColor: active ? '#533afd' : 'transparent',
border: 'none',
borderRadius: '4px',
padding: '8px 12px',
margin: '16px 0 16px 16px', /* extracted toggle margin */
display: 'inline-flex',
alignItems: 'center',
gap: '8px',
cursor: 'pointer',
transition:
'background-color 0.3s cubic-bezier(0.25, 1, 0.5, 1), ' +
'color 0.3s cubic-bezier(0.25, 1, 0.5, 1)',
}}
>
{label}
</button>
);
```
---
### 6.5 Tab
**Anatomy:** Tab list (flex row) → Tab items (14px, weight 300)
| State | color | border-bottom | Notes |
|---|---|---|---|
| Default | `#64748d` | none | Muted text |
| Hover | `#061b31` | none | (inferred) |
| Active | `#533afd` | 2px `#533afd` | (inferred — violet active indicator) |
| Focus | `#533afd` | — | + outline |
| Disabled | `#64748d` | none | opacity 0.5 |
---
### 6.6 Badge
**Anatomy:** `<span>` inline-block with label text
*(58 instances; likely used as product category or status labels. Exact bg/border values not extracted — inferred from primary palette.)*
| State | bg | color | border-radius | Note |
|---|---|---|---|---|
| Default (inferred) | `#e2e4ff` | `#533afd` | `4px` | reconstructed, moderate confidence |
| Active (inferred) | `#533afd` | `#ffffff` | `4px` | reconstructed, moderate confidence |
```tsx
export const Badge = ({ label, active }: { label: string; active?: boolean }) => (
<span
style={{
fontFamily: 'sohne-var, "SF Pro Display", sans-serif',
fontSize: '14px', /* --font-size-xs */
fontWeight: 400,
lineHeight: '16px',
letterSpacing: '-0.42px',
color: active ? '#ffffff' : '#533afd',
backgroundColor: active ? '#533afd' : '#e2e4ff',
borderRadius: '4px', /* --radius-sm */
padding: '4px 8px', /* inferred — common badge padding */
display: 'inline-block',
}}
>
{label}
</span>
);
```
---
## 7. Elevation & Depth
```css
:root {
/* ── Shadows ── */
--shadow-none: none; /* reconstructed — Stripe uses flat design; no shadows on buttons or cards */
--shadow-subtle: 0 1px 3px rgba(6, 27, 49, 0.08); /* reconstructed, low confidence — inferred for hover lift */
--shadow-card: 0 4px 16px rgba(6, 27, 49, 0.10); /* reconstructed, low confidence — inferred for elevated modals */
/* ── Borders ── */
--border-none: none; /* reconstructed — extracted computed borders are all 0px */
--border-subtle: 1px solid rgba(6, 27, 49, 0.12); /* reconstructed, moderate confidence — inferred for card edges */
/* ── Z-index scale ── */
--z-base: 0;
--z-card: 1;
--z-card-hover: 2; /* reconstructed — card uses z-index step transition */
--z-nav: 100; /* reconstructed — sticky nav above content */
--z-dropdown: 200; /* reconstructed — dropdown overlays nav */
--z-modal: 300; /* reconstructed — modal above everything */
--z-toast: 400; /* reconstructed, low confidence */
}
```
**Stripe's elevation model:** Depth is communicated through **spacing and colour contrast, not shadows**. The computed styles show `box-shadow: none` on every element. The card uses a `z-index` step transition (`0.1s steps(1)`) for hover — a z-index flip, not a shadow. Do not add drop shadows to replicate "elevation" — use background colour and spacing instead.
---
## 8. Motion
```css
:root {
/* ── Durations ── */
--duration-instant: 0.1s; /* reconstructed — z-index step (card hover z-layer swap) */
--duration-base: 0.3s; /* reconstructed — standard UI transitions (123 elements) */
--duration-reveal: 0.8s; /* reconstructed — content reveals, modal entry, H3 transforms */
--duration-icon: 0.25s; /* reconstructed — SVG path animations (8 elements) */
/* ── Easing ── */
--ease-cta: cubic-bezier(0.25, 1, 0.5, 1); /* reconstructed — button bg/color/border */
--ease-reveal: cubic-bezier(0.165, 0.84, 0.44, 1); /* reconstructed — modal + h3 transform */
--ease-linear: linear; /* reconstructed — icon/loading animations */
/* ── Composite transitions ── */
--transition-button:
background-color var(--duration-base) var(--ease-cta),
color var(--duration-base) var(--ease-cta),
border var(--duration-base) var(--ease-cta);
--transition-reveal:
transform var(--duration-reveal) var(--ease-reveal);
--transition-z:
z-index var(--duration-instant) steps(1);
}
```
### Motion Rules
| When | Use | Avoid |
|---|---|---|
| Button bg/color change | `--transition-button` (0.3s ease-cta) | Instant jumps |
| Card section reveal / modal entry | `--transition-reveal` (0.8s ease-reveal) | `ease-in` (too abrupt) |
| Z-layer swap on hover | `--transition-z` (0.1s steps(1)) | Smooth z-index (creates flicker) |
| Icon path animations | `0.25s linear` | Long durations (feels sluggish) |
| `prefers-reduced-motion` | Set all durations to `0.01s` | Ignoring the media query |
**NEVER** animate `width`, `height`, or `top/left` — use `transform` only.
---
## 9. Anti-Patterns & Constraints
1. **Using Inter, Roboto, or Arial as the body font → Why it fails:** AI agents default to system-safe fonts when no explicit font is injected. Stripe's entire typographic identity depends on `sohne-var` — the tight negative letter-spacing on headings only reads correctly at that specific metric. A swap to Inter at –0.88px letter-spacing looks broken because Inter's character widths differ. **What to do instead:** Always declare `font-family: sohne-var, "SF Pro Display", sans-serif` as the first rule in your base CSS. If sohne-var is unavailable, SF Pro Display is the intended fallback — not a generic sans.
2. **Using border-radius > 6px on any component → Why it fails:** AI agents frequently default to `border-radius: 8px` or `border-radius: 12px` as "safe" rounded corners. On Stripe, the brand radius cap is 6px (6 elements) with 4px as the dominant button radius (30 elements). Pill buttons (`border-radius: 50px`) are a completely different brand aesthetic — not Stripe's. Applying 8px+ makes buttons and cards look like a different product entirely. **What to do instead:** Use only `--radius-sm: 4px`, `--radius-md: 5px`, or `--radius-lg: 6px`.
3. **Hardcoding colour hex values inline → Why it fails:** When the AI generates `backgroundColor: "#533afd"` inline across 10 components, a brand colour update requires finding and replacing every occurrence. More critically, the AI may use a similar-but-wrong violet (`#5340ff`, `#6366f1`) from memory if not anchored to the token. **What to do instead:** Always use `var(--color-primary)` and define the token in `:root`. If writing JSX, `style={{ backgroundColor: 'var(--color-primary)' }}`.
4. **Using font-weight 600 or 700 on any heading → Why it fails:** AI agents associate "prominent heading" with "bold weight." On Stripe, every headline from H1 to H3 is `font-weight: 300` — the visual prominence comes from size (44px/32px) and letter-spacing, not weight. Adding 700 creates a jarring heavy-handed look that contradicts the brand's confidence-through-restraint aesthetic. **What to do instead:** Use weight 300 for all headings, 400 for UI/body, 500 only for cookie/legal UI buttons.
5. **Removing negative letter-spacing from headings → Why it fails:** AI agents often omit `letter-spacing` when generating headings, defaulting to `letter-spacing: normal`. Stripe's headings carry `–0.88px` (H1), `–0.64px` (H2), `–0.26px` (H3) — tracking that optically tightens large type for a premium feel. Without it, headings look loose and unpolished. **What to do instead:** Always include `letter-spacing` as part of every composite type group, never as an optional property.
6. **Adding box-shadow to cards or buttons for "elevation" → Why it fails:** Stripe's computed styles show `box-shadow: none` on every extracted element. AI agents assume shadow = elevation = good UX. On Stripe, elevation is communicated through z-index layer transitions and colour contrast — not shadows. Adding shadows violates the flat, technical aesthetic. **What to do instead:** Use `--shadow-none` by default. For hover depth, use `--transition-z` (z-index step) or a subtle background tint only.
7. **Building dynamic Tailwind class names with string concatenation → Why it fails:** `className={\`bg-[${activeColor}]\`}` breaks Tailwind's static class scanner — the generated class is never in the purged CSS bundle. The button silently loses its background colour in production. **What to do instead:** Use `style={{ backgroundColor: 'var(--color-primary)' }}` for dynamic values, or use complete static class names with conditional logic (`className={active ? 'bg-[#533afd]' : 'bg-[#e2e4ff]'}`).
8. **Using absolute/fixed positioning for layout structure → Why it fails:** The nav and card grid are both flex-based. AI agents sometimes reach for `position: absolute; top: 0; left: 0` to "pin" elements. This breaks reflowing layouts at mobile breakpoints and causes content overlap when font sizes scale. **What to do instead:** Use `display: flex; flex-direction: row; gap: 28px` for nav items, flex column for vertical stacks — as extracted from computed styles.
9. **Applying `transition: all` to components → Why it fails:** The extracted computed styles show `transition: all` on body, h2, and alert elements. This is a browser default bleed-through, not a design intent. Using `transition: all` on interactive components causes unintended transitions on `width`, `padding`, and `border-radius` on resize or state change, creating jank. **What to do instead:** Use the explicit composite transition tokens: `--transition-button` for CTA elements, `--transition-reveal` for content panels.
10. **Generating placeholder lorem ipsum content in stat sections → Why it fails:** The 48px stat elements contain real numbers ("500 Mio.+", "10.000+", "150.000+"). AI agents generating components often insert "0" or "N/A" as placeholders. In production the component renders with the wrong data type (plain number vs. formatted string with suffix). **What to do instead:** Always type stat values as `string` in component props, never `number`, and require a `suffix` prop for units/plusses.
---
## 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 (34) */
--brand-primary-cta: rgb(83, 58, 253); /* Primary CTA background, dominant on 8 buttons — e.g. "Jetzt starten" /* mined from computed styles */ */
--brand-secondary-cta: rgb(226, 228, 255); /* Secondary CTA background, dominant on 3 buttons — e.g. "Kundenstory lesen" /* mined from computed styles */ */
--color-primary: #533afd;
--color-primary-soft: #e2e4ff;
--color-brand-green: #81b81a;
--color-text-dark: #061b31;
--color-text-muted: #64748d;
--color-text-inverse: #ffffff;
--color-surface: #ffffff;
--primitive-navy-900: #061b31;
--primitive-slate-500: #64748d;
--primitive-white: #ffffff;
--primitive-violet-600: #533afd;
--primitive-violet-100: #e2e4ff;
--primitive-green-500: #81b81a;
--color-surface-overlay: rgba(0, 0, 0, 0);
--color-text-heading: #061b31;
--color-text-body: #64748d;
--color-text-accent: #81b81a;
--color-primary-text: #533afd;
--color-primary-hover: #3d22e8;
--color-primary-active: #2d16cc;
--color-primary-disabled: #e2e4ff;
--color-text-disabled: #64748d;
--btn-primary-bg: var(--color-primary);
--btn-primary-bg-hover: var(--color-primary-hover);
--btn-primary-bg-disabled: var(--color-primary-disabled);
--btn-secondary-bg: var(--color-primary-soft);
--nav-text: #061b31;
--nav-bg: transparent;
--toggle-text: #533afd;
--toggle-bg: transparent;
--border-none: none;
--border-subtle: 1px solid rgba(6,27,49,0.12);
/* Typography (14) */
--font-size-xs: 14px; /* 10 elements — e.g. span "Anmelden", a "Preisgestaltung", a "AnmeldenAnmelden" /* mined from computed styles */ */
--font-size-sm: 16px; /* 126 elements — e.g. h4 "Fachdienstleistungen", h4 "Stripe-zertifizierte", h4 "Supportpläne." /* mined from computed styles */ */
--font-size-md: 18px; /* 7 elements — e.g. p "29. bis 30. April,", p "2026", p "Moscone West," /* mined from computed styles */ */
--font-size-lg: 22px; /* 6 elements — e.g. h3 "Hertz entscheidet si", h3 "URBN bündelt 5 Mrd. ", h3 "Instacart betreibt s" /* mined from computed styles */ */
--font-size-xl: 26px; /* 21 elements — e.g. h3 "Akzeptieren und opti", h3 "Aktivieren Sie das p", h3 "Monetarisieren Sie A" /* mined from computed styles */ */
--font-size-2xl: 32px; /* 9 elements — e.g. h2 "Flexible Lösungen fü", h2 "Unterstützung für Un", h2 "Zuverlässige, erweit" /* mined from computed styles */ */
--font-size-3xl: 48px; /* 8 elements — e.g. h4 "500 Mio.+", h4 "10.000+", h4 "150.000+" /* mined from computed styles */ */
--font-weight-regular: 300; /* 83 elements — e.g. h1 "Die Finanzinfrastruk", h2 "Flexible Lösungen fü", h2 "Das Rückgrat des glo" /* mined from computed styles */ */
--font-weight-medium: 400; /* 105 elements — e.g. h4 "Fachdienstleistungen", h4 "Stripe-zertifizierte", h4 "Supportpläne." /* mined from computed styles */ */
--font-weight-semibold: 500; /* 2 elements — e.g. button "Alle akzeptieren", button "Alle ablehnen" /* mined from computed styles */ */
--line-height-normal: 22.4px; /* 60 elements — e.g. h4 "Fachdienstleistungen", h4 "Stripe-zertifizierte", h4 "Supportpläne." /* mined from computed styles */ */
--line-height-loose: 29.12px; /* 15 elements — e.g. h3 "Akzeptieren und opti", h3 "Aktivieren Sie das p", h3 "Monetarisieren Sie A" /* mined from computed styles */ */
--btn-primary-text: var(--color-text-inverse);
--btn-secondary-text: var(--color-primary-text);
/* Spacing (30) */
--space-xs: 8px; /* 18 elements — e.g. header .section-header, header .section-header, header .section-header /* mined from computed styles */ */
--space-sm: 12px; /* 9 elements — e.g. div .developers-scale-subsection__stats-grid-, div .developers-scale-subsection__stats-grid-, div .developers-scale-subsection__stats-grid- /* mined from computed styles */ */
--space-md: 16px; /* 92 elements — e.g. section .navigation-menu-footer, section .navigation-menu-footer, section .navigation-menu-footer /* mined from computed styles */ */
--space-lg: 32px; /* 14 elements — e.g. section .startups-carousel, section .startups-carousel, section .startups-carousel /* mined from computed styles */ */
--space-xl: 40px; /* 17 elements — e.g. div .platform-value__graphic-container, div .platform-value__graphic-container, div .stats-menu__stat-wrapper /* mined from computed styles */ */
--space-2xl: 64px; /* 18 elements — e.g. section .section-row, section .section-row, section .section-row /* mined from computed styles */ */
--space-3xl: 96px; /* 16 elements — e.g. section .section-row, section .section-row, section .section-row /* mined from computed styles */ */
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 24px;
--space-6: 28px;
--space-7: 32px;
--space-8: 48px;
--space-9: 64px;
--space-10: 80px;
--container-max: 1200px;
--container-md: 960px;
--container-sm: 720px;
--bp-sm: 576px;
--bp-md: 768px;
--bp-lg: 992px;
--bp-xl: 1200px;
--bp-xxl: 1400px;
--type-label-lg: 22px;
--type-body: 16px;
--type-body-md: 18px;
--type-tab: 14px;
--type-button: 16px;
/* Radius (6) */
--radius-sm: 4px; /* 30 elements — e.g. button .hds-button "Produkte", button .hds-button "Lösungen", button .hds-button "Entwickler/innen" /* mined from computed styles */ */
--radius-md: 5px; /* 1 element — e.g. div .modular-solutions-bento-card__content "Rösterei bezahlenCar" /* mined from computed styles */ */
--radius-lg: 6px; /* 6 elements — e.g. button .hds-ui-button, button .hds-ui-button, div .modular-solutions-bento-card__border /* mined from computed styles */ */
--radius-sm: 4px;
--radius-md: 5px;
--radius-lg: 6px;
/* Effects (3) */
--shadow-none: none;
--shadow-subtle: 0 1px 3px rgba(6,27,49,0.08);
--shadow-card: 0 4px 16px rgba(6,27,49,0.10);
/* Motion (4) */
--duration-fast: 0.1s; /* 6 elements — e.g. button, button, button /* mined from computed styles */ */
--duration-base: 0.25s; /* 8 elements — e.g. path, path, path /* mined from computed styles */ */
--duration-slow: 0.3s; /* 123 elements — e.g. button, button, button /* mined from computed styles */ */
--ease-default: ease; /* 142 elements — e.g. button, button, button /* mined from computed styles */ */
```
## Appendix B: Token Source Metadata
```
tokenSource: reconstructed-from-computed
confidence: low (0 CSS custom properties found natively)
extraction-method: computed style sampling of DOM elements by type/role
clustering-method: hue-family grouping for colour; 4px grid fitting for spacing
radius census by element count for border-radius
font-data: single @font-face declaration for sohne-var confirmed
library-signals: Bootstrap (grid system), Next.js (build tooling)
Reconstruction notes:
- ALL tokens are synthetic — none are native CSS custom properties from Stripe's source
- Colour tokens: 2 native button colours extracted (high confidence); hover/active/disabled
states are mathematically derived (lightness adjustments) — moderate confidence
- Spacing: button padding (15.5px/24px/16.5px) and nav gap (28px) are extracted directly;
all other scale values are inferred from 4px grid pattern — moderate confidence
- Shadow tokens: ALL are inferred. Stripe computed styles show box-shadow:none universally.
Shadow tokens are provided for agent convenience but should be treated as low-confidence
guesses until manually verified
- H1 colour (#81b81a green): extracted directly from h1 computed style — this is
contextually unusual (most headings are #061b31). May be specific to a hero section
or a gradient text treatment — verify before applying globally
- font-weight-semibold (500): only 2 elements (cookie banner buttons) — do not use in
main product UI
- body computed style shows fontSize:32px/fontWeight:300 — this appears to be the h2
style bleeding into body selector. True body text is 16px/400 (dominant at 126 elements)
- Tab letter-spacing (-0.42px): extracted directly. All other heading letter-spacings
extracted directly from h1/h2/h3 computed values
Authoritative sources to verify against:
- Stripe's public design docs: stripe.com/docs
- sohne-var font metrics (commercial font — ensure licence for usage)
- Actual Stripe CSS bundle for any missing border, shadow, or hover state values
```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