contracts / tokens.md

Tokens

Values from the stylesheets in app/. Later files override earlier ones. Import order: globals.css, sidebar.css, reading.css, shape.css, theme.css, settings.css, columns.css, share.css, tree.css, block-editor.css, projects.css.

globals.css imports Tailwind and defines no @theme tokens. Tailwind's default theme is not part of this contract.

A number from specs/PLAN.md that the CSS does not use is recorded on the shipped value, as not what shipped.

Color

Dark is :root in globals.css, plus --selection in theme.css. Light is [data-theme="light"] in theme.css. Unset theme is dark (components/theme-store.ts is the behavior; the CSS itself does not set the attribute).

TokenDarkLight
--bg#111111#f6f6f4
--bg-sidebar#1b1b1b#efefec
--bg-chrome#1b1b1b#efefec
--bg-outline#191919#f3f3f0
--bg-source#191919#f3f3f0
--bg-raised#242424#ffffff
--bg-hover#2c2c2c#e6e6e2
--bg-sunken#1f1f1f#ecece8
--text#ececec#1a1a1a
--text-dim#a3a3a3#3a3a3a
--text-faint#8c8c8c#6a6a66
--accent#7aa2f7#3b6fd6
--amber#e6c07b#8a5a10
--danger#f0a8a8#a33b3b
--selection#2a3a55#d5e2fb

color-scheme is dark on :root and light on [data-theme="light"].

Paper design direction (ADR-0037)

TokenDarkLight
--primary-top / --primary-bottom#0e3ed2 / #011fadsame
--field-top / --field-bottomrgb(72 72 72 / 0.6) / rgb(72 72 72 / 0.4)#e2e2de / #ecece8
--outline-cardrgb(44 44 44 / 0.2)var(--bg-sunken)
--row-activergb(255 255 255 / 0.08)rgb(0 0 0 / 0.06)
--kbd-text#b0b0b0var(--text-dim)
--font-displayvar(--font-seb-display, var(--font-geist-sans)), sans-serifsame
--font-buttonvar(--font-seb-var, var(--font-geist-sans)), sans-serifsame

--font-seb-display and --font-seb-var are not defined until the Seb Sans files are loaded in app/layout.tsx, so both families resolve to Geist today. The Share button is the gradient with a #fff label. The title row is 56px. Tables sit in an 18px rounded wrapper. The outline column is --bg with the card inside.

Dark ::selection in globals.css is hardcoded #2a3a55, the same value as --selection. Light ::selection uses var(--selection).

Project tiles

Defined on .project-tile. --tile-hue is not given a value in CSS. See Referenced names.

TokenDarkLight
--tile-bgoklch(0.36 0.07 var(--tile-hue))oklch(0.9 0.05 var(--tile-hue))
--tile-fgoklch(0.9 0.06 var(--tile-hue))oklch(0.38 0.1 var(--tile-hue))

These colors are not custom properties.

UseDarkLight
Markdown link, missing link, wiki chip#9db7ff#2451b5
Markdown link hover#c5d6ff#1a3d8f
Wiki chip underlinergba(157, 183, 255, 0.4)(no light override)
Mark background / text#3a3116 / #f3e6c0#f4e3b0 / #3a3014
Callout note background / title#1a2438 / #9db7ff#e7eefc / #2451b5
Callout tip background / title#173026 / #7dcea0#e5f4eb / #1d6b45
Callout important background / title#261f38 / #c4b5fd#efe8fb / #5b3d9e
Callout warning background / title#2e2818 / #e6c07b#f8efd8 / #8a5a10
Callout caution background / title#321c1c / var(--danger)#f8e4e4 / #9a3030
Scrollbar thumb#333#c8c8c2
Share button label#fff#fff
Destructive share button#b64242(no light override)
Overlay scrimrgba(0, 0, 0, 0.55)(same)
Mobile files scrimrgba(0, 0, 0, 0.45)(same)

Shadows

Shadows are in the CSS. C2 removed hairline borders in favor of solid fills. These shadows still shipped.

UseDarkLight
Switcher0 12px 32px rgba(0, 0, 0, 0.35), 0 0 0 1px rgba(255, 255, 255, 0.04)0 12px 32px rgba(0, 0, 0, 0.12), 0 0 0 1px rgba(0, 0, 0, 0.06)
Menu, block menu, suggest0 12px 32px rgba(0, 0, 0, 0.35)(no light override)
Bubble0 10px 28px rgba(0, 0, 0, 0.35)(no light override)
Toast0 12px 32px rgba(0, 0, 0, 0.3)(no light override)
Toast button hoverrgba(127, 127, 127, 0.18)(same)
Light project card—0 0 0 1px rgba(0, 0, 0, 0.06)
Light project card hover—0 0 0 1px rgba(0, 0, 0, 0.1), 0 6px 18px rgba(0, 0, 0, 0.06)

