OpenAI
MIT
Clean, 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
Colour (45)
color.navbgtransparent
color.cardbgtransparent
color.inputbgtransparent
color.btnghostbgtransparent
color.cardbordervar(--color-border-default)
color.colorblack#000000
color.colorwhite#ffffff
color.openaiinfo#3b82f680
color.inputbordervar(--color-border-default)
color.openaibgapp#fff
color.twringcolor#3b82f680
color.bordersubtle1px solid rgb(229, 231, 235)
color.btnprimarybgvar(--color-black)
color.colorblack02rgba(0, 0, 0, 0.02)
color.colorblack05rgba(0, 0, 0, 0.05)
color.colorblack06rgba(0, 0, 0, 0.06)
color.colorblack12rgba(0, 0, 0, 0.12)
color.colorblack25rgba(0, 0, 0, 0.25)
color.colorblack50rgba(0, 0, 0, 0.50)
color.colorblack60rgba(0, 0, 0, 0.60)
color.colorfocusbgvar(--tw-ring-offset-color)
color.colorsurfacevar(--color-white)
color.openaibordervar(--color-black)
color.borderdefault1px solid rgba(0, 0, 0, 0.12)
color.inputbgactivevar(--color-white)
color.openaibghoverrgba(0, 0, 0, 0.80)
color.btnghostbordervar(--color-border-default)
color.colorblue50060#3b82f680
color.colorfocusringvar(--tw-ring-color)
color.btnghostbghoverrgba(0, 0, 0, 0.04)
color.openaibgsurfacevar(--color-white)
color.twshadowcolored0 0 #0000
color.btnprimarybordervar(--color-black)
color.colortextprimaryvar(--color-black)
color.openaibgelevatedvar(--color-white)
color.openaibgselectedvar(--color-white)
color.twborderspacingx0
color.btnprimarybghoverrgba(0, 0, 0, 0.80)
color.openaiaccenthover#3b82f680
color.openaiborderfocusvar(--tw-ring-color)
color.twringoffsetcolor#fff
color.colorborderdefaultvar(--color-black-12)
color.colortextsecondaryvar(--color-black-60)
color.colorsurfaceelevatedvar(--color-white)
color.motioncarouselnavprogressindicatormodulelbalawcarouselnavprogressfill@keyframes CarouselNavProgressIndicator-module__LbalAW__carousel-nav-progress-fill {
100% { stroke-dashoffset: 0
Spacing (50)
spacing.typeh1track-.03em
spacing.typexltrack-.02em
spacing.typenavheadersecondarytrack-.01em
spacing.twringinset
spacing.typectatrack0em
spacing.typenavtrack0em
spacing.typemetatrack0em
spacing.twborderspacingx0
spacing.typecaptiontrack0em
spacing.twringoffsetwidth0px
spacing.typenavheadertrack0em
spacing.typenavmobiletrack0em
spacing.typenavdesktoptrack0em
spacing.typecodesnippettrack0em
spacing.type2xsline.66rem
spacing.typexsline.825rem
spacing.typectaline.875rem
spacing.typenavmobilesize1rem
spacing.typenavheadersecondaryline1.05rem
spacing.typep1size1.0625rem
spacing.typemetaline1.225rem
spacing.typenavheaderline1.23rem
spacing.typenavmobileline1.25rem
spacing.typep2line1.435rem
spacing.typecaptionline1.435rem
spacing.typenavdesktopline1.435rem
spacing.typep1line1.74994rem
spacing.tocbuttonh3rem
spacing.headerh4rem
spacing.openaispacexs4px
spacing.spacexs4.2px
spacing.spacesm16px
spacing.openaispacesm16px
spacing.spacemd20px
spacing.openaispacemd20px
spacing.vpmin23.4375rem
spacing.spacelg24px
spacing.openaispacelg24px
spacing.spacexl32px
spacing.openaispacexl32px
spacing.space2xl40px
spacing.openaispace2xl40px
spacing.vpmax90rem
spacing.space3xl120px
spacing.openaispace3xl120px
spacing.bpsm640px
spacing.bpmd768px
spacing.bplg1024px
spacing.bpxl1280px
spacing.bp2xl1536px
Radius (10)
cardradiusvar(--radius-md)
inputradiusvar(--radius-full)
radiussm4px
openairadiussm4px
radiusmd6.08px
openairadiusmd6.08px
radiuslg40px
openairadiuslg40px
radiusfull9999px
openairadiusfull9999px
Shadow (16)
effect.dir1
effect.twpanx
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, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0.02) 0px 4px 6px 0px, rgba(0, 0, 0, 0.05) 0px 0px 2px 0px
effect.twrotate0
effect.twshadow0 0 #0000
effect.shadowlabelrgba(0,0,0,0.06) 0px 2px 5px 0px, rgba(0,0,0,0.01) 0px 4px 4px 0px
effect.twringshadow0 0 #0000
effect.typexlweight500
effect.openaishadowmdrgba(0,0,0,0.06) 0px 2px 5px 0px, rgba(0,0,0,0.01) 0px 4px 4px 0px
effect.openaishadowsmrgba(0,0,0,0.02) 0px 4px 6px 0px, rgba(0,0,0,0.05) 0px 0px 2px 0px
effect.lightningcssdarkinitial
effect.typecaptionweight400
effect.twringoffsetshadow0 0 #0000
effect.twscrollsnapstrictnessproximity
effect.typenavheadersecondaryweight600
effect.motioncrtscreenmodulevxh9iqcrttextshadowglitch@keyframes CRTScreen-module__vxH9Iq__crt-text-shadow-glitch {
0%, 16.8%, 18.3… <0.4KB elided>
# layout.md — OpenAI Design System
---
## 0. Quick Reference
> Standalone injectable — copy into CLAUDE.md or .cursorrules
**Stack:** Next.js · Tailwind CSS (utility-class-based) · CSS custom properties · LightningCSS
**Token source:** extracted-css-vars (high confidence, 136 properties) + computed-style clustering
**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 */
--openai-info: #3b82f680;
--openai-accent: var(--color-black);
--openai-bg-app: #fff;
--openai-border: var(--color-black);
--openai-bg-hover: rgba(0, 0, 0, 0.80);
--openai-bg-surface: var(--color-white);
--openai-bg-elevated: var(--color-white);
--openai-bg-selected: var(--color-white);
--openai-accent-hover: #3b82f680;
--openai-border-focus: var(--tw-ring-color);
--openai-text-primary: var(--color-black);
--openai-text-secondary: var(--color-black-60);
/* Other */
--openai-space-lg: 24px;
--openai-space-md: 20px;
--openai-space-sm: 16px;
--openai-space-xl: 32px;
--openai-space-xs: 4px;
--openai-font-mono: "KaTeX_Typewriter", "KaTeX_Main", monospace;
--openai-font-sans: "OpenAI Sans", sans-serif;
--openai-radius-lg: 40px;
--openai-radius-md: 6.08px;
--openai-radius-sm: 4px;
--openai-shadow-md: rgba(0,0,0,0.06) 0px 2px 5px 0px, rgba(0,0,0,0.01) 0px 4px 4px 0px;
--openai-shadow-sm: rgba(0,0,0,0.02) 0px 4px 6px 0px, rgba(0,0,0,0.05) 0px 0px 2px 0px;
--openai-space-2xl: 40px;
--openai-space-3xl: 120px;
--openai-font-serif: "LF Serif", serif;
--openai-radius-full: 9999px;
--openai-ease-default: ease;
--openai-font-size-lg: 18px;
--openai-font-size-md: 17px;
--openai-font-size-sm: 14px;
--openai-font-size-xl: 22px;
--openai-font-size-xs: 13px;
--openai-duration-base: 0.2s;
--openai-duration-fast: 0.1s;
--openai-duration-slow: 0.25s;
--openai-font-size-2xl: 28px;
--openai-font-size-3xl: 48px;
--openai-line-height-loose: 27.999px;
--openai-line-height-tight: 19.6px;
--openai-font-weight-medium: 500;
--openai-line-height-normal: 22.96px;
--openai-font-weight-regular: 400;
--openai-font-weight-semibold: 600;
}
```
```tsx
// PRIMARY BUTTON — correct token usage
<button className="
flex items-center px-[var(--space-lg)] h-10
rounded-[var(--radius-full)]
border border-black/12
text-[length:var(--type-cta-size)] font-[var(--type-cta-weight)]
bg-black text-white
transition-all duration-[var(--duration-slow)]
hover:bg-black/80 focus-visible:outline focus-visible:outline-2
focus-visible:outline-offset-2 focus-visible:outline-[var(--tw-ring-color)]
disabled:opacity-40 disabled:cursor-not-allowed
">Try ChatGPT</button>
```
**NEVER rules:**
- **NEVER** use `font-family: Inter` or `Arial` — always `"OpenAI Sans", sans-serif`
- **NEVER** use `border-radius: 8px` for buttons — primary buttons are `border-radius: 9999px` (pill)
- **NEVER** use spacing values off the 4px grid — use `--space-*` tokens only
- **NEVER** hardcode `#3b82f6` or any hex directly — use `var(--tw-ring-color)` and token aliases
- **NEVER** use `font-weight: 700` for UI text — max weight in use is `600` (semibold), default UI is `500`
- **NEVER** set `letter-spacing: 0` on headings — headings use negative tracking (–0.01em to –0.03em)
- **NEVER** construct Tailwind classes dynamically (`text-${size}`) — classes won't be generated at build time
**Full design system → see layout.md**
---
## 1. Design Direction & Philosophy
**Character:** Precise, minimal, confident. OpenAI's visual language is editorial-grade technology — clean whites, near-black text, generous whitespace, and restrained typographic hierarchy. It communicates authority without ornamentation.
**Mood:** Calm intelligence. The design does not excite — it clarifies. Animations are subtle (0.1–0.25s), transitions feel mechanical-smooth, not bouncy.
**Aesthetic pillars:**
- **Monochromatic first** — the palette is almost entirely black and white with a single blue focus ring. No gradient primaries, no vibrant brand colours in UI chrome.
- **Typography-led** — the proprietary `OpenAI Sans` typeface carries all hierarchy. Size and weight variation do the work that colour would do elsewhere.
- **Fluid scaling** — all headings use `clamp()` fluid type. Nothing snaps at breakpoints; everything flows.
- **Pill-shaped controls** — buttons and inputs use `border-radius: 9999px`. This is a core brand signal.
- **Negative letter-spacing** — headings carry deliberate negative tracking (–0.01em to –0.03em), giving display text a premium, tight feel.
- **Serif accent** — `LF Serif` exists for editorial/long-form contexts (research papers, blog). It is NOT used for UI chrome.
**What this design explicitly rejects:**
- Rounded-corner cards as the primary surface treatment (cards use `6.08px`, not `16px`+)
- Coloured buttons in the nav chrome — nav uses ghost/outline pill styles
- Decorative shadows — `--shadow-sm` is nearly invisible (`rgba(0,0,0,0.02)`)
- Font weights above 600 in UI contexts
- Warm colour temperatures — everything is cool neutral or pure black/white
- Bouncy or elastic easing — all easings are `ease` or `cubic-bezier(0,0,1,1)` (linear-feeling)
---
## 2. Colour System
### Tier 1: Primitives (extracted)
```css
/* Primitive colour values — extracted from CSS custom properties */
--color-black: #000000; /* pure black */
--color-white: #ffffff; /* pure white */
--color-blue-500-60: #3b82f680; /* blue-500 at 60% opacity — focus rings only */
--color-black-12: rgba(0, 0, 0, 0.12); /* borders */
--color-black-60: rgba(0, 0, 0, 0.60); /* secondary text (dropdown) */
--color-black-50: rgba(0, 0, 0, 0.50); /* scrollbar thumb hover */
--color-black-25: rgba(0, 0, 0, 0.25); /* scrollbar thumb default */
--color-black-06: rgba(0, 0, 0, 0.06); /* card shadow layer 1 */
--color-black-02: rgba(0, 0, 0, 0.02); /* button/label shadow layer */
--color-black-05: rgba(0, 0, 0, 0.05); /* button/label shadow layer */
```
### Tier 2: Semantic aliases
```css
/* Semantic colour tokens — map primitives to intent */
--color-surface: var(--color-white); /* page background */
--color-surface-elevated: var(--color-white); /* card/modal background */
--color-text-primary: var(--color-black); /* all primary text */
--color-text-secondary: var(--color-black-60); /* secondary/muted text */
--color-border-default: var(--color-black-12); /* all UI borders */
--color-focus-ring: var(--tw-ring-color); /* #3b82f680 — focus outlines */
--color-focus-bg: var(--tw-ring-offset-color); /* #fff — ring offset */
```
### Tier 3: Component tokens
```css
/* Component-level colour applications */
--btn-primary-bg: var(--color-black);
--btn-primary-text: var(--color-white);
--btn-primary-border: var(--color-black);
--btn-primary-bg-hover: rgba(0, 0, 0, 0.80);
--btn-ghost-bg: transparent;
--btn-ghost-text: var(--color-black);
--btn-ghost-border: var(--color-border-default); /* rgba(0,0,0,0.12) */
--btn-ghost-bg-hover: rgba(0, 0, 0, 0.04);
--input-bg: transparent;
--input-bg-active: var(--color-white);
--input-border: var(--color-border-default);
--input-text: var(--color-text-primary);
--input-radius: var(--radius-full); /* 9999px */
--card-bg: transparent;
--card-border: var(--color-border-default);
--card-radius: var(--radius-md); /* 6.08px */
--nav-bg: transparent;
--nav-text: var(--color-text-primary);
```
**Colour note:** The extracted CSS vars contain minimal colour tokens — OpenAI relies on Tailwind utility classes (`bg-black`, `text-white`, `border-black/12`) rather than CSS custom property colour primitives. The tokens above are synthesised from computed styles. Confidence: moderate.
---
## 3. Typography System
**Primary typeface:** `OpenAI Sans` (proprietary, served from `cdn.openai.com`)
**Secondary typeface:** `LF Serif` (editorial/long-form only — research, blog)
**Math rendering:** `KaTeX_*` family (research pages only)
### Font stack
```css
--font-sans: "OpenAI Sans", sans-serif; /* ALL UI, headings, body, nav, buttons */
--font-serif: "LF Serif", serif; /* editorial/research contexts only */
--font-mono: "KaTeX_Typewriter", "KaTeX_Main", monospace; /* math/code only */
```
### Weight scale
```css
--font-weight-regular: 400; /* body copy, links, captions */
--font-weight-medium: 500; /* headings h1–h6, nav, meta, buttons — DEFAULT UI weight */
--font-weight-semibold: 600; /* nav-header-secondary, display callouts only */
/* NOTE: 700 (bold) is only loaded for LF Serif and KaTeX — NEVER use bold on OpenAI Sans in UI */
```
### Composite type tokens (fluid)
Each group bundles size + line-height + weight + letter-spacing. Use all properties together.
```css
/* ── DISPLAY ── */
/* type-xl: hero display text */
--type-xl-size: clamp(4rem, calc(4rem + 3 * ((100vw - 23.4375rem) / 66.5625)), 7rem);
--type-xl-line: clamp(4rem, calc(4rem + 3 * ((100vw - 23.4375rem) / 66.5625)), 7rem);
--type-xl-weight: 500;
--type-xl-track: -0.02em;
/* ── HEADINGS ── */
/* type-h1 */
--type-h1-size: clamp(2rem, calc(2rem + 2 * ((100vw - 23.4375rem) / 66.5625)), 4rem);
--type-h1-line: clamp(2.28rem, calc(2.28rem + 1.72 * ((100vw - 23.4375rem) / 66.5625)), 4rem);
--type-h1-weight: 500;
--type-h1-track: -0.03em;
/* type-h2 */
--type-h2-size: clamp(2rem, calc(2rem + 1 * ((100vw - 23.4375rem) / 66.5625)), 3rem);
--type-h2-line: clamp(2.28rem, calc(2.28rem + 1.2 * ((100vw - 23.4375rem) / 66.5625)), 3.48rem);
--type-h2-weight: 500;
--type-h2-track: clamp(-0.03em, calc(-0.03em + 0.02 * ((90rem - 100vw) / 66.5625)), -0.01em);
/* type-h2-5: mid-heading (between h2 and h3) */
--type-h2-5-size: clamp(1.5rem, calc(1.5rem + .88 * ((100vw - 23.4375rem) / 66.5625)), 2.38rem);
--type-h2-5-line: clamp(1.725rem, calc(1.725rem + 1.012 * ((100vw - 23.4375rem) / 66.5625)), 2.737rem);
--type-h2-5-weight: 500;
--type-h2-5-track: -0.02em;
/* type-h3 */
--type-h3-size: clamp(1.5rem, calc(1.5rem + .375 * ((100vw - 23.4375rem) / 66.5625)), 1.875rem);
--type-h3-line: clamp(1.98rem, calc(1.98rem + .495 * ((100vw - 23.4375rem) / 66.5625)), 2.475rem);
--type-h3-weight: 500;
--type-h3-track: -0.01em;
/* type-h4 */
--type-h4-size: clamp(1.25rem, calc(1.25rem + .125 * ((100vw - 23.4375rem) / 66.5625)), 1.375rem);
--type-h4-line: clamp(1.5rem, calc(1.5rem + .2325 * ((100vw - 23.4375rem) / 66.5625)), 1.7325rem);
--type-h4-weight: 500;
--type-h4-track: -0.01em;
/* type-h5 */
--type-h5-size: clamp(1rem, calc(1rem + .125 * ((100vw - 23.4375rem) / 66.5625)), 1.125rem);
--type-h5-line: clamp(1.25rem, calc(1.25rem + .235 * ((100vw - 23.4375rem) / 66.5625)), 1.485rem);
--type-h5-weight: 500;
--type-h5-track: -0.01em;
/* type-h6 */
--type-h6-size: clamp(1rem, calc(1rem + .125 * ((100vw - 23.4375rem) / 66.5625)), 1.125rem);
--type-h6-line: clamp(1.25rem, calc(1.25rem + .235 * ((100vw - 23.4375rem) / 66.5625)), 1.485rem);
--type-h6-weight: 500;
--type-h6-track: -0.01em;
/* ── BODY ── */
/* type-p1: primary body */
--type-p1-size: 1.0625rem; /* 17px */
--type-p1-line: 1.74994rem; /* ~28px */
--type-p1-weight: 400;
--type-p1-track: -0.01em;
/* type-p2: secondary body */
--type-p2-size: .875rem; /* 14px */
--type-p2-line: 1.435rem; /* ~23px */
--type-p2-weight: 400;
--type-p2-track: -0.01em;
/* ── UI / FUNCTIONAL ── */
/* type-cta: button labels */
--type-cta-size: .875rem; /* 14px */
--type-cta-line: .875rem; /* 14px — tight, single-line buttons */
--type-cta-weight: 500;
--type-cta-track: 0em;
/* type-meta: eyebrows, labels, metadata */
--type-meta-size: .875rem; /* 14px */
--type-meta-line: 1.225rem;
--type-meta-weight: 500;
--type-meta-track: 0em;
/* type-caption */
--type-caption-size: .875rem;
--type-caption-line: 1.435rem;
--type-caption-weight: 400;
--type-caption-track: 0em;
/* type-nav: navigation items (responsive — shrinks on large screens) */
--type-nav-size: clamp(.875rem, calc(.875rem + .125 * ((90rem - 100vw) / 66.5625)), 1rem);
--type-nav-line: clamp(1.25rem, calc(1.25rem + .185 * ((100vw - 23.4375rem) / 66.5625)), 1.435rem);
--type-nav-weight: 500;
--type-nav-track: 0em;
/* type-nav-desktop */
--type-nav-desktop-size: .875rem;
--type-nav-desktop-line: 1.435rem;
--type-nav-desktop-weight: 500;
--type-nav-desktop-track: 0em;
/* type-nav-mobile */
--type-nav-mobile-size: 1rem;
--type-nav-mobile-line: 1.25rem;
--type-nav-mobile-weight: 500;
--type-nav-mobile-track: 0em;
/* type-nav-header */
--type-nav-header-size: .8125rem;
--type-nav-header-line: 1.23rem;
--type-nav-header-weight: 500;
--type-nav-header-track: 0em;
/* type-nav-header-secondary */
--type-nav-header-secondary-size: .8125rem;
--type-nav-header-secondary-line: 1.05rem;
--type-nav-header-secondary-weight: 600; /* only place 600 appears in nav */
--type-nav-header-secondary-track: -0.01em;
/* type-code-snippet */
--type-code-snippet-size: clamp(.8125rem, calc(.8125rem + .0625 * ((100vw - 23.4375rem) / 66.5625)), .875rem);
--type-code-snippet-line: clamp(1.3325rem, calc(1.3325rem + .1025 * ((100vw - 23.4375rem) / 66.5625)), 1.435rem);
--type-code-snippet-weight: 400;
--type-code-snippet-track: 0em;
/* type-xs / type-2xs: fine print, badges */
--type-xs-size: .625rem;
--type-xs-line: .825rem;
--type-xs-weight: 400;
--type-xs-track: clamp(-0.01em, calc(-0.01em + .01 * ((90rem - 100vw) / 66.5625)), 0em);
--type-2xs-size: .5rem;
--type-2xs-line: .66rem;
--type-2xs-weight: 400;
--type-2xs-track: clamp(-0.01em, calc(-0.01em + .01 * ((90rem - 100vw) / 66.5625)), 0em);
```
### Typography pairing rules
| Context | Token group | Notes |
|---|---|---|
| Hero/page title | `type-xl` or `type-h1` | Weight 500, tight tracking |
| Section heading | `type-h2` | Fluid, weight 500 |
| Sub-section | `type-h3` or `type-h4` | Weight 500 |
| Body copy | `type-p1` | 17px, weight 400 |
| Supporting body | `type-p2` | 14px, weight 400 |
| Button labels | `type-cta` | 14px, weight 500, tracking 0 |
| Nav links | `type-nav` (responsive) | Weight 500, tracking 0 |
| Code | `type-code-snippet` | Weight 400, tracking 0 |
| Editorial (blog/research) | `LF Serif` | NOT for UI chrome |
---
## 4. Spacing & Layout
### Base unit: **4px**
```css
/* ── SPACING SCALE ── (original CSS custom properties, extracted) */
--space-xs: 4px; /* 1× base — tight internal padding, icon gaps */
--space-sm: 16px; /* 4× base — component internal padding default */
--space-md: 20px; /* 5× base — medium component padding */
--space-lg: 24px; /* 6× base — card padding, section sub-gaps */
--space-xl: 32px; /* 8× base — body padding-right, section rhythm */
--space-2xl: 40px; /* 10× base — component-to-component separation */
--space-3xl: 120px; /* 30× base — major section vertical spacing */
/* ── STRUCTURAL / LAYOUT TOKENS ── */
--header-h: 4rem; /* 64px — sticky header height */
--toc-button-h: 3rem; /* 48px — table-of-contents button height */
```
**Note:** The extracted value for `--space-xs` is `4.2px`. **Round to 4px** to maintain the 4px grid — the 0.2px deviation is a floating-point artifact. Use `--space-xs: 4px` in all implementations.
### Grid & breakpoints
```css
/* ── RESPONSIVE BREAKPOINTS (Tailwind standard + extracted) ── */
--bp-sm: 640px;
--bp-md: 768px;
--bp-lg: 1024px;
--bp-xl: 1280px;
--bp-2xl: 1536px;
/* Fluid type viewport bounds (from clamp expressions) */
--vp-min: 23.4375rem; /* 375px — minimum viewport for fluid scaling */
--vp-max: 90rem; /* 1440px — maximum viewport for fluid scaling */
```
### Layout rules
- **Flex for components:** buttons, nav items, card rows — `display: flex; flex-direction: row; align-items: center`
- **Block for sections:** cards and navigation containers use `display: block`
- **Body gap:** `8px 12px` (row / column) — from computed body styles
- **Button padding:** `0 12px` horizontal, height set by line-height (no vertical padding on pill buttons)
- **Input padding:** `10px 24px 10px 52px` (left offset accommodates search icon)
- **Label/card padding:** `16px` all sides (base), `52px` right where close/action icons appear
- **Container:** max-width not directly extracted — infer `1280px` (`--bp-xl`) as standard content container with `--space-xl` (32px) side padding on mobile
---
## 5. Page Structure & Layout Patterns
> No screenshots available. All section rows marked with **(inferred)** are derived from the component inventory, layout digest computed styles, and typography hierarchy. Rows anchored to extracted values are marked **(extracted)**.
### 5.1 Section Map
| Order | Section | Layout type | Key elements | Source |
|---|---|---|---|---|
| 1 | **Nav / Header** | Sticky block, full-width | Logo, nav links (type-nav), CTA buttons (pill, border black/12), hamburger | (extracted — role_navigation) |
| 2 | **Hero** | Full-width, centered flex column | type-xl or type-h1 headline, subhead (type-p1), 1–2 CTA buttons (pill black + ghost), optional video/animation | (inferred) |
| 3 | **Feature / Product Highlight** | Full-width, 2-col or alternating | Headline (type-h2), body (type-p1), CTA link, image/media right | (inferred) |
| 4 | **Card Grid** | Grid, 3-col desktop / 1-col mobile | Cards (radius-md 6.08px, border black/12), type-p2 metadata, type-h4/h5 title | (extracted — card inventory, 20 instances) |
| 5 | **Recent News / Stories** | Grid or flex row | type-h2 "Recent news" (22px 500), article cards with image + type-p2 metadata | (extracted — typography census) |
| 6 | **CTA / Get Started** | Full-width, centered | type-3xl headline (48px), sub-body, primary pill button | (extracted — font-size-3xl on "Get started with ChatGPT") |
| 7 | **Footer** | Multi-column grid | Nav link groups (type-p2), logo, social icons, legal (type-caption) | (inferred) |
### 5.2 Layout Patterns
**Navigation (extracted):**
```
display: block
Internal nav links: display flex, flex-direction row, align-items center
Height: var(--header-h) = 4rem (64px)
Padding: 0px (spacing handled by child flex items)
Nav link size: var(--type-nav-desktop-size) = 0.875rem, weight 500
CTA buttons: pill (border-radius: 9999px), border: 1px solid rgba(0,0,0,0.12)
```
**Card Grid (extracted):**
```
Card element: display block, border-radius 6.08px (--radius-md)
Card border: 1px solid rgba(0,0,0,0.12)
Card padding: 0px (content inside has own padding)
Grid gap: infer --space-lg (24px) based on spacing scale
Desktop: 3 columns; Tablet (768px): 2 columns; Mobile: 1 column
```
**Hero section (inferred from typography census + button styles):**
```
Layout: flex column, align-items center, text-align center
Headline: var(--type-h1-size) fluid, font-weight 500, tracking -0.03em
Subhead: var(--type-p1-size) 1.0625rem, weight 400
CTA row: flex row, gap var(--space-sm) 16px
Primary CTA: black pill button, padding 0 var(--space-lg)
Ghost CTA: transparent pill button, border black/12
```
**Content section (inferred):**
```
Max-width: ~1280px, centered
Side padding: var(--space-xl) 32px
Vertical section gap: var(--space-3xl) 120px between major sections
Sub-section gap: var(--space-2xl) 40px
```
### 5.3 Visual Hierarchy
- **Most prominent element:** Hero headline at `clamp(2rem, ..., 4rem)`, weight 500, tracking –0.03em
- **Primary CTA position:** Hero section — black pill button, immediately after headline/subhead
- **Secondary CTA:** Ghost/outline pill alongside primary, same row
- **Section headings:** type-h2 at 22px (desktop computed) weight 500 — confirmed by census (`h2: 22px 500`)
- **Whitespace rhythm:** `--space-3xl` (120px) between major page sections; `--space-2xl` (40px) between components within a section
- **Image position:** Right column or full-bleed behind text in alternating feature sections (inferred)
- **Colour hierarchy:** Black primary CTAs dominate; ghost/outline CTAs recede; no coloured buttons in primary chrome
### 5.4 Content Patterns
**Repeating card pattern:**
```
[Image/media block — top, full card width]
[Metadata row — type-meta, weight 500, tracking 0, category + read time]
[Title — type-h4 or type-h5, weight 500, tracking -0.01em]
[Optional excerpt — type-p2, weight 400]
[CTA link — type-cta or inline text link]
```
**Feature highlight pattern:**
```
[Eyebrow label — type-meta, weight 500]
[Heading — type-h2 or type-h3, weight 500]
[Body — type-p1, weight 400, max ~60ch]
[CTA — inline text link or pill button]
```
**Nav item pattern:**
```
[Link text — type-nav-desktop-size 0.875rem, weight 500]
[Hover: transition color var(--duration-fast) 0.1s ease]
[Focus: outline ring var(--tw-ring-color) #3b82f680]
[Dropdown: display block, padding 16px 16px 0, type text rgba(0,0,0,0.6)]
```
---
## 6. Component Patterns
### 6.1 Button
**Anatomy:** `[optional-icon] [label]` — flex row, align center, pill radius
**Token-to-property mapping:**
| State | bg | border | text | shadow | transform |
|---|---|---|---|---|---|
| default (primary) | `#000` | `1px solid #000` | `#fff` | `--shadow-sm` | — |
| hover (primary) | `rgba(0,0,0,0.80)` | same | `#fff` | — | — |
| default (ghost) | `transparent` | `1px solid rgba(0,0,0,0.12)` | `#000` | `--shadow-sm` | — |
| hover (ghost) | `rgba(0,0,0,0.04)` | same | `#000` | — | — |
| focus | any | `outline: 2px solid var(--tw-ring-color)` | — | — | — |
| active | `rgba(0,0,0,0.90)` | same | — | — | `scale(0.98)` |
| disabled | any | same | — | — | `opacity: 0.40` |
| loading | any | same | hidden | — | spinner visible |
**Transition:** `color, background-color, border-color, opacity, box-shadow, transform` all at `var(--duration-slow) cubic-bezier(0,0,1,1)`
```tsx
// Button.tsx — complete with all states
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'ghost';
loading?: boolean;
children: React.ReactNode;
}
export function Button({ variant = 'primary', loading, disabled, children, ...props }: ButtonProps) {
const base = [
'inline-flex items-center justify-center gap-2',
'px-[var(--space-lg)] h-10', // 0 24px horizontal, 40px height
'rounded-[var(--radius-full)]', // 9999px pill
'text-[length:var(--type-cta-size)]', // 0.875rem
'font-[500]', // --font-weight-medium
'leading-[var(--type-cta-line)]', // 0.875rem
'tracking-[var(--type-cta-track)]', // 0em
'select-none whitespace-nowrap',
'transition-[color,background-color,border-color,opacity,box-shadow,transform]',
'duration-[var(--duration-slow)]', // 0.25s
'ease-[var(--ease-default)]', // ease
'focus-visible:outline-none',
'focus-visible:ring-2',
'focus-visible:ring-[var(--tw-ring-color)]', // #3b82f680
'focus-visible:ring-offset-2',
'focus-visible:ring-offset-[var(--tw-ring-offset-color)]', // #fff
'disabled:opacity-40 disabled:cursor-not-allowed disabled:pointer-events-none',
'active:scale-[0.98]',
];
const variants = {
primary: [
'bg-black text-white border border-black',
'hover:bg-black/80',
'shadow-[rgba(0,0,0,0)_0px_0px_0px_0px,rgba(0,0,0,0)_0px_0px_0px_0px,rgba(0,0,0,0.02)_0px_4px_6px_0px,rgba(0,0,0,0.05)_0px_0px_2px_0px]',
],
ghost: [
'bg-transparent text-black border border-black/[0.12]',
'hover:bg-black/[0.04]',
'shadow-[rgba(0,0,0,0)_0px_0px_0px_0px,rgba(0,0,0,0)_0px_0px_0px_0px,rgba(0,0,0,0.02)_0px_4px_6px_0px,rgba(0,0,0,0.05)_0px_0px_2px_0px]',
],
};
return (
<button
{...props}
disabled={disabled || loading}
className={[...base, ...variants[variant], props.className].filter(Boolean).join(' ')}
aria-busy={loading}
>
{loading ? (
<>
<span className="sr-only">Loading…</span>
<svg className="animate-spin h-4 w-4" viewBox="0 0 24 24" fill="none" aria-hidden>
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" />
</svg>
</>
) : children}
</button>
);
}
```
---
### 6.2 Card
**Anatomy:** `[media] [content: [meta] [title] [body] [cta]]`
**Token-to-property mapping:**
| State | bg | border | radius | shadow | opacity |
|---|---|---|---|---|---|
| default | `transparent` | `none` | `6.08px` | `none` | 1 |
| hover | `transparent` | `none` | `6.08px` | `none` | varies (transition: opacity 0.3s) |
| focus | `transparent` | `outline: 2px var(--tw-ring-color)` | same | same | 1 |
| disabled | `transparent` | same | same | same | 0.4 |
```tsx
// Card.tsx
interface CardProps {
category?: string;
readTime?: string;
title: string;
excerpt?: string;
href: string;
imageSrc?: string;
imageAlt?: string;
}
export function Card({ category, readTime, title, excerpt, href, imageSrc, imageAlt }: CardProps) {
return (
<a
href={href}
className={[
'group block',
'rounded-[6.08px]', // --radius-md exactly
'overflow-hidden',
'transition-opacity duration-300', // card transition: opacity 0.3s cubic-bezier(0,0.56,0.46,1)
'ease-[cubic-bezier(0,0.56,0.46,1)]',
'hover:opacity-80',
'focus-visible:outline-none',
'focus-visible:ring-2',
'focus-visible:ring-[var(--tw-ring-color)]',
'focus-visible:ring-offset-2',
].join(' ')}
>
{imageSrc && (
<div className="w-full aspect-[16/9] overflow-hidden bg-black/[0.04]">
<img
src={imageSrc}
alt={imageAlt ?? ''}
className="w-full h-full object-cover transition-transform duration-[var(--duration-slow)]
group-hover:scale-[1.02]"
/>
</div>
)}
<div className="pt-[var(--space-sm)] pb-[var(--space-lg)]">
{(category || readTime) && (
<p
className="text-[length:var(--type-meta-size)] font-[500] tracking-[0em]
leading-[var(--type-meta-line)] text-black/60 mb-[var(--space-xs)]"
>
{category}{readTime && <span>{category ? '\u00A0· ' : ''}{readTime}</span>}
</p>
)}
<h3
className="text-[length:var(--type-h5-size)] font-[500]
leading-[var(--type-h5-line)] tracking-[-0.01em] text-black"
>
{title}
</h3>
{excerpt && (
<p
className="mt-[var(--space-xs)] text-[length:var(--type-p2-size)] font-[400]
leading-[var(--type-p2-line)] tracking-[-0.01em] text-black/60"
>
{excerpt}
</p>
)}
</div>
</a>
);
}
```
---
### 6.3 Input (Search / Chat)
**Anatomy:** `[search-icon (left)] [input field] [action button (right)]`
**Token-to-property mapping:**
| State | bg | border | radius | shadow |
|---|---|---|---|---|
| default | `transparent` | `none` | `9999px` | `none` |
| focus-within (container) | `#fff` | `none` | `9999px` | `none` (outline suppressed) |
| hover | `transparent` | — | — | — |
| disabled | `transparent` | — | — | — |
```tsx
// SearchInput.tsx
interface SearchInputProps {
value: string;
onChange: (v: string) => void;
placeholder?: string;
disabled?: boolean;
}
export function SearchInput({ value, onChange, placeholder = 'Ask anything', disabled }: SearchInputProps) {
return (
<div
className={[
'relative flex items-center',
'rounded-[var(--radius-full)]', // 9999px pill
'border border-black/[0.12]',
'bg-white',
'shadow-[rgba(0,0,0,0)_0px_0px_0px_0px,rgba(0,0,0,0.06)_0px_2px_5px_0px,rgba(0,0,0,0.01)_0px_4px_4px_0px]',
'has-[:focus]:outline-none', // suppress default per extracted CSS
'has-[:focus]:shadow-none',
'transition-shadow duration-[var(--duration-base)]',
].join(' ')}
>
{/* Search icon */}
<span className="absolute left-[var(--space-lg)] text-black/40 pointer-events-none" aria-hidden>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<circle cx="6.5" cy="6.5" r="5" stroke="currentColor" strokeWidth="1.5" />
<path d="M10.5 10.5L14 14" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
</svg>
</span>
<input
type="search"
value={value}
onChange={e => onChange(e.target.value)}
placeholder={placeholder}
disabled={disabled}
className={[
'w-full bg-transparent',
'pl-[52px] pr-[var(--space-lg)] py-[10px]', // exact extracted padding
'rounded-[var(--radius-full)]',
'text-[length:var(--type-p2-size)]', // 0.875rem / 14px
'font-[500]',
'leading-[var(--type-cta-line)]', // 14px
'text-black placeholder:text-black/40',
'outline-none border-none',
'disabled:opacity-40 disabled:cursor-not-allowed',
'[transition:height_0.4s_cubic-bezier(0.4,0,0.2,1)]', // extracted transition
].join(' ')}
/>
</div>
);
}
```
---
### 6.4 Nav Item
**Anatomy:** `[label] [optional-arrow/chevron]`
**Token-to-property mapping:**
| State | color | text-decoration | transition |
|---|---|---|---|
| default | `rgb(0,0,0)` | none | `color var(--duration-fast) cubic-bezier(0,0,1,1)` |
| hover | `rgb(0,0,0)` (unchanged) | none (or underline for sub-links) | same |
| focus | `rgb(0,0,0)` | `outline: 2px var(--tw-ring-color)` | — |
| active | `rgba(0,0,0,0.6)` | none | — |
```tsx
// NavItem.tsx
interface NavItemProps {
label: string;
href: string;
isActive?: boolean;
}
export function NavItem({ label, href, isActive }: NavItemProps) {
return (
<a
href={href}
aria-current={isActive ? 'page' : undefined}
className={[
'flex items-center',
'py-[var(--space-md)]', // 20px vertical — extracted from link padding
'text-[length:var(--type-nav-desktop-size)]', // 0.875rem
'font-[500]', // --type-nav-desktop-weight
'leading-[var(--type-nav-desktop-line)]', // 1.435rem
'tracking-[var(--type-nav-desktop-track)]', // 0em
'text-black',
'transition-colors',
'duration-[var(--duration-fast)]', // 0.1s
'ease-[cubic-bezier(0,0,1,1)]',
'hover:text-black/70',
'focus-visible:outline-none',
'focus-visible:ring-2',
'focus-visible:ring-[var(--tw-ring-color)]',
'focus-visible:ring-offset-2',
'focus-visible:rounded-[var(--radius-sm)]',
isActive ? 'text-black' : 'text-black/80',
].join(' ')}
>
{label}
</a>
);
}
```
---
### 6.5 Modal
**Anatomy:** `[backdrop] [panel: [header] [body] [footer]]`
| State | display | bg | transition |
|---|---|---|---|
| closed | `none` | `#fff` | `clip-path 0.2s, opacity 0.2s cubic-bezier(0.4,0,0.2,1)` |
| open | `block` | `#fff` | same (enter animation: `content-show` keyframe) |
| focus-trap | — | — | `focus-visible:ring-2 ring-[var(--tw-ring-color)]` |
**Motion:** Uses `@keyframes content-show` — `opacity 0 → 1`, `translate -50% -48% → -50% -50%`, `scale 0.96 → 1`
---
### 6.6 Dropdown
**Anatomy:** `[trigger button] [panel: [section-header] [items]]`
| State | color | bg | transition |
|---|---|---|---|
| default (panel) | `rgba(0,0,0,0.6)` | `transparent` | `all var(--ease-default)` |
| item hover | `rgb(0,0,0)` | — | `color var(--duration-fast)` |
| item focus | — | — | `ring-2 var(--tw-ring-color)` |
**Typography:** Panel body `16px / 24px / weight 400 / tracking -0.16px` (from computed dropdown styles)
---
## 7. Elevation & Depth
```css
/* ── SHADOW TOKENS ── */
/* --shadow-sm: extracted CSS variable — used on buttons and labeled surfaces */
--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,
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0.02) 0px 4px 6px 0px,
rgba(0, 0, 0, 0.05) 0px 0px 2px 0px;
/* Applies to: pill buttons, ghost buttons — nearly imperceptible lift */
/* Computed from label element — slightly heavier than --shadow-sm */
--shadow-label: rgba(0, 0, 0, 0.06) 0px 2px 5px 0px,
rgba(0, 0, 0, 0.01) 0px 4px 4px 0px;
/* Applies to: interactive label cards, option selectors */
/* Tailwind ring shadows (internal Tailwind infrastructure — do not use directly) */
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
```
### Border tokens
```css
/* ── BORDER TOKENS ── */
--border-default: 1px solid rgba(0, 0, 0, 0.12); /* buttons, inputs, cards with borders */
--border-subtle: 1px solid rgb(229, 231, 235); /* computed from border: 0px solid rgb(229,231,235) on base elements — used as reset */
```
### Z-index scale
```css
/* ── Z-INDEX SCALE (inferred from component roles) ── */
/* reconstructed: moderate confidence, inferred from component types */
--z-base: 0; /* page content */
--z-elevated: 10; /* cards, dropdowns */
--z-sticky: 100; /* sticky header (--header-h) */
--z-modal: 200; /* modals, overlays */
--z-toast: 300; /* toasts, notifications */
```
### Layering principles
- **Almost no shadows** — openai.com is nearly shadow-free. The single `--shadow-sm` is cosmetically invisible at 2% opacity.
- **Borders over shadows** — depth is communicated via `border: 1px solid rgba(0,0,0,0.12)`, not shadows.
- **Backdrop/blur:** No backdrop-filter in use on primary components (extracted `backdropFilter: none` on all key elements). Reserved for special interactive modules only.
- **Modal elevation:** Uses `clip-path` animation, not shadow stacking.
---
## 8. Motion
```css
/* ── DURATION TOKENS ── */
--duration-fast: 0.1s; /* micro-interactions: hover colour, link colour */
--duration-base: 0.2s; /* standard UI transitions: modal open/close */
--duration-slow: 0.25s; /* button transitions, primary interactive elements */
/* ── EASING TOKENS ── */
--ease-default: ease; /* general UI transitions */
--ease-linear: cubic-bezier(0, 0, 1, 1); /* colour/opacity transitions (extracted from buttons, links) */
--ease-standard: cubic-bezier(0.4, 0, 0.2, 1); /* modal enter, input height, content reveal */
--ease-decelerate: cubic-bezier(0, 0.56, 0.46, 1); /* card opacity — enters with deceleration */
/* ── TRANSITION PATTERNS ── */
/* Fast (links, nav hover): color/bg/border/fill/stroke at 0.1s cubic-bezier(0,0,1,1) */
/* Slow (buttons, primary controls): all interactive properties at 0.25s cubic-bezier(0,0,1,1) */
/* Modal: clip-path + opacity at 0.2s cubic-bezier(0.4,0,0.2,1) */
/* Input height: height at 0.4s cubic-bezier(0.4,0,0.2,1) */
/* Card: opacity at 0.3s cubic-bezier(0,0.56,0.46,1) */
```
### Named keyframes (extracted)
| Animation | Usage | Key behaviour |
|---|---|---|
| `fade-in` / `fade-out` | General show/hide | opacity 0↔1 |
| `content-show` | Modal entrance | scale 0.96→1 + translate |
| `slide-in-start` / `slide-in-end` | Panel slides | translateX, respects `--dir` |
| `scale-up` | Accordion expand | `grid-template-rows: 0 → 1fr` |
| `enter` / `exit` | Tailwind Animate primitives | opacity + transform |
| `spin` | Loading spinner | `rotate(360deg)` |
| `pulse` | Skeleton/loading | opacity 1→0.5 |
| `skeleton-loading` | Skeleton screen | background-position sweep |
| `search-loading-gradient` | Search skeleton | bg-position sweep |
| `Float-module float-x/y-rotate` | Hero floating elements | translate + rotate loop |
| `ComputerScreen adjustment-toast-fade` | Toast notification | fade + translate lifecycle |
| CRT variants | Interactive feature demos | Glitch, flicker, typewriter |
### Motion rules
- **Use `prefers-reduced-motion`:** Suppress all keyframe animations. Transitions ≤ 0.1s may remain.
- **No spring/bounce:** All easings are linear-feeling (`cubic-bezier(0,0,1,1)`) or standard material (`cubic-bezier(0.4,0,0.2,1)`). Never use `cubic-bezier(0.34, 1.56, 0.64, 1)` (bounce) or similar.
- **Opacity + transform only** for enter/exit animations — never animate `width`, `height` (except input height via its specific transition), or layout properties.
- **0.25s maximum** for interactive hover transitions on UI controls.
---
## 9. Anti-Patterns & Constraints
1. **Hardcoded colour values → Why it fails → What to do instead.**
AI agents default to writing `color: #000000` or `backgroundColor: '#3b82f6'` directly in style props or CSS. This breaks theming, makes future token updates require grep-and-replace across the entire codebase, and loses semantic intent. **→** Always use `var(--tw-ring-color)` for focus rings, `var(--color-text-primary)` (or Tailwind `text-black`) for text, and `border-black/[0.12]` for borders. Never hardcode hex values.
2. **Using Inter, Roboto, or system-ui as the font → Why it fails → What to do instead.**
AI agents trained on common codebases default to `font-family: 'Inter', sans-serif` because it appears in the majority of Tailwind starters. On OpenAI.com this renders entirely the wrong typeface — the proprietary `OpenAI Sans` has distinct letterforms, spacing, and weight mapping. **→** Always declare `font-family: "OpenAI Sans", sans-serif` or rely on the body-level CSS declaration. Never override with a different sans-serif.
3. **Using `border-radius: 8px` or `rounded-lg` for buttons → Why it fails → What to do instead.**
AI agents applying a "common design system" heuristic assume medium-radius buttons (8px). OpenAI uses pill buttons (`border-radius: 9999px`) as a brand signal — 19 elements use `radius-full`, making it the dominant button shape. An 8px radius button looks like a generic SaaS product, not OpenAI. **→** Always use `rounded-[var(--radius-full)]` (9999px) for buttons, or `rounded-full` in Tailwind. Cards use `rounded-[6.08px]` (`--radius-md`).
4. **Using font-weight 700 (bold) on OpenAI Sans → Why it fails → What to do instead.**
The weight census shows: regular (400) on body, medium (500) on ALL headings and UI chrome, semibold (600) only in one nav-header-secondary context. The bold weight (700) is loaded only for `LF Serif` and `KaTeX_*` families. Applying `font-weight: 700` to `OpenAI Sans` renders with synthesised bold (browser-faked), which looks visually heavier and different from the designed weight. **→** Max weight for `OpenAI Sans` UI is `font-weight: 600`. Default heading/UI weight is `500`.
5. **Applying positive letter-spacing to headings → Why it fails → What to do instead.**
Heading letter-spacing on openai.com is consistently negative: h1 = –0.03em, h2 = –0.01em to –0.03em (fluid), h3–h6 = –0.01em. AI agents writing `letter-spacing: 0` or `letter-spacing: 0.05em` destroy the premium, tight feel of display type. **→** Always apply `--type-h*-track` values (–0.01em to –0.03em) to heading elements. Only functional/UI type (`type-cta`, `type-nav`, `type-meta`) uses `0em` tracking.
6. **Constructing Tailwind class names dynamically → Why it fails → What to do instead.**
Writing `className={\`text-${sizeVar}\`}` or `\`bg-${colorVar}\`` causes Tailwind's JIT compiler to miss the class at build time — it only includes classes that appear as complete strings in scanned files. The element renders with no styling applied. **→** Use complete class strings (`text-[length:var(--type-p2-size)]`, `bg-black`) or CSS-in-JS `style={{ fontSize: 'var(--type-p2-size)' }}`. Never concatenate partial Tailwind class names.
7. **Missing focus-visible states on interactive elements → Why it fails → What to do instead.**
AI agents commonly implement hover styles but omit focus styles, reasoning that "hover covers it." Keyboard users and assistive technologies depend on visible focus rings. OpenAI uses `--tw-ring-color: #3b82f680` (blue, 60% opacity) as the focus ring token, applied via Tailwind `focus-visible:ring-2`. Omitting this makes the component inaccessible and inconsistent with the design system. **→** Every interactive element must have `focus-visible:ring-2 focus-visible:ring-[var(--tw-ring-color)] focus-visible:ring-offset-2`.
8. **Using off-grid spacing values → Why it fails → What to do instead.**
The extraction found `--space-xs: 4.2px` — a floating-point artifact. AI agents may copy this verbatim and also invent values like `13px`, `18px`, or `22px` for padding. These break the visual rhythm established by the 4px grid and create misaligned layouts when composed. **→** Round to the nearest 4px multiple and use `--space-*` tokens exclusively. The corrected `--space-xs` is `4px`.
9. **Using `display: block` with negative margins to fake flex layout → Why it fails → What to do instead.**
The extracted styles show clear flex patterns: `button_primary: display flex, align-items center`; `body: display flex, gap 8px 12px`. AI agents sometimes generate block-level elements with `margin` hacks instead of flex, leading to fragile layouts that break on text reflow or zoom. **→** Use `display: flex` with `gap: var(--space-sm)` (16px) for button content, and `display: flex; flex-direction: row; align-items: center` for nav items, exactly as extracted.
10. **Applying `!important` to override token values → Why it fails → What to do instead.**
AI agents encountering specificity conflicts from Tailwind's utility classes sometimes reach for `!important` rather than understanding specificity. On a Tailwind + CSS-variables stack, `!important` on a custom property can prevent runtime theme switching and breaks the token cascade entirely. **→** Resolve specificity by adjusting class order, using Tailwind's `[&_selector]` arbitrary-variant syntax, or restructuring component composition. Never use `!important` on token values.
11. **Using `position: absolute` for primary layout structure → Why it fails → What to do instead.**
The OpenAI page uses `block` and `flex` layout — computed styles show no absolute positioning in the main content flow. AI agents sometimes place hero text or card grids with `position: absolute; top: X; left: Y`, which breaks responsive reflow, causes overflow clipping, and makes the layout unmaintainable. **→** Use `display: flex` with gap tokens for component layout, CSS Grid for card grids, and reserve `position: absolute` only for overlays (search icons inside inputs, close buttons inside modals) as evidenced by the `input` computed padding (`padding-left: 52px` to accommodate an absolute-positioned icon).
---
## 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) */
--tw-ring-offset-color: #fff;
--tw-ring-color: #3b82f680;
--tw-shadow-colored: 0 0 #0000;
--color-black: #000000;
--color-white: #ffffff;
--color-blue-500-60: #3b82f680;
--color-black-12: rgba(0, 0, 0, 0.12);
--color-black-60: rgba(0, 0, 0, 0.60);
--color-black-50: rgba(0, 0, 0, 0.50);
--color-black-25: rgba(0, 0, 0, 0.25);
--color-black-06: rgba(0, 0, 0, 0.06);
--color-black-02: rgba(0, 0, 0, 0.02);
--color-black-05: rgba(0, 0, 0, 0.05);
--color-surface: var(--color-white);
--color-surface-elevated: var(--color-white);
--color-text-primary: var(--color-black);
--color-text-secondary: var(--color-black-60);
--color-border-default: var(--color-black-12);
--color-focus-ring: var(--tw-ring-color);
--color-focus-bg: var(--tw-ring-offset-color);
--btn-primary-bg: var(--color-black);
--btn-primary-border: var(--color-black);
--btn-primary-bg-hover: rgba(0, 0, 0, 0.80);
--btn-ghost-bg: transparent;
--btn-ghost-border: var(--color-border-default);
--btn-ghost-bg-hover: rgba(0, 0, 0, 0.04);
--input-bg: transparent;
--input-bg-active: var(--color-white);
--input-border: var(--color-border-default);
--card-bg: transparent;
--card-border: var(--color-border-default);
--nav-bg: transparent;
--border-default: 1px solid rgba(0, 0, 0, 0.12);
--border-subtle: 1px solid rgb(229, 231, 235);
/* Typography (48) */
--type-xl-size: clamp(4rem, calc(4rem + 3 * ((100vw - 23.4375rem) / 66.5625)), 7rem);
--type-h1-size: clamp(2rem, calc(2rem + 2 * ((100vw - 23.4375rem) / 66.5625)), 4rem);
--type-h1-line: clamp(2.28rem, calc(2.28rem + 1.72 * ((100vw - 23.4375rem) / 66.5625)), 4rem);
--type-h2-size: clamp(2rem, calc(2rem + 1 * ((100vw - 23.4375rem) / 66.5625)), 3rem);
--type-h2-line: clamp(2.28rem, calc(2.28rem + 1.2 * ((100vw - 23.4375rem) / 66.5625)), 3.48rem);
--type-h2-track: clamp(-.03em, calc(-.03em + .02 * ((90rem - 100vw) / 66.5625)), -.01em);
--type-h2-5-size: clamp(1.5rem, calc(1.5rem + .88 * ((100vw - 23.4375rem) / 66.5625)), 2.38rem);
--type-h2-5-line: clamp(1.725rem, calc(1.725rem + 1.012 * ((100vw - 23.4375rem) / 66.5625)), 2.737rem);
--type-h3-size: clamp(1.5rem, calc(1.5rem + .375 * ((100vw - 23.4375rem) / 66.5625)), 1.875rem);
--type-h3-line: clamp(1.98rem, calc(1.98rem + .495 * ((100vw - 23.4375rem) / 66.5625)), 2.475rem);
--type-h4-size: clamp(1.25rem, calc(1.25rem + .125 * ((100vw - 23.4375rem) / 66.5625)), 1.375rem);
--type-h4-line: clamp(1.5rem, calc(1.5rem + .2325 * ((100vw - 23.4375rem) / 66.5625)), 1.7325rem);
--type-h5-size: clamp(1rem, calc(1rem + .125 * ((100vw - 23.4375rem) / 66.5625)), 1.125rem);
--type-h5-line: clamp(1.25rem, calc(1.25rem + .235 * ((100vw - 23.4375rem) / 66.5625)), 1.485rem);
--type-p1-size: 1.0625rem;
--type-caption-size: .875rem;
--type-nav-size: clamp(.875rem, calc(.875rem + .125 * ((90rem - 100vw) / 66.5625)), 1rem);
--type-nav-line: clamp(1.25rem, calc(1.25rem + .185 * ((100vw - 23.4375rem) / 66.5625)), 1.435rem);
--type-nav-mobile-size: 1rem;
--type-nav-header-secondary-size: .8125rem;
--type-code-snippet-size: clamp(.8125rem, calc(.8125rem + .0625 * ((100vw - 23.4375rem) / 66.5625)), .875rem);
--type-code-snippet-line: clamp(1.3325rem, calc(1.3325rem + .1025 * ((100vw - 23.4375rem) / 66.5625)), 1.435rem);
--type-xs-size: .625rem;
--type-xs-track: clamp(-.01em, calc(-.01em + .01 * ((90rem - 100vw) / 66.5625)), 0em);
--type-2xs-size: .5rem;
--font-size-xs: 13px; /* 13 elements — e.g. span "Research", span "Research", span "Products" /* mined from computed styles */ */
--font-size-sm: 14px; /* 126 elements — e.g. p "Product18 min read", p "Product", p "Product7 min read" /* mined from computed styles */ */
--font-size-md: 17px; /* 45 elements — e.g. a "Research", a "Business", a "Developers" /* mined from computed styles */ */
--font-size-lg: 18px; /* 21 elements — e.g. p "A salvage yard in Ne", p "A seed farm in South", p "A tamale shop in Cal" /* mined from computed styles */ */
--font-size-xl: 22px; /* 4 elements — e.g. h2 "Recent news", h2 "Stories", h2 "Latest research" /* mined from computed styles */ */
--font-size-2xl: 28px; /* 1 element — e.g. span "What can I help with" /* mined from computed styles */ */
--font-size-3xl: 48px; /* 2 elements — e.g. h2 "Get started with Cha", div "Introducing GPT-5.5" /* mined from computed styles */ */
--font-weight-regular: 400; /* 46 elements — e.g. a "Research", a "Business", a "Developers" /* mined from computed styles */ */
--font-weight-medium: 500; /* 166 elements — e.g. h2 "Recent news", h2 "Stories", h2 "Latest research" /* mined from computed styles */ */
--font-weight-semibold: 600; /* 1 element — e.g. span "What can I help with" /* mined from computed styles */ */
--line-height-tight: 19.6px; /* 44 elements — e.g. p "Product18 min read", p "Product", p "Product7 min read" /* mined from computed styles */ */
--line-height-normal: 22.96px; /* 64 elements — e.g. a "Research Index", a "Research Overview", a "Research Residency" /* mined from computed styles */ */
--line-height-loose: 27.999px; /* 45 elements — e.g. a "Research", a "Business", a "Developers" /* mined from computed styles */ */
--btn-primary-text: var(--color-white);
--btn-ghost-text: var(--color-black);
--input-text: var(--color-text-primary);
--nav-text: var(--color-text-primary);
--font-sans: "OpenAI Sans", sans-serif;
--font-serif: "LF Serif", serif;
--font-mono: "KaTeX_Typewriter", "KaTeX_Main", monospace;
--font-weight-regular: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
/* Spacing (50) */
--type-xl-track: -.02em;
--type-h1-track: -.03em;
--type-nav-header-secondary-track: -.01em;
--type-p1-line: 1.74994rem;
--type-caption-line: 1.435rem;
--type-meta-line: 1.225rem;
--type-caption-track: 0em;
--type-cta-line: .875rem;
--type-nav-mobile-line: 1.25rem;
--type-nav-header-line: 1.23rem;
--type-nav-header-secondary-line: 1.05rem;
--type-xs-line: .825rem;
--type-2xs-line: .66rem;
--tw-border-spacing-x: 0;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--header-h: 4rem;
--toc-button-h: 3rem;
--space-xs: 4.2px; /* 22 elements — e.g. button .transition, button .transition, button .transition /* mined from computed styles */ */
--space-sm: 16px; /* 48 elements — e.g. div .px-sm, div .px-sm, div .px-sm /* mined from computed styles */ */
--space-md: 20px; /* 11 elements — e.g. div .gap-xs, div .gap-xs, div .gap-xs /* mined from computed styles */ */
--space-lg: 24px; /* 48 elements — e.g. div .@lg:max-w-container, div .@lg:max-w-container, div .@lg:max-w-container /* mined from computed styles */ */
--space-xl: 32px; /* 43 elements — e.g. div .px-sm, div .px-sm, div .-mb-2xl /* mined from computed styles */ */
--space-2xl: 40px; /* 9 elements — e.g. div .mt-11, div .mt-11, div .mt-11 /* mined from computed styles */ */
--space-3xl: 120px; /* 10 elements — e.g. article .flex, article .flex, article .flex /* mined from computed styles */ */
--type-p1-size: 1.0625rem;
--space-xs: 4px;
--space-sm: 16px;
--space-md: 20px;
--space-lg: 24px;
--space-xl: 32px;
--space-2xl: 40px;
--space-3xl: 120px;
--type-p2-line: 1.435rem;
--type-cta-track: 0em;
--type-meta-track: 0em;
--type-nav-track: 0em;
--type-nav-desktop-line: 1.435rem;
--type-nav-desktop-track: 0em;
--type-nav-mobile-size: 1rem;
--type-nav-mobile-track: 0em;
--type-nav-header-track: 0em;
--type-code-snippet-track: 0em;
--bp-sm: 640px;
--bp-md: 768px;
--bp-lg: 1024px;
--bp-xl: 1280px;
--bp-2xl: 1536px;
--vp-min: 23.4375rem;
--vp-max: 90rem;
/* Radius (10) */
--radius-sm: 4px; /* 5 elements — e.g. a .transition "View more", a .transition "View more", a .transition "View all" /* mined from computed styles */ */
--radius-md: 6.08px; /* 20 elements — e.g. div .ease-curve-c, div .ease-curve-c, div .ease-curve-c /* mined from computed styles */ */
--radius-lg: 40px; /* 10 elements — e.g. button .transition "Log in", a .transition "Skip to main content", a .transition "Try ChatGPT(opens in" /* mined from computed styles */ */
--radius-full: 9999px; /* 19 elements — e.g. button .ease-curve-a, button .ease-curve-a, button .ease-curve-a /* mined from computed styles */ */
--radius-sm: 4px;
--radius-md: 6.08px;
--radius-lg: 40px;
--radius-full: 9999px;
--input-radius: var(--radius-full);
--card-radius: var(--radius-md);
/* Effects (14) */
--type-xl-weight: 500;
--type-caption-weight: 400;
--type-nav-header-secondary-weight: 600;
--tw-rotate: 0;
--dir: 1;
--tw-pan-x: ;
--tw-scroll-snap-strictness: proximity;
--tw-shadow: 0 0 #0000;
--lightningcss-dark: initial;
--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.2KB elided>; /* 1 element — e.g. button .transition /* mined from computed styles */ */
--shadow-sm: rgba(0,0,0,0.02) 0px 4px 6px 0px, rgba(0,0,0,0.05) 0px 0px 2px 0px;
--shadow-label: rgba(0,0,0,0.06) 0px 2px 5px 0px, rgba(0,0,0,0.01) 0px 4px 4px 0px;
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
/* Motion (37) */
----motion-TerminalSequencedTypewriterText-module__cZh51G__terminal-sequenced-cursor-flicker: @keyframes TerminalSequencedTypewriterText-module__cZh51G__terminal-sequenced-cursor-flicker {
0%, 100% { opacity: 1; }
50% { opacity: 0.2; }
}; /* @keyframes TerminalSequencedTypewriterText-module__cZh51G__terminal-sequenced-cursor-flicker */
----motion-TerminalTypewriterText-module__khG1Da__terminal-typewriter-cursor-flicker: @keyframes TerminalTypewriterText-module__khG1Da__terminal-typewriter-cursor-flicker {
0%, 100% { opacity: 1; }
50% { opacity: 0.2; }
}; /* @keyframes TerminalTypewriterText-module__khG1Da__terminal-typewriter-cursor-flicker */
----motion-CRTScreen-module__vxH9Iq__crt-flicker: @keyframes CRTScreen-module__vxH9Iq__crt-flicker {
0%, 100% { opacity: var(--… <0.2KB elided>; /* @keyframes CRTScreen-module__vxH9Iq__crt-flicker */
----motion-CRTScreen-module__vxH9Iq__crt-pulse: @keyframes CRTScreen-module__vxH9Iq__crt-pulse {
0%, 100% { opacity: calc(.1 * var(--crt-overlay-intensity)); }
50% { opacity: var(--crt-overlay-after-pulse); }
}; /* @keyframes CRTScreen-module__vxH9Iq__crt-pulse */
----motion-CRTScreen-module__vxH9Iq__crt-typewriter: @keyframes CRTScreen-module__vxH9Iq__crt-typewriter {
0% { clip-path: inset(0px 100% 0px 0px); }
100% { clip-path: inset(0px); }
}; /* @keyframes CRTScreen-module__vxH9Iq__crt-typewriter */
----motion-CRTScreen-module__vxH9Iq__crt-blink-cursor: @keyframes CRTScreen-module__vxH9Iq__crt-blink-cursor {
0% { opacity: 0; }
100% { opacity: 1; }
}; /* @keyframes CRTScreen-module__vxH9Iq__crt-blink-cursor */
----motion-CRTScreen-module__vxH9Iq__crt-content-glitch: @keyframes CRTScreen-module__vxH9Iq__crt-content-glitch {
0%, 16.8%, 18.3%, 4… <0.5KB elided>; /* @keyframes CRTScreen-module__vxH9Iq__crt-content-glitch */
----motion-CRTScreen-module__vxH9Iq__crt-text-shadow-glitch: @keyframes CRTScreen-module__vxH9Iq__crt-text-shadow-glitch {
0%, 16.8%, 18.3… <0.4KB elided>; /* @keyframes CRTScreen-module__vxH9Iq__crt-text-shadow-glitch */
----motion-CRTScreen-module__vxH9Iq__crt-overlay-glitch: @keyframes CRTScreen-module__vxH9Iq__crt-overlay-glitch {
0%, 16.7%, 18.5%, 4… <0.5KB elided>; /* @keyframes CRTScreen-module__vxH9Iq__crt-overlay-glitch */
----motion-ComputerScreen-module__PQy4La__adjustment-toast-fade: @keyframes ComputerScreen-module__PQy4La__adjustment-toast-fade {
0% { opacit… <0.3KB elided>; /* @keyframes ComputerScreen-module__PQy4La__adjustment-toast-fade */
----motion-CarouselNavProgressIndicator-module__LbalAW__carousel-nav-progress-fill: @keyframes CarouselNavProgressIndicator-module__LbalAW__carousel-nav-progress-fill {
100% { stroke-dashoffset: 0; }
}; /* @keyframes CarouselNavProgressIndicator-module__LbalAW__carousel-nav-progress-fill */
----motion-CarouselNavProgressIndicator-module__LbalAW__carousel-nav-progress-fade: @keyframes CarouselNavProgressIndicator-module__LbalAW__carousel-nav-progress-fade {
100% { opacity: 0; }
}; /* @keyframes CarouselNavProgressIndicator-module__LbalAW__carousel-nav-progress-fade */
----motion-Float-module__lPG5_q__float-x: @keyframes Float-module__lPG5_q__float-x {
0% { transform: translateX(calc(var(--float-x) * -1)); }
50% { transform: translate(0px); }
100% { transform: translateX(var(--float-x)); }
}; /* @keyframes Float-module__lPG5_q__float-x */
----motion-Float-module__lPG5_q__float-y-rotate: @keyframes Float-module__lPG5_q__float-y-rotate {
0% { transform: translateY(… <0.3KB elided>; /* @keyframes Float-module__lPG5_q__float-y-rotate */
----motion-fade-in: @keyframes fade-in {
0% { opacity: 0; }
100% { opacity: 1; }
}; /* @keyframes fade-in */
----motion-fade-out: @keyframes fade-out {
0% { opacity: 1; }
100% { opacity: 0; }
}; /* @keyframes fade-out */
----motion-content-show: @keyframes content-show {
0% { opacity: 0; translate: -50% -48%; scale: 0.96; }
100% { opacity: 1; translate: -50% -50%; scale: 1; }
}; /* @keyframes content-show */
----motion-safety-diagram-arrow-left: @keyframes safety-diagram-arrow-left {
0% { transform: translate(0px); }
40… <0.3KB elided>; /* @keyframes safety-diagram-arrow-left */
----motion-safety-diagram-arrow-right: @keyframes safety-diagram-arrow-right {
0% { transform: translate(0px); }
4… <0.3KB elided>; /* @keyframes safety-diagram-arrow-right */
----motion-search-loading-gradient: @keyframes search-loading-gradient {
0% { background-position: 100% center; }
100% { background-position: 0px center; }
}; /* @keyframes search-loading-gradient */
----motion-slide-in-start: @keyframes slide-in-start {
0% { transform: translateX(calc(-100% * var(--dir,1))); }
100% { transform: translate(0px); }
}; /* @keyframes slide-in-start */
----motion-slide-in-end: @keyframes slide-in-end {
0% { transform: translateX(calc(100% * var(--dir,1))); }
100% { transform: translate(0px); }
}; /* @keyframes slide-in-end */
----motion-slide-out-end: @keyframes slide-out-end {
0% { transform: translate(0px); }
100% { transform: translateX(calc(100% * var(--dir,1))); }
}; /* @keyframes slide-out-end */
----motion-slide-anim: @keyframes slide-anim {
0% { transform: translate(0px); }
50% { left: 0px; }
100% { transform: translate(-100%); }
}; /* @keyframes slide-anim */
----motion-type: @keyframes type {
0% { background-size: 0px 200%; }
}; /* @keyframes type */
----motion-scale-up: @keyframes scale-up {
100% { grid-template-rows: 1fr; }
}; /* @keyframes scale-up */
----motion-show-content: @keyframes show-content {
100% { opacity: 1; }
}; /* @keyframes show-content */
----motion-shrink-out: @keyframes shrink-out {
100% { transform: translate(24px) scale(0); }
}; /* @keyframes shrink-out */
----motion-enter: @keyframes enter {
0% { opacity: var(--tw-enter-opacity,1); transform: transl… <0.3KB elided>; /* @keyframes enter */
----motion-exit: @keyframes exit {
100% { opacity: var(--tw-exit-opacity,1); transform: transl… <0.3KB elided>; /* @keyframes exit */
----motion-spin: @keyframes spin {
100% { transform: rotate(360deg); }
}; /* @keyframes spin */
----motion-pulse: @keyframes pulse {
50% { opacity: 0.5; }
}; /* @keyframes pulse */
----motion-skeleton-module__qvQnLq__loading: @keyframes skeleton-module__qvQnLq__loading {
0% { background-position: 200% 0px; }
100% { background-position: -200% 0px; }
}; /* @keyframes skeleton-module__qvQnLq__loading */
--duration-fast: 0.1s; /* 74 elements — e.g. button, button, button /* mined from computed styles */ */
--duration-base: 0.2s; /* 46 elements — e.g. button, button, button /* mined from computed styles */ */
--duration-slow: 0.25s; /* 28 elements — e.g. a, a, a /* mined from computed styles */ */
--ease-default: ease; /* 129 elements — e.g. button, button, button /* mined from computed styles */ */
```
## Appendix B: Token Source Metadata
```
tokenSource: extracted-css-vars
confidence: high
properties-found: 136 CSS custom properties
extraction-target: openai.com (live production site)
extraction-date: [TBD - note date of extraction for cache invalidation]
font-declarations:
primary: "OpenAI Sans" (proprietary, 8 variants: 400/500/600/700 × normal/italic)
secondary: "LF Serif" (editorial, variable 400–700)
math: KaTeX_* family (research pages)
served-from: cdn.openai.com/common/fonts/
css-framework: Tailwind CSS (utility-class-based) + custom CSS modules
css-processor: LightningCSS (evidenced by --lightningcss-light/dark vars)
module-naming: CSS Modules with hashed names (*.module__hash__className)
token-naming-system: --type-[level]-[property] for typography (systematic)
--space-[size] for spacing
--tw-* for Tailwind internals (DO NOT override)
reconstruction-notes:
- Colour primitives: SPARSE. openai.com uses Tailwind utility classes for colour
(bg-black, text-white, border-black/12) rather than CSS custom property colour tokens.
Colour tokens synthesised from computed styles are marked "reconstructed: moderate confidence".
- --space-xs extracted as 4.2px — rounded to 4px in implementation (floating-point artifact).
- Z-index scale not found in CSS vars — reconstructed from component role hierarchy.
- Page background inferred as #fff from --tw-ring-offset-color (confirmed by modal bg: rgb(255,255,255)).
- Button primary bg colour inferred as #000 from computed button_primary color: rgb(0,0,0) and
border: 1px solid rgba(0,0,0,0.12) — actual bg not captured in extraction (likely Tailwind bg-black).
breakpoints: 640px, 768px, 1024px, 1280px, 1536px (standard Tailwind sm/md/lg/xl/2xl)
fluid-type-bounds: min 375px (23.4375rem), max 1440px (90rem)
```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

Duolingo
MITVibrant, playful design system with bright green accents and light blue surfaces, built for engaging educational and language-learning products
00
lightboldcontent-firstmobile