56 KiB
Codex Design System Notes
These notes describe the inspected Codex desktop app, not Codium's implementation. The source snapshot was taken from the running macOS Codex app via the Chrome DevTools Protocol on app://-/index.html?hostId=local.
Observed app version:
- App title:
Codex - User agent:
Codex/26.422.30944 Chrome/146.0.7680.179 Electron/41.2.0 - Theme class:
electron-dark - Viewport sampled:
1344 x 877
Theming Pipeline (reverse-engineered)
Codex generates ~149 CSS custom properties from 5 atomic user inputs per theme, via a JS pre-pass that writes derived values onto <html>'s inline style, plus a CSS layer that resolves the rest with color-mix(in oklab, …).
Inputs (per theme — Light, Dark)
The user-editable theme object is persisted to ~/.codex/.codex-global-state.json under electron-persisted-atom-state.appearance{Light,Dark}ChromeTheme:
{
"accent": "#0169cc", // link/highlight color
"ink": "#0d0d0d", // foreground text/icon color
"surface": "#ffffff", // primary surface color
"contrast": 40, // 0–100 — single shaping knob
"fonts": { "ui": "Geist, Inter", "code": "\"Geist Mono\", ui-monospace, ..." },
"opaqueWindows": true, // light=true, dark=false (vibrancy)
"semanticColors": {
"diffAdded": "#00a240",
"diffRemoved": "#e02e2a",
"skill": "#751ed9"
}
}
appearanceTheme (top-level) selects the resolved mode: "light" | "dark" | "system". system queries nativeTheme.shouldUseDarkColors.
Resolution flow
- Resolve mode → pick the matching
appearance{Light,Dark}ChromeThemeobject. - Set
<html>class:electron-lightorelectron-dark(resolved theme)+ electron-opaqueifopaqueWindows: true(Tailwind variant trigger)
- Inline-style
<html>with the 5 base values verbatim:--codex-base-accent: <accent>; --codex-base-ink: <ink>; --codex-base-surface: <surface>; --codex-base-contrast: <contrast>; --vscode-font-family: <fonts.ui>; --vscode-editor-font-family: <fonts.code>; - JS pre-pass writes ~80 derived hex/rgba values onto the same inline
style, so most tokens are concrete colors at runtime. - CSS rules add ~70 more tokens via
color-mix(in oklab, var(--color-text-foreground) X%, transparent)etc., for the constant-alpha overlays.
The shaping function
Every contrast-driven token has the form:
value(contrast) = base + delta · t(contrast)
Where t(0) = 0, t(100) = 1, and t(c) = (c/100)^p. Fitting 9 different alpha tokens against the same shaping function yields tightly clustered exponents:
- Light theme:
p ≈ 1.50 - Dark theme:
p ≈ 1.66
Dark uses a slightly steeper curve, presumably to compensate for vibrancy compositing reducing perceptual contrast on translucent surfaces. Each token defines its own (base, delta) pair; the curve t(c) is shared across all of them.
Token derivation buckets
| Bucket | Behavior | Example tokens | Formula |
|---|---|---|---|
| A. Pass-through | Literal copy of an input | --color-text-foreground = ink, --color-background-surface = surface, --color-text-accent = accent (light only) |
value = input |
| B. Static-alpha ink overlays | Constant alpha, contrast-independent | --color-token-list-hover-background (5%), --color-token-bg-fog (2.5%), --color-token-badge-background (4.7%), --color-token-input-border (7.4%) |
rgba(ink, k) — k is per-token constant |
| C. Contrast-shaped ink overlays | rgba(ink, …) ramped by contrast |
--color-border (0.04 → 0.17), --color-text-foreground-secondary (0.60 → 0.93), --color-background-button-primary-active (0.04 → 0.44), --color-icon-secondary, --color-text-foreground-tertiary |
rgba(ink, base + delta · t(c)) |
| D. Surface ↔ ink mixes | Slight tint of surface toward ink | --color-background-surface-under (sidebar/page bg) |
mix(surface, ink, base + delta · t(c)) |
| E. Surface ↔ accent mixes | Pale accent backgrounds (selection/hover) | --color-background-accent, --color-background-accent-hover, --color-background-accent-active |
mix(surface, accent, base + delta · t(c)) |
In dark mode only, --color-text-accent is itself a contrast-shaped mix toward ink (= white):
text-accent_dark(c) = mix(accent, ink, 0.19 + 0.46 · t(c))
This auto-lightens the user-supplied accent (e.g. #339cff) toward white for legibility against #181818. Light mode leaves accent as a pass-through.
Empirical token table (light theme, ink=#0d0d0d, surface=#ffffff, accent=#0169cc)
| Token | c=0 | c=40 | c=100 | Formula |
|---|---|---|---|---|
--color-text-foreground |
#0d0d0d |
#0d0d0d |
#0d0d0d |
ink |
--color-background-surface |
#ffffff |
#ffffff |
#ffffff |
surface |
--color-background-surface-under |
#ffffff |
#f7f7f7 |
#e5e5e5 |
mix(surface, ink, 0.10·t(c)) |
--color-text-foreground-secondary |
rgba(13,13,13,0.598) |
rgba(…,0.684) |
rgba(…,0.933) |
rgba(ink, 0.60 + 0.34·t(c)) |
--color-text-foreground-tertiary |
rgba(…,0.398) |
rgba(…,0.484) |
rgba(…,0.733) |
secondary − 0.20 (constant offset) |
--color-border |
rgba(…,0.039) |
rgba(…,0.074) |
rgba(…,0.173) |
rgba(ink, 0.04 + 0.13·t(c)) |
--color-border-heavy |
rgba(…,0.059) |
rgba(…,0.111) |
rgba(…,0.260) |
rgba(ink, 0.06 + 0.20·t(c)) |
--color-background-button-primary-active |
rgba(…,0.037) |
rgba(…,0.141) |
rgba(…,0.440) |
rgba(ink, 0.04 + 0.40·t(c)) |
--color-background-accent |
#e8f2fa |
#e0ecf9 |
#c6def4 |
mix(surface, accent, 0.09 + 0.13·t(c)) |
--color-token-list-hover-background |
rgba(…,0.05) |
rgba(…,0.05) |
rgba(…,0.05) |
rgba(ink, 0.05) (constant) |
--color-token-bg-fog |
mix(oklab, ink 2.5%, transparent) |
same | same | constant alpha, CSS-only |
Effects of changing each input
| Input | Tokens affected |
|---|---|
accent |
Only the accent family (text-accent, icon-accent, bg-accent, bg-accent-hover, bg-accent-active) — ~6 tokens. Borders and ink overlays don't shift. |
ink |
Most tokens — every rgba(ink, α) overlay plus surface↔ink mixes. Setting ink: #ff0000 recolors borders, secondary text, fog, hover, etc., and tints surface-under toward red. |
surface |
background-surface directly, plus all surface mixes (surface-under, bg-accent family, dropdown, input-background). |
contrast |
Only the contrast-shaped tokens (~25). Static-alpha overlays don't move. |
opaqueWindows |
Toggles vibrancy: light = opaque body, dark = body { background: color(srgb 0.157 0.157 0.157 / 0.55) } so macOS vibrancy shows through. Adds/removes electron-opaque class. |
Design ideas
- Few inputs, many outputs. A theme is a small JSON; designers pick 3 colors + 1 slider; "Copy theme" / "Import" exchange the JSON.
- Single shaping curve per theme. Contrast isn't 25 knobs — it's one
t(c) = (c/100)^papplied to every contrast-driven token. Per-token(base, delta)is the only thing that varies. - Ink as an alpha source. Dividers/secondary text/hover are
rgba(ink, α), not literal greys. Auto-flips with theme; works with custom ink colors; keeps perceptual contrast consistent. - Accent stays on its own track. Changing accent never recolors general tokens — only the explicit accent family. Custom themes can't go chaotic.
- Constant-alpha for ambient, contrast-shaped for interactive. Fog/list-hover are quiet across the slider; borders/button states get punchier as the user dials contrast up — exactly what the slider name implies.
- Two-tier compute. Heavy precomputed colors live in JS-injected inline style; light constant-alpha overlays stay in CSS via
color-mix(in oklab, …)so they hot-recompute when ink changes. - Theme-aware accent legibility. Dark mode auto-mixes accent toward white as contrast increases, so users can pick any accent hue without worrying about contrast failures on
#181818. - Semantic colors are literal.
diffAdded/diffRemoved/skillbypass the derivation pipeline — they're direct inputs because diff red and OK green need to mean what they mean.
Token Counts
When the renderer settles, getComputedStyle(:root) reports ~149 reachable
custom properties. They split as:
| Layer | Count | Source |
|---|---|---|
| Inline-style chrome tokens | 65 | Computed in JS by deriveTokens() and written to <html>.style |
--color-token-* design-system aliases |
96 | CSS-only, var() references to inline tokens |
| Other (fonts, Tailwind base palette, VS Code editor tokens) | ~700 | Unrelated to chrome theme; bundled VS Code editor / framework defaults |
The 65 + 96 ≈ 161 surface that chrome components actually consume.
Built-in Themes Catalog
Codex's app.asar bundles ~70 theme files. They split into two kinds based
on what they export, and both kinds end up as selectable themes in the UI.
Kind 1 — first-class chrome themes (12 files)
These export a fully-formed chromeTheme object next to their VS Code-style
colors object. They were authored explicitly for Codex's chrome — designer
chose every input by hand:
// e.g. linear-dark.js
export const chromeTheme = {
accent: '#5e6ad2',
contrast: 60,
fonts: { ui: 'Inter', code: null },
ink: '#e3e4e6',
opaqueWindows: true,
semanticColors: {
diffAdded: '#69c967',
diffRemoved: '#ff7e78',
skill: '#c2a1ff',
},
surface: '#0f0f11',
}
Bundled themes of this kind: Linear Dark/Light, Vercel Dark/Light, Raycast
Dark/Light, Notion Dark/Light, Sentry Dark, Lobster Dark, Matrix Dark, Proof
Light (12 entries). Plus the codex default in appearance{Light,Dark}ChromeTheme
on the persistent global state file (counts as 2 more).
The chrome theme object can be a partial — Codex merges it on top of the
codex default for the same variant. Lobster, Matrix, Notion, Proof, and
Sentry only override fonts and/or opaqueWindows and inherit everything
chromatic from the codex default.
Kind 2 — VS Code code themes (~55 files)
The rest are pure VS Code colorTheme JSONs (Dracula, Tokyo Night,
Catppuccin, One Dark Pro, Gruvbox, Monokai, Nord, Solarized, Material
Theme, etc.). They define editor / activity-bar / terminal colors but
don't carry a chromeTheme object:
// e.g. dracula.js
{
"editor.background": "#282A36",
"editor.foreground": "#F8F8F2",
"focusBorder": "#bd93f9",
"activityBar.activeBorder": "#bd93f9",
"terminal.ansiBlue": "#bd93f9",
/* …50+ more editor.* / activityBar.* / terminal.* fields… */
}
When the user picks one of these from the code-theme picker, Codex synthesizes a chrome theme on the fly from the VS Code colors. The mapping is:
| Chrome input | Source field (first non-null wins) | Notes |
|---|---|---|
surface |
editor.background |
The dominant background color of the editor pane. |
ink |
editor.foreground |
The primary text color. |
accent |
focusBorder → activityBar.activeBorder → editorCursor.foreground → terminal.ansiBlue → button.background |
Pick the first that's non-transparent and ≠ surface. Themes whose focusBorder is #XXXXXX00 (alpha 0) fall through to the next candidate. |
contrast |
(codex default for variant) | 45 light / 60 dark. |
opaqueWindows |
(codex default for variant) | false for both — code themes don't author this. |
fonts |
(codex default) | Geist UI + Geist Mono code. |
semanticColors.diffAdded |
(codex default for variant) | #00a240 light / #40c977 dark. |
semanticColors.diffRemoved |
(codex default) | #ba2623 / #fa423e. |
semanticColors.skill |
(codex default) | #924ff7 / #ad7bf9. |
The synthesized chrome theme then runs through the same deriveTokens()
pipeline as a first-class chrome theme — there's no separate code path.
Catalog (Codium ships)
| Kind | Source | Count |
|---|---|---|
| Codex defaults | appearance{Light,Dark}ChromeTheme global state |
2 |
| First-class chrome themes | bundled chromeTheme exports |
12 |
| Code-theme-derived | synthesized from editor.* / focusBorder |
55 |
| Total | 69 |
Limitations of synthesis
A code theme can't communicate the things that don't live in its color table:
- Contrast — the
editor.*JSON has no equivalent of the contrast knob, so all 55 derived themes start at the codex default contrast (45 / 60). The user can still adjust it in Settings → Appearance. opaqueWindows— same: no signal for whether the window should be opaque vs translucent. Defaults tofalse(vibrancy on).- Fonts — code themes don't ship UI/code font preferences; we use the
codex defaults. (Codex's first-class chrome themes like Linear sometimes
do override fonts —
Interfor Linear,Satoshifor Lobster.) - Diff / skill colors — these are intentionally meaningful (red-must-mean-error, green-must-mean-added) and code themes don't always agree on them, so we always use the codex default semantic colors.
If you want a more faithful version of any synthesized theme, override the
relevant inputs by hand — the chrome theme is just a JSON, persisted to
localStorage by applyPreset().
JS Derivation Functions
The runtime functions that compute the 65 inline tokens. Pseudocode pulled
from the running app, normalized to the names used in
packages/codium/sources/theme/derive.ts.
Inputs
type ChromeTheme = {
accent: string // hex
ink: string // hex (foreground)
surface: string // hex (primary surface)
contrast: number // 0..100
opaqueWindows: boolean
fonts: { ui: string | null, code: string | null }
semanticColors: { diffAdded: string, diffRemoved: string, skill: string }
}
type ThemeMode = 'light' | 'dark'
Color helpers
type RGB = [number, number, number]
const parseHex = (h: string): RGB => … // '#0d0d0d' → [13, 13, 13]
const rgbToHex = (rgb: RGB): string => … // [13, 13, 13] → '#0d0d0d'
const rgba = (rgb: RGB, a: number): string // → 'rgba(13, 13, 13, 0.123)'
const rgb = (rgb: RGB): string // → 'rgb(13, 13, 13)'
// Standard sRGB linear interpolation (the running app does NOT use oklab here
// for the JS-side mixes; it only uses oklab for the CSS-side `color-mix`).
const mix = (a: RGB, b: RGB, t: number): RGB => [
a[0] * (1 - t) + b[0] * t,
a[1] * (1 - t) + b[1] * t,
a[2] * (1 - t) + b[2] * t,
]
const BLACK: RGB = [0, 0, 0]
const WHITE: RGB = [255, 255, 255]
Shaping function
The contrast slider is a single non-linear knob applied to every contrast-driven token. Each mode has its own exponent.
const shape = (contrast: number, mode: ThemeMode): number => {
const c = clamp01(contrast / 100)
const p = mode === 'dark' ? 1.66 : 1.50
return Math.pow(c, p)
}
const apply = (curve: { base: number, delta: number }, t: number) =>
curve.base + curve.delta * t
Per-token coefficient table
Empirically fit from snapshots of the running Codex app.
const COEFFS: Record<ThemeMode, ModeCoeffs> = {
light: {
p: 1.5,
fgSecondary: { base: 0.598, delta: 0.335 },
fgTertiary: { base: 0.398, delta: 0.335 },
border: { base: 0.04, delta: 0.133 },
borderHeavy: { base: 0.06, delta: 0.20 },
borderLight: { base: 0.03, delta: 0.067 },
iconPrimary: 'passthrough', // light: ink as-is
btnPrimaryActive: { base: 0.04, delta: 0.40 },
btnPrimaryHover: { base: 0.02, delta: 0.20 },
btnPrimaryInactive: { base: 0.151, delta: 0.306 },
btnSecondary: { base: 0.037, delta: 0.041 },
btnSecondaryActive: { base: 0.027, delta: 0.041 },
btnSecondaryHover: { base: 0.035, delta: 0.061 },
btnSecondaryInactive: { base: 0.007, delta: 0.041 },
btnTertiary: { base: 0, delta: 0 }, // always transparent
btnTertiaryActive: { base: 0.140, delta: 0.184 },
btnTertiaryHover: { base: 0.073, delta: 0.082 },
surfUnder: { base: 0.0, delta: 0.10 }, // mix(surface, ink)
bgAccent: { base: 0.09, delta: 0.13 }, // mix(surface, accent)
bgAccentHover: { base: 0.10, delta: 0.15 },
bgAccentActive: { base: 0.11, delta: 0.16 },
accentForegroundMix: { base: 0, delta: 0 }, // light: passthrough
accentPurple: '#751ed9',
decorationAdded: '#00a240',
decorationDeleted: '#e02e2a',
editorOverlayAlpha: 0.15,
},
dark: {
p: 1.66,
fgSecondary: { base: 0.58, delta: 0.303 },
fgTertiary: { base: 0.329, delta: 0.394 },
border: { base: 0.032, delta: 0.121 },
borderHeavy: { base: 0.078, delta: 0.182 },
borderLight: { base: 0.016, delta: 0.061 },
iconPrimary: { base: 0.79, delta: 0.27 },
btnPrimaryActive: { base: 0.035, delta: 0.152 },
btnPrimaryHover: { base: 0.019, delta: 0.091 },
btnPrimaryInactive: { base: 0.017, delta: 0.036 },
btnSecondary: { base: 0.037, delta: 0.036 },
btnSecondaryActive: { base: 0.078, delta: 0.099 },
btnSecondaryHover: { base: 0.051, delta: 0.063 },
btnSecondaryInactive: { base: 0.015, delta: 0.054 },
btnTertiary: { base: 0.017, delta: 0.027 },
btnTertiaryActive: { base: 0.058, delta: 0.099 },
btnTertiaryHover: { base: 0.041, delta: 0.063 },
surfUnder: { base: 0.05, delta: 0.16 }, // mix(surface, BLACK)
bgAccent: { base: 0.17, delta: 0.19 }, // mix(BLACK, accent)
bgAccentHover: { base: 0.18, delta: 0.21 },
bgAccentActive: { base: 0.20, delta: 0.23 },
accentForegroundMix: { base: 0.19, delta: 0.46 },
accentPurple: '#ad7bf9',
decorationAdded: '#40c977',
decorationDeleted: '#fa423e',
editorOverlayAlpha: 0.23,
},
}
// Dark-mode-only "above surface" mixes (no headroom in light because
// surface is already at maximum brightness).
const DARK_SURFACE_MIX = {
controlOpaque: { base: 0.049, delta: 0.099 }, // mix(surface, ink, …)
editorOpaque: { base: 0.058, delta: 0.027 }, // mix(surface, ink, …)
elevatedPrimary: { base: 0.053, delta: 0.180 }, // mix(surface, ink, …)
buttonPrimaryFill: { base: 0.46, delta: 0 }, // mix(surface, BLACK, …)
textBtnSecondary: { base: 0.20, delta: 0.09 }, // mix(surface, ink, …)
}
Main derivation
function deriveTokens(theme: ChromeTheme, mode: ThemeMode): Record<string, string> {
const ink = parseHex(theme.ink)
const surface = parseHex(theme.surface)
const accent = parseHex(theme.accent)
const t = shape(theme.contrast, mode)
const C = COEFFS[mode]
const inkA = (alpha: number) => rgba(ink, alpha)
const inkOverlay = (curve: Curve) => inkA(apply(curve, t))
// — accent foreground (legibility)
const accentFg = mode === 'dark'
? rgbToHex(mix(accent, WHITE, apply(C.accentForegroundMix, t)))
: theme.accent
// — surface "below" (page / sidebar bg) — quieter than surface in both modes
const surfaceUnder = rgbToHex(mix(
surface,
mode === 'dark' ? BLACK : ink,
apply(C.surfUnder, t),
))
// — surface "above" (panel / elevated / control / editor)
const above = mode === 'dark'
? {
control: mix(surface, ink, apply(DARK_SURFACE_MIX.controlOpaque, t)),
editor: mix(surface, ink, apply(DARK_SURFACE_MIX.editorOpaque, t)),
elevated: mix(surface, ink, apply(DARK_SURFACE_MIX.elevatedPrimary, t)),
}
: { control: surface, editor: surface, elevated: surface }
// — bg-accent family (selection / hover accent)
const bgAccentBase = mode === 'dark' ? BLACK : surface
const bgAccentRgb = mix(bgAccentBase, accent, apply(C.bgAccent, t))
const bgAccentHoverRgb = mix(bgAccentBase, accent, apply(C.bgAccentHover, t))
const bgAccentActiveRgb = mix(bgAccentBase, accent, apply(C.bgAccentActive, t))
// — primary button fill
const buttonPrimaryFill = mode === 'dark'
? mix(surface, BLACK, apply(DARK_SURFACE_MIX.buttonPrimaryFill, t))
: ink
return {
// base inputs (echoed)
'--codex-base-accent': theme.accent,
'--codex-base-ink': theme.ink,
'--codex-base-surface': theme.surface,
'--codex-base-contrast': String(theme.contrast),
// fonts
'--vscode-font-family': appendFontFallback(theme.fonts.ui ?? 'Geist, Inter', 'sans'),
'--vscode-editor-font-family': appendFontFallback(theme.fonts.code ?? '"Geist Mono", ui-monospace, "SFMono-Regular"', 'mono'),
// typography (fixed; not theme-dependent)
'--text-xs': '11px', '--text-sm': '12px', '--text-base': '13px', '--text-lg': '16px',
'--text-heading-sm': '18px', '--text-heading-md': '20px', '--text-heading-lg': '24px',
'--text-xl': '28px', '--text-2xl': '36px', '--text-3xl': '48px', '--text-4xl': '72px',
'--vscode-font-size': '13px', '--vscode-editor-font-size': '12px',
// pass-throughs / accent
'--color-text-foreground': theme.ink,
'--color-icon-primary': C.iconPrimary === 'passthrough' ? theme.ink : inkA(apply(C.iconPrimary, t)),
'--color-text-accent': accentFg,
'--color-icon-accent': accentFg,
'--color-background-surface': theme.surface,
'--color-accent-blue': theme.accent,
'--color-accent-purple': C.accentPurple,
// static-alpha ink overlays
'--color-hover': inkA(0.05),
'--color-token-list-hover-background': inkA(0.05),
'--color-token-toolbar-hover-background': inkA(0.05),
'--color-token-badge-background': inkA(0.047),
'--color-token-input-border': inkA(0.074),
'--color-token-bg-fog': inkA(0.025),
// contrast-shaped ink overlays
'--color-text-foreground-secondary': inkOverlay(C.fgSecondary),
'--color-text-foreground-tertiary': inkOverlay(C.fgTertiary),
'--color-icon-secondary': inkOverlay(C.fgSecondary),
'--color-icon-tertiary': inkOverlay(C.fgTertiary),
'--color-border': inkOverlay(C.border),
'--color-border-heavy': inkOverlay(C.borderHeavy),
'--color-border-light': inkOverlay(C.borderLight),
'--color-active': inkA(0.04 + 0.06 * t),
'--color-button-bg': inkA(0.04 + 0.06 * t),
'--color-button-hover': inkA(0.04 + 0.04 * t),
// simple-scrim: BLACK in light, INK in dark
'--color-simple-scrim': mode === 'light'
? rgba(BLACK, 0.06 + 0.13 * t)
: inkA(0.06 + 0.13 * t),
// button background family
'--color-background-button-primary': mode === 'dark' ? rgb(buttonPrimaryFill) : rgbToHex(buttonPrimaryFill),
'--color-background-button-primary-active': inkOverlay(C.btnPrimaryActive),
'--color-background-button-primary-hover': inkOverlay(C.btnPrimaryHover),
'--color-background-button-primary-inactive': inkOverlay(C.btnPrimaryInactive),
'--color-background-button-secondary': inkOverlay(C.btnSecondary),
'--color-background-button-secondary-active': inkOverlay(C.btnSecondaryActive),
'--color-background-button-secondary-hover': inkOverlay(C.btnSecondaryHover),
'--color-background-button-secondary-inactive': inkOverlay(C.btnSecondaryInactive),
'--color-background-button-tertiary': inkOverlay(C.btnTertiary),
'--color-background-button-tertiary-active': inkOverlay(C.btnTertiaryActive),
'--color-background-button-tertiary-hover': inkOverlay(C.btnTertiaryHover),
// text on buttons
'--color-text-button-primary': mode === 'dark' ? rgb(buttonPrimaryFill) : theme.surface,
'--color-text-button-secondary': mode === 'dark'
? rgbToHex(mix(surface, ink, apply(DARK_SURFACE_MIX.textBtnSecondary, t)))
: theme.ink,
'--color-text-button-tertiary': inkOverlay(C.fgTertiary),
// surface family
'--color-background-surface-under': surfaceUnder,
'--color-background-panel': mode === 'dark' ? rgbToHex(mix(surface, ink, 0.07)) : theme.surface,
'--color-background-control': rgba(above.control, 0.96),
'--color-background-control-opaque': rgb(above.control),
'--color-background-editor-opaque': rgb(above.editor),
'--color-background-elevated': rgba(above.elevated, 0.96),
'--color-background-elevated-primary': rgba(above.elevated, 0.96),
'--color-background-elevated-primary-opaque': rgb(above.elevated),
'--color-background-elevated-secondary': mode === 'dark' ? inkOverlay(C.btnPrimaryInactive) : rgba(surface, 0.96),
'--color-background-elevated-secondary-opaque': rgbToHex(above.editor),
// accent backgrounds
'--color-background-accent': rgbToHex(bgAccentRgb),
'--color-background-accent-hover': rgbToHex(bgAccentHoverRgb),
'--color-background-accent-active': rgbToHex(bgAccentActiveRgb),
// focus ring
'--color-border-focus': mode === 'dark'
? rgba(parseHex(accentFg), 0.76)
: theme.accent,
// fixed brand: diff / editor overlays
'--color-decoration-added': C.decorationAdded,
'--color-decoration-deleted': C.decorationDeleted,
'--color-editor-added': rgba(parseHex(C.decorationAdded), C.editorOverlayAlpha),
'--color-editor-deleted': rgba(parseHex(C.decorationDeleted), C.editorOverlayAlpha),
// legacy semantic-color shortcuts
'--color-accent-green': theme.semanticColors.diffAdded,
'--color-accent-red': theme.semanticColors.diffRemoved,
}
}
Apply to the document
function applyTheme(theme: ChromeTheme, mode: ThemeMode) {
const root = document.documentElement
for (const [k, v] of Object.entries(deriveTokens(theme, mode))) {
root.style.setProperty(k, v)
}
root.classList.toggle('electron-light', mode === 'light')
root.classList.toggle('electron-dark', mode === 'dark')
root.classList.toggle('electron-opaque', !!theme.opaqueWindows)
root.style.colorScheme = mode
}
CSS-Side Aliases
Codex defines ~96 --color-token-* design-system aliases in CSS that
reference the inline-injected tokens. Components consume these via Tailwind
utilities (e.g. bg-token-input-background, text-token-foreground).
They live in CSS so they hot-recompute when an inline token changes — no JS
re-derive needed. Most are var() references; a few are color-mix()
derivations; status/error/charts colors are fixed brand colors per mode.
:root {
/* text aliases */
--color-token-foreground: var(--color-text-foreground);
--color-token-text-primary: var(--color-text-foreground);
--color-token-text-secondary: var(--color-text-foreground-secondary);
--color-token-text-tertiary: var(--color-text-foreground-tertiary);
--color-token-description-foreground: var(--color-text-foreground-tertiary);
--color-token-disabled-foreground: var(--color-text-foreground-tertiary);
/* icon */
--color-token-icon-foreground: var(--color-icon-primary);
/* surface / background */
--color-token-main-surface-primary: var(--color-background-surface);
--color-token-bg-primary: var(--color-background-surface-under);
--color-token-bg-secondary: color-mix(in srgb, var(--color-background-surface-under) 92%, transparent);
--color-token-bg-tertiary: color-mix(in srgb, var(--color-background-surface-under) 85%, transparent);
--color-token-side-bar-background: var(--color-background-surface-under);
--color-token-input-background: var(--color-background-control);
--color-token-dropdown-background: var(--color-background-elevated-primary-opaque);
--color-token-menu-background: var(--color-background-elevated-primary);
--color-token-checkbox-background: var(--color-background-elevated-primary);
--color-token-bg-fog: color-mix(in oklab, var(--color-text-foreground) 2.5%, transparent);
/* input / checkbox / menu */
--color-token-input-foreground: var(--color-text-foreground);
--color-token-input-placeholder-foreground: var(--color-text-foreground-tertiary);
--color-token-input-border: var(--color-border);
--color-token-checkbox-foreground: var(--color-text-foreground);
--color-token-checkbox-border: var(--color-border);
--color-token-menu-border: var(--color-border);
--color-token-menubar-selection-foreground: var(--color-text-foreground);
--color-token-menubar-selection-background: var(--color-token-bg-fog);
--color-token-radio-active-foreground: var(--color-text-foreground);
/* link */
--color-token-link: var(--color-text-accent);
--color-token-text-link-foreground: var(--color-text-accent);
--color-token-text-link-active-foreground: var(--color-text-accent);
/* border */
--color-token-border: var(--color-border);
--color-token-border-default: var(--color-border);
--color-token-border-heavy: var(--color-border-heavy);
--color-token-border-light: var(--color-border-light);
--color-token-focus-border: var(--color-border-focus);
--color-token-list-focus-outline: var(--color-border-focus);
/* button */
--color-token-button-background: var(--color-background-button-primary);
--color-token-button-foreground: var(--color-background-button-primary);
--color-token-button-border: var(--color-border);
--color-token-button-secondary-hover-background: var(--color-background-button-secondary-hover);
/* list / hover */
--color-token-list-hover-background: var(--color-background-button-tertiary-hover);
--color-token-toolbar-hover-background: var(--color-background-button-tertiary-hover);
--color-token-list-active-selection-background: var(--color-background-button-secondary);
--color-token-list-active-selection-foreground: var(--color-text-foreground);
--color-token-list-active-selection-icon-foreground: var(--color-icon-primary);
/* badge */
--color-token-badge-background: var(--color-background-button-secondary);
--color-token-badge-foreground: var(--color-text-foreground-secondary);
/* code block */
--color-token-text-code-block-background: var(--color-border);
/* scrollbar */
--color-token-scrollbar-slider-background: var(--color-border);
--color-token-scrollbar-slider-hover-background: var(--color-border-heavy);
--color-token-scrollbar-slider-active-background: var(--color-border-heavy);
/* editor */
--color-token-editor-foreground: var(--color-text-foreground);
--color-token-editor-background: var(--color-background-editor-opaque);
--color-token-editor-error-foreground: #ff6764;
--color-token-editor-warning-foreground: #ff8549;
/* terminal */
--color-token-terminal-foreground: var(--color-text-foreground);
--color-token-terminal-background: var(--color-background-surface);
--color-token-terminal-border: var(--color-border);
--color-token-terminal-ansi-black: var(--color-text-foreground-tertiary);
--color-token-terminal-ansi-bright-black: var(--color-text-foreground-secondary);
--color-token-terminal-ansi-blue: var(--color-accent-blue);
--color-token-terminal-ansi-bright-blue: var(--color-accent-blue);
--color-token-terminal-ansi-cyan: var(--color-accent-blue);
--color-token-terminal-ansi-bright-cyan: var(--color-accent-blue);
--color-token-terminal-ansi-green: var(--color-decoration-added);
--color-token-terminal-ansi-bright-green: var(--color-decoration-added);
--color-token-terminal-ansi-magenta: var(--color-accent-purple);
--color-token-terminal-ansi-bright-magenta: var(--color-accent-purple);
--color-token-terminal-ansi-red: #ff6764;
--color-token-terminal-ansi-bright-red: #ff6764;
--color-token-terminal-ansi-white: var(--color-text-foreground);
--color-token-terminal-ansi-bright-white: var(--color-text-foreground);
--color-token-terminal-ansi-yellow: #ffd240;
--color-token-terminal-ansi-bright-yellow: #ffd240;
/* charts */
--color-token-charts-blue: var(--color-accent-blue);
--color-token-charts-green: var(--color-decoration-added);
--color-token-charts-orange: #fb6a22;
--color-token-charts-purple: var(--color-accent-purple);
--color-token-charts-red: #ff6764;
--color-token-charts-yellow: #ffd240;
/* git decoration */
--color-token-git-decoration-added-resource-foreground: var(--color-decoration-added);
--color-token-git-decoration-deleted-resource-foreground: var(--color-decoration-deleted);
/* diff surface (mix toward foreground for contrast) */
--color-token-diff-surface: color-mix(in srgb, var(--color-background-surface) 94%, var(--color-text-foreground));
/* error / warning / status */
--color-token-error-foreground: #ff6764;
--color-icon-warning: #ff8549;
--color-background-status-error: #4d100e;
--color-background-status-warning: #4a2206;
--color-background-status-success: color-mix(in oklab, #04b84c 16%, transparent);
}
/* Light-mode tweaks for status/error tokens whose dark default doesn't read on a light surface */
html.electron-light {
--color-token-editor-error-foreground: #b6161c;
--color-token-editor-warning-foreground: #b55a16;
--color-token-error-foreground: #b6161c;
--color-token-terminal-ansi-red: #b6161c;
--color-token-terminal-ansi-bright-red: #b6161c;
--color-token-charts-red: #b6161c;
--color-token-charts-orange: #b04300;
--color-token-charts-yellow: #ad7e00;
--color-icon-warning: #b55a16;
--color-background-status-error: color-mix(in oklab, #b6161c 12%, transparent);
--color-background-status-warning: color-mix(in oklab, #b55a16 12%, transparent);
--color-background-status-success: color-mix(in oklab, #04b84c 12%, transparent);
}
Visual Direction
Codex is a dense desktop tool UI. It uses a transparent macOS sidebar, a dark rounded main surface, compact 28-30px controls, low-contrast token borders, and almost no decorative gradients. The design language is practical and quiet:
- Surfaces are flat or slightly elevated.
- Borders are usually translucent white at very low opacity.
- Hover states use subtle translucent fills.
- Gradients are functional, mostly for resize affordances and fade masks.
- Text is small: 12px for sidebar/control labels, 13px for body and menus.
Token Vocabulary
Codex uses Tailwind-like utility classes backed by semantic tokens. The important observed class families are:
| Family | Examples | Meaning |
|---|---|---|
| Text | text-token-foreground, text-token-description-foreground, text-token-muted-foreground, text-token-text-link-foreground |
Primary, secondary, muted, and link text |
| Background | bg-token-bg-fog, bg-token-input-background/90, bg-token-side-bar-background, bg-token-dropdown-background |
Quiet control, composer, sidebar, dropdown surfaces |
| Border | border-token-border, border-token-border/70, bg-token-border-default |
Default borders and dividers |
| Hover | hover:bg-token-list-hover-background, enabled:hover:bg-token-list-hover-background |
Row/control hover fill |
| Radius | rounded-lg, rounded-md, rounded-full, rounded-3xl, rounded-s-2xl |
Rows, icon buttons, pills, composer, main surface |
| Sizing | h-toolbar, h-token-button-composer, h-token-button-composer-sm, size-token-button-composer |
Header and compact button heights |
| Spacing | px-row-x, py-row-y, px-toolbar, px-panel |
System row/header/panel spacing |
Computed dark colors from the inspected UI:
| Use | Value |
|---|---|
| Body translucent window background | color(srgb 0.156863 0.156863 0.156863 / 0.55) |
| Main surface | rgb(24, 24, 24) |
| Primary foreground | rgb(255, 255, 255) |
| Tertiary/muted foreground | rgba(255, 255, 255, 0.498) |
| Link/accent text | rgb(131, 195, 255) |
| Button fog background | rgba(255, 255, 255, 0.03) |
| Standard border on split buttons | rgba(255, 255, 255, 0.082) |
| Main surface outline ring | rgba(255, 255, 255, 0.157) |
| Main surface shadow | rgba(0, 0, 0, 0.08) 0px 2px 4px -1px |
Typography
Observed text sizes:
| Context | Size | Line height | Weight |
|---|---|---|---|
| Body/default browser base | 16px |
24px |
400 |
| App body text / editor | 13px |
19.5px |
400 |
| Sidebar rows | 12px |
17.1429px |
400 |
| Composer footer labels | 12px |
18px |
400 |
| Header split buttons | 13px |
18px |
400 |
| Home headline | class heading-xl |
not measured in this pass | 400 via font-normal |
Use truncate, min-w-0, and whitespace-nowrap heavily. Codex prefers one-line labels with ellipsis over wrapping in navigation and controls.
App Shell
Window Background
The body is translucent:
background-color: color(srgb 0.156863 0.156863 0.156863 / 0.55);
The sidebar sits on this transparent window background rather than a solid panel.
Sidebar
Measured sidebar:
- Width:
318.93px(319pxtarget). - Height: full viewport.
- Top padding:
46px, matching toolbar height. - Background: transparent.
- Border: no visible solid sidebar border in the sampled aside itself.
- Nav content begins at
y = 46px.
Sidebar nav rows:
- Row width:
302.93px. - Row height:
29.5px. - Row padding:
5px 8px. - Row gap:
8px. - Row radius:
12.5px. - Text size:
12px. - Line height:
17.1429px. - Hover class:
hover:bg-token-list-hover-background. - Focus:
focus-visible:outline-token-border,outline-2,outline-offset-2.
Section headers such as Projects are quieter:
- Height:
23.5px. - Padding:
2px 4px 2px 0. - Radius:
10px. - Text size:
13px. - Color:
rgba(255, 255, 255, 0.498).
Main Surface
The main work surface is the strongest framed shape:
- Left offset: starts after
319pxsidebar. - Background:
rgb(24, 24, 24). - Radius:
12.5px 0 0 12.5px(rounded-s-2xl). - Shadow stack:
box-shadow:
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(255, 255, 255, 0.157) 0px 0px 0px 0.5px,
rgba(0, 0, 0, 0.08) 0px 2px 4px -1px;
The important detail is the 0.5px translucent white ring. This is the primary border for the main surface.
Borders
Borders are tokenized and low contrast. They are used more as surface separators than visible outlines.
Default Border
Split buttons and framed header controls use:
border-color: rgba(255, 255, 255, 0.082);
Observed on header split buttons:
border-token-borderborderborder-r-0orborder-l-0for attached split-button halves.
Transparent Border Reservation
Many buttons use border-transparent while still carrying the border class. This reserves layout space so hover/open states do not shift the control.
Observed examples:
- Sidebar/top icon buttons.
- Composer icon buttons.
- Tertiary toolbar buttons.
Divider Border
Codex uses border-token-border/70 and divide-token-border/70 for quieter separators in menus/lists. The intent is visible separation only at close range.
Focus Border
Focus rings are outlines, not heavier borders:
focus-visible:outlinefocus-visible:outline-2focus-visible:outline-offset-2focus-visible:outline-token-border
Composer send focus uses focus-visible:outline-token-button-background.
Gradients And Fades
Codex uses very few gradients. They are utility/affordance gradients, not decorative backgrounds.
Sidebar Resize Handle
The clearest actual gradient is the sidebar resize handle:
sidebar-resize-handle-line
bg-gradient-to-b
from-transparent
via-token-foreground/25
to-transparent
Measured handle:
- Width:
1px. - Height:
923pxin the sampled viewport. - Horizontal margin:
0 5.5px. - Initial opacity:
0. - Visible on group hover/active via
group-hover:opacity-100andgroup-active:opacity-100.
This gradient is a vertical line that fades at both ends.
Text Fade Masks
Long sidebar/folder rows use mask gradients when hover/focus actions appear:
group-focus-within:[mask-image:linear-gradient(to_right,transparent_0,transparent_21px,black_26px)]
group-hover:[mask-image:linear-gradient(to_right,transparent_0,transparent_21px,black_26px)]
This is not a visible background gradient. It is a text/content mask that prevents overlap with row actions.
Scroll Fade Mask
The sidebar contains vertical-scroll-fade-mask, indicating a vertical fade at scroll boundaries. Treat this as a functional overflow affordance.
Not Used As Decoration
No large hero gradients, orb gradients, or decorative color washes were observed. The main app depends on surface color, blur, borders, and shadows instead.
Toolbar And Header Controls
Header controls are compact, often split-button pairs.
Observed split button values:
- Height:
28px. - Text size:
13px. - Line height:
18px. - Background:
rgba(255, 255, 255, 0.03)(bg-token-bg-fog). - Border:
rgba(255, 255, 255, 0.082). - Left half radius:
12.5px 0 0 12.5px. - Right half radius:
0 12.5px 12.5px 0. - Left half padding:
0 4px 0 8px. - Right half padding:
0 6px 0 2px. - Icon-only tertiary buttons:
36px x 28px, padding0 8px, radius12.5px, transparent border/background.
Attached split buttons remove the shared border:
- Left half:
border-r-0. - Right half:
border-l-0.
Composer
The Codex composer is a centered, elevated input with a pill-like rounded shape.
Observed classes:
rounded-3xl
bg-token-input-background/90
ring
ring-black/10
backdrop-blur-lg
electron:shadow-[0_4px_16px_0_rgba(0,0,0,0.05)]
electron:dark:bg-token-dropdown-background
Measured composer/editor area:
- Composer content width:
728px. - Editor visual width:
704px. - Editor height in empty state:
40px. - Editor max height:
25dvh. - ProseMirror min height:
2rem. - Text size:
13px. - Line height:
19.5px.
Composer footer:
- Width:
728px. - Height:
28px. - Display: grid.
- Columns:
minmax(0, auto) auto minmax(0, 1fr). - Gap:
5px. - Padding:
0 8px. - Margin bottom:
8px.
Footer controls:
- Main button height:
28px. - Small button height:
28pxin measured state. - Label font:
12px. - Label line height:
18px. - Pill radius:
9999px. - Text color for inactive controls:
rgba(255, 255, 255, 0.498). - Link/accent label color:
rgb(131, 195, 255). - Icon-only composer buttons:
28px x 28px. - Send button:
28px x 28px,9999pxradius, white background,0.5opacity when disabled.
Buttons
General button pattern:
- Keep a
bordereven when transparent. - Use
cursor-interaction. - Use
user-select-none. - Use
whitespace-nowrap. - Disabled state:
disabled:cursor-not-allowedand eitherdisabled:opacity-40ordisabled:opacity-50. - Hover/open state:
enabled:hover:bg-token-list-hover-backgroundanddata-[state=open]:bg-token-list-hover-background.
Control sizes:
| Control | Size |
|---|---|
| Sidebar row | 303px x 30px sampled |
| Header split button | 28px high |
| Header icon-only tertiary | 36px x 28px |
| Sidebar section icon button | 24px x 24px |
| Composer icon button | 28px x 28px |
| Composer send button | 28px x 28px |
Radius Scale
Observed radii:
| Radius | Use |
|---|---|
10px |
Section toggle, compact icon button |
12.5px |
Sidebar rows, header split buttons, main surface side radius |
9999px |
Composer footer pills and circular send button |
rounded-3xl |
Composer shell |
rounded-b-2xl |
Lower rounded panel in prompt/options area |
Codex often uses rounded-lg utility classes, but computed radius in the sampled environment is 12.5px.
Spacing Scale
Observed spacing:
| Context | Value |
|---|---|
| Sidebar row padding | 5px 8px |
| Sidebar row gap | 8px |
| Section toggle padding | 2px 4px 2px 0 |
| Header split left padding | 0 4px 0 8px |
| Header split right padding | 0 6px 0 2px |
| Header icon padding | 0 8px |
| Composer footer padding | 0 8px |
| Composer footer gap | 5px |
| Composer footer bottom margin | 8px |
| Composer small pill padding | 0 6px |
| Composer standard pill padding | 0 8px |
Shadows And Elevation
Codex uses minimal elevation:
- Main surface:
0.5pxwhite ring plus subtle shadow. - Composer:
0 4px 16px rgba(0,0,0,0.05)in Electron. - Dropdown/composer background uses blur and opaque token surfaces rather than large shadows.
The main visual separation comes from border rings and surface contrast, not heavy drop shadows.
Plugins Catalog
The Plugins screen keeps the same shell but uses content-centered catalog sections.
Observed structure:
- Main content still begins at
x = 319pxand uses the samemain-surface. - Header height remains
46px. - Content column is centered with
max-widtharound736px. - Catalog sections use transparent backgrounds and
gap-4. - Section examples:
- Featured/plugin rows:
x = 463,width = 736. - Vertical section gaps are large enough that categories read as separate bands without card wrappers.
- Featured/plugin rows:
Header tabs/actions on the Plugins page:
- Header text includes
Plugins,Skills,Manage,Create. - These are compact header controls rather than large tabs.
- They inherit the same 28px control language as toolbar buttons.
Plugin detail rows/cards:
- Plugin settings pages use bordered list cards rather than heavy panels.
- Example Computer Use plugin row:
- Rect:
672px x 62px. - Radius:
12.5px. - Border:
1px rgba(255, 255, 255, 0.082). - Class pattern:
border-token-border/40 flex flex-col gap-2.5 rounded-2xl border p-2.5 transition.
- Rect:
- Action button such as
Try in Chat:- Size:
28px x 28px. - Radius:
10px. - Transparent background and transparent reserved border.
- Size:
Automations
The Automations screen introduces rounded prompt/template cards.
Page layout:
- Header action:
New automation, white filled button. - Content header column:
x = 447,width = 768, withpx-panel. - Template section width:
728px. - Template sections use
flex flex-col gap-4.
Automation template cards:
- Card width:
356px. - Card height: usually
97px; shorter cards can be78px. - Two-column grid inside the
728pxcontent column. - Background:
oklab(0.297161 0.0000135154 0.00000594556 / 0.672549). - Border:
1px oklab(0.999994 0.0000455678 0.0000200868 / 0.0411765). - Radius:
30px(rounded-4xl). - Padding class:
px-3 py-3. - Text size:
13px, line-height19.5px. - Hover classes strengthen the card:
hover:border-token-borderandhover:bg-token-input-background.
This is the largest radius observed in normal content cards. It is used for selectable prompt templates, not ordinary panels.
Account Popover
Clicking the sidebar Settings row first opens an account popover near the lower-left sidebar.
Measured popover:
- Rect:
x = 8,y = 663,width = 282,height = 176. - It contains account identity, account type, settings, rate limits, and log out.
- The popover itself is visually transparent in computed background, relying on internal menu structure and separators.
- Separators:
- Width:
256px. - Height:
1px. - Background:
rgba(255, 255, 255, 0.082). - Class:
bg-token-menu-border.
- Width:
This popover confirms menu separators use the same low-contrast border color as split-button borders.
Settings Shell
The actual Settings surface is a separate app state with a fixed left settings nav and a right content panel.
Settings nav:
- Nav width:
300px. - Nav starts below toolbar at
y = 46px. - Nav padding class:
px-row-x. - Row width:
284px. - Row height:
30px. - Row padding uses the same
px-row-x py-row-ytoken pair as main sidebar rows. - Row radius:
12.5px. - Row text:
12px, line-height17.1429px. - Active background:
rgba(255, 255, 255, 0.08). - Items observed:
- General
- Appearance
- Configuration
- Personalization
- MCP servers
- Git
- Environments
- Worktrees
- Browser use
- Computer use
- Archived chats
- Usage
Settings content:
- Content starts at
x = 300px. - Top header strip:
height = 46px. - Scroll panel:
x = 300,y = 46,width = 1044,height = 831. - Content class:
flex-1 overflow-y-auto p-panel. - Inner content column:
- Usually
x = 486. - Width:
672px. - Header block height:
67px. - Section group gap:
var(--padding-panel).
- Usually
Settings headings are plain text blocks, not cards. Most settings sections are transparent rows and sections; controls provide the visual weight.
Settings: General
General contains work-mode cards, switches, dropdown buttons, and segmented controls.
Work mode cards:
- Two side-by-side cards.
- Each card:
330px x 284px. - Radius:
12.5px. - Selected background:
rgba(255, 255, 255, 0.08). - Unselected background:
rgb(24, 24, 24). - Unselected border:
1px rgba(255, 255, 255, 0.082). - Class pattern:
cursor-interaction flex min-h-[284px] w-full min-w-0 flex-col items-center.
Switches:
- Switch button size:
32px x 20px. - Text around switches uses
12px. - Switch buttons report transparent background at the outer button level; the visual track/thumb is inside child elements.
- Focus class:
focus-visible:ring-2. - Aria labels carry the setting names, for example:
Default permissions are always shownShow Auto-review in the composerShow Full access in the composerPrevent sleep while runningEnable ambient suggestionsEnable permission notificationsEnable question notifications
Dropdown/select-like buttons:
- Width: often
240px. - Height:
28px. - Radius:
12.5px. - Background:
oklab(0.999994 0.0000455678 0.0000200868 / 0.05). - Border is reserved but transparent:
1px rgba(0, 0, 0, 0). - Text size:
13px, line-height18px. - Examples:
VS Code,Auto Detect,Standard,Only when unfocused.
Small action buttons:
Setbuttons are38px x 28px.- Same radius/background/font as dropdown buttons.
Segmented pills:
- Height:
24px. - Radius:
9999px. - Text size:
12px, line-height18px. - Selected background:
oklab(0.999994 0.0000455678 0.0000200868 / 0.05). - Unselected background: transparent.
- Examples:
Queue/Steer,Inline/Detached.
Settings: Appearance
Appearance adds theme segmented controls, theme import/copy actions, code theme select, swatches, and sliders.
Theme segmented control:
- Buttons:
Light,Dark,System. - Height:
24px. - Radius:
9999px. - Text size:
12px, line-height18px. - Selected
Darkbackground:oklab(0.999994 0.0000455678 0.0000200868 / 0.05). - Unselected buttons are transparent.
Theme actions:
Import:58px x 28px.Copy theme:91px x 28px.- Radius:
12.5px. - Transparent background and transparent reserved border.
- Text size:
13px, line-height18px.
Code theme selector:
- Example:
Aa Codex. - Size:
240px x 28px. - Background:
oklab(0.999994 0.0000455678 0.0000200868 / 0.05). - Radius:
12.5px. - Box shadow includes a tiny
0 1px 2px -1px rgba(0,0,0,0.08).
Color swatches:
- Small swatch buttons measured at
14px x 14px. - Background color carries the swatch value, for example accent green/blue.
- Swatches are much smaller than toolbar buttons and are embedded in rows.
Settings: Plugin Panels
Browser Use and Computer Use settings use the same plugin panel pattern:
- Header block: transparent,
672pxwide. - Section groups are transparent and stacked with
var(--padding-panel). - Plugin summary cards use:
border-token-border/40rounded-2xlborderp-2.5gap-2.5transition
- Try/action icon button:
28px x 28px.- Radius:
10px. - Transparent background and transparent reserved border.
This is a quieter, smaller card style than Automation templates.
Settings: Archived Chats
Archived chats is a list surface:
- Content column:
672px. - List section uses
flex flex-col gap-2. - Rows are transparent sections rather than cards.
- Row actions use standard 28px buttons.
Unarchivebutton:- Size:
79px x 28px. - Background:
oklab(0.999994 0.0000455678 0.0000200868 / 0.05). - Radius:
12.5px. - Text size:
13px, line-height18px. - Transparent reserved border.
- Size:
Settings: Usage
Usage uses transparent sections with progress/limit content and standard actions.
Observed sections:
General usage limitsGPT-5.3-Codex-Spark usage limitsCredit
Section layout:
- Section width:
672px. - Usage limit sections measured
672px x 173px. - Credit section measured
672px x 189px. - Sections are transparent; progress values and row text provide structure.
Actions:
Purchase:74px x 28px.Settings:68px x 28px.- Background:
oklab(0.999994 0.0000455678 0.0000200868 / 0.05). - Radius:
12.5px. - Text size:
13px, line-height18px.
Links:
- Links can use muted secondary text rather than blue when they are ancillary.
- Example
Doc:text-token-text-secondary hover:text-token-text-primary.
Practical Replication Rules
- Use semantic tokens/classes first: foreground, description foreground, border, list hover, fog, input background.
- Keep controls compact:
28pxfor toolbar/composer controls,30pxfor nav rows. - Reserve border space with transparent borders to avoid hover layout shift.
- Use
12.5pxradii for rows and split controls; use full pill radius for composer footer controls. - Use gradients only for resize handles, overflow fades, or masks.
- Use
0.5pxtranslucent rings for major surfaces instead of strong 1px borders. - Keep text at
12pxor13pxfor operational UI. - Use
min-w-0,truncate, and mask fades anywhere row actions can overlap labels. - Use
30pxradius only for large selectable prompt/template cards. - In settings, keep the content column at about
672pxand let controls carry the visual framing.