Inset rings use var(--accent), var(--bg-hover), or var(--bg) and are not separate colors. Drop-target and divider rings are 1px or 1.5px.

Type

TokenValue
--font-sansvar(--font-geist-sans), sans-serif
--font-monovar(--font-geist-mono), ui-monospace, monospace

UI text on body is 13px, line-height 1.45. Mono is used for paths, kbd, code, and the share-link field.

Reading line

Shipped, from reading.css, and it wins over globals.css:

PropertyShippedEarlier rule, not what you see
Font size17px on .md-column; .md inheritsglobals.css sets .md to 16.5px
Line height1.7 on .mdglobals.css sets 1.65
MeasureColumn capped at 760px; text 696px widereading.css sets width: min(100%, calc(66ch + 64px)). 66ch at 17px is 744px, so that width is 808px and max-width: 760px from globals.css binds. Padding is 32px on each side inside the border box, so the text is 696px wide. PRD 720px is not what shipped. A line near 66 characters is not the shipped width

Below the 640px container breakpoint the column width becomes 100%, and the 760px cap does not bind.

C4 asked for a line near 66 characters at 17px and 1.7 line height. The type size matches the CSS. The width that ships is the 760px cap.

Reading headings

From reading.css, on .md. Root font size is the browser default (the CSS does not set it); rem below assumes 16px.

LevelSize
h1clamp(1.6rem, 1.15rem + 1.5vw, 1.875rem)
h2clamp(1.15rem, 1rem + 0.5vw, 1.25rem)
h31.05rem
h4No font-size in the CSS. Line-height 1.25 and letter-spacing -0.02em come from globals.css

Other reading sizes that shipped: footnotes 14px; table 14.5px; table header 12.5px; code block 13.5px; embed body 15px; embed h1 and h2 16px; history preview 15px; palette input 16px (the 15px in globals.css loses).

Other sizes in the CSS

11px labels and kbd, 12px meta and crumbs, 12.5px outline entries, 14px dialog titles and settings section titles, 15px palette rows and project card titles, 18px settings pane title, 26px missing-page title, 28px launcher title (24px at the 640px breakpoint). Letter-spacing on titles is -0.01em or -0.03em. Uppercase labels use 0.06em.

Radius

C5 shipped for the roles it names. shape.css is the rule that wins for those selectors.

RoleShippedWhere
Controls12pxSearch, tree link, text button, icon button, palette row, files button, edit toggle, share button, settings entry, several inputs
Outline rows10px.outline button
Inline code and kbd8px.md :not(pre) > code, .md kbd, shortcut kbd, copy button
Mark6px.md mark
Blocks18pxCallout, embed, blockquote, details, image, code fence, Mermaid, chart, table scroll
Dialogs20pxPalette, shortcut help, settings, share dialog, sheet

The PRD radii, 6px controls and 8px panels, are not what shipped for those roles.

Other radii that are in the CSS, and are not the C5 roles:

ValueWhere
3pxCurrent-page bar in the file tree
4pxSelected inline math, inline math field. globals.css also sets 4px on kbd, outline buttons, inline code, and the copy button; shape.css overrides those
6pxBlock-handle buttons, bubble buttons, source-editor controls, menu glyphs, project tile. globals.css 6px on search, tree links, text buttons, and palette rows loses to 12px
7pxProject tile in the switcher
8pxProject tile at medium size, folder chips, disk-banner button, block atom, empty block, suggest and block-menu rows, embed picker. globals.css 8px on blockquote, details, image, callout, embed, and code loses to 18px
9pxTheme-switch option, MCP copy button, tree action, menu row
10pxProject switch, switcher rows, settings nav, count pill, toast button, bubble, source editor, backlink chip
12pxTree rows, tree folders, tree inputs, history rows, project fields, launcher buttons, MCP sample, new-project icon, large project tile
14pxSwitcher panel, menu, toast
16pxProject card. Block menu becomes 16px 16px 0 0 as a bottom sheet
999pxFrontmatter chips

Spacing

Spacing is not a custom-property scale. These are the layout values in the CSS.

UseValue
Top bar and brand row44px, plus env(safe-area-inset-top)
Status line28px, plus env(safe-area-inset-bottom)
Coarse-pointer targets44px on icon buttons, search, text buttons, edit toggle, share, tree rows, menu rows, settings entry, project switch, launcher buttons
Reading padding40px 32px, bottom 96px plus the safe area
Reading padding at the 640px container28px 20px, bottom 72px plus the safe area
Reading padding at the 420px containerinline 16px
Focus outline2px solid var(--accent), offset 2px (0 on project fields)

globals.css padding of 36px 32px on .md-column loses to the reading padding above. The 24px 18px padding at the 860px breakpoint also loses to reading.css for the column.

Column widths

ColumnShipped
Files248px (grid-template-columns on .shell and .shell.has-outline)
Outline220px (same shell rule, and the old .stage.preview rule in globals.css)
Pageminmax(0, 1fr)

columns.css wins for the shell: files, page, and outline are three full-height columns. .stage.preview is forced back to one column so the outline is not a second 220px inside the page.

Also in the CSS, not the two named columns:

UseValue
Mobile files drawermin(280px, 88vw)
Source editor textmax-width: 860px on .stage.source .cm-content
Settingsmin(760px, calc(100vw - 32px)) by min(520px, calc(100dvh - 48px)); nav 180px
Share dialogmin(480px, calc(100vw - 32px))
Search palette and shortcut helpmin(520px, calc(100vw - 32px))
History sheetmin(980px, 100%), list 240px
Trash and move sheetsmin(560px, 100%)
Switcher264px
Menu224px
Block menu260px
Launcher contentmin(1120px, 100%)
Project gridrepeat(auto-fill, minmax(280px, 1fr))

The plan's 248px files column and 220px outline column match the CSS.

Breakpoints

QueryWhat changes
max-width: 860pxShell stacks. Outline hides. Files drawer and scrim. Settings gear shows when the sidebar is collapsed
max-width: 720pxHistory becomes one column; the version list scrolls sideways
max-width: 640pxLauncher stacks. Settings becomes one column and nearly full screen. Share dialog width calc(100vw - 16px)
max-width: 420pxStatus line gap 8px and inline padding 12px
@container (max-width: 840px)Block handle moves in; the add button hides
@container (max-width: 640px)Reading column goes to 100% width. Source font size 15px. Block handle buttons shrink to 20px
@container (max-width: 420px)Reading inline padding 16px
(hover: none) and (max-width: 900px), also max-width: 640pxBlock menu becomes a bottom sheet

The plan's 860px shell, 640px column, and 420px column match the CSS.

The plan's 900px split breakpoint is not what shipped. 900px appears only in the block-menu query above. Preview and Markdown source are separate modes, not columns that stack at 900px.

(pointer: coarse) and (hover: none) are in the CSS and are not width breakpoints. (prefers-reduced-motion: reduce) is under Motion.

Motion

UseValue
Chevrontransform 120ms ease
Project cardbackground 120ms ease, transform 120ms ease
Reduced motionCard transition none. scroll-behavior: auto on everything

The PRD range of 120–160ms is not what shipped. 120ms is in the CSS. 160ms is not.

Referenced names

These names appear in the CSS and have no value in the stylesheets. They are recorded here so they are not invented.

NameRoleWhere the value comes from
--font-geist-sansTypenext/font on <html> in app/layout.tsx
--font-geist-monoTypenext/font on <html> in app/layout.tsx
--shiki-darkColorShiki sets it on highlighted code. The CSS only reads it
--shiki-lightColorShiki sets it. The CSS only reads it, under [data-theme="light"]
--tile-hueColorcomponents/project-tile.tsx sets 250, 160, 30, 290, 200, 340, 110, or 60
--depthSpacingTree rows set an integer. The CSS uses calc(var(--depth, 0) * 14px). The fallback 0 is in the CSS

Values outside the categories

These are in the CSS and are not a color, a type size, a radius, a space, a column width, a breakpoint, or a motion duration.

KindValues
z-index1 outline heading, 5 block handle, 30 mobile scrim, 40 files drawer, 50 overlay and bubble, 60 settings, share, block menu, and suggest, 70 switcher and menu, 80 toast
opacity0, 0.45 (disabled buttons), 0.8 (callout switch hover), 1
filterbrightness(1.08) share button hover, brightness(1.12) launcher secondary hover, brightness(0.96) that hover in light