Skip to content

Latest commit

 

History

History
969 lines (843 loc) · 55.2 KB

File metadata and controls

969 lines (843 loc) · 55.2 KB
version 3.0.0
schemaVersion 1.0.0
name Editorial Design System
status stable
description A portable, cross-domain design system: editorial and literary, ink on warm paper, manuscript red for interaction, a real serif worth reading, and flat, type-led composition. This manifest is normative; prose below explains it.
tokenContract
root layers referenceSyntax modeResolution portability
tokens
primitive
semantic
component
{tokens.<layer>.<group>.<name>}
Resolve semantic light/dark values before platform export.
Adapters emit resolved values; platform syntax is not copied verbatim.
tokens
primitive semantic component
color space radius size number glyph duration breakpoint font
transparent ink-950 ink-800 paper-50 paper-100 white field-light night-950 night-900 night-850 paper-dark red-700 red-800 red-300 red-dark-400 red-dark-300 brass-600 brass-dark-400 green-700 green-dark-400 orange-700 orange-dark-400 error-600 error-800 error-dark-400 teal-700 teal-dark-400 purple-600 purple-dark-300 gold-700 gold-dark-300
transparent
#17171a
#3a3a3d
#faf8f4
#f2ede4
#ffffff
#fffdfa
#121211
#1c1b19
#211f1c
#f3efe6
#a3341f
#872a19
#d9b9b0
#d8664c
#e0724f
#b0812f
#cda24e
#2f7d5a
#5aa07f
#a85f1e
#d08a3a
#c23b2b
#9e2a1d
#e0604e
#3f6f78
#5a97a2
#7a6fa6
#a896d8
#8a6c00
#e0bc45
0 half 1 2 3 4 5 6 8 10 12 16
0px
2px
4px
8px
12px
16px
20px
24px
32px
40px
48px
64px
none xs sm md lg full
0px
2px
3px
4px
6px
9999px
border-width focus-width focus-offset target-min target-preferred control-height icon-compact icon-default icon-stroke status-dot sidebar page-inset content-max reading-measure
1px
2px
1px
24px
44px
44px
12px
14px
1.5px
7px
216px
32px
1100px
66ch
drop-cap-lines
3
brand loader
fast base slow toast toast-error caret
120ms
180ms
300ms
6000ms
10000ms
1.05s
sm md lg xl
640px
880px
1128px
1440px
serif sans mono
Fraunces, 'Iowan Old Style', Charter, Georgia, 'Times New Roman', serif
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, system-ui, sans-serif
'JetBrains Mono', 'Cascadia Code', 'SF Mono', ui-monospace, Menlo, Consolas, monospace
color typography radius space elevation motion layout z-index
ink ink-soft paper paper-sunken surface surface-raised field accent accent-hover accent-disabled on-accent brass text-primary text-secondary text-tertiary text-quaternary hairline border border-strong focus-ring stroke-hair success warning error error-strong info on-error selection scrim fill-primary fill-secondary fill-tertiary fill-quaternary fill-quinary fill-info fill-success fill-warning fill-error cat-1 cat-2 cat-3 cat-4 cat-5 cat-6
light dark
{tokens.primitive.color.ink-950}
{tokens.primitive.color.paper-dark}
light dark
{tokens.primitive.color.ink-800}
#d0cbc2
light dark
{tokens.primitive.color.paper-50}
{tokens.primitive.color.night-950}
light dark
{tokens.primitive.color.paper-100}
{tokens.primitive.color.night-900}
light dark
{tokens.primitive.color.white}
{tokens.primitive.color.night-900}
light dark
{tokens.primitive.color.white}
{tokens.primitive.color.night-850}
light dark
{tokens.primitive.color.field-light}
{tokens.primitive.color.night-850}
light dark
{tokens.primitive.color.red-700}
{tokens.primitive.color.red-dark-400}
light dark
{tokens.primitive.color.red-800}
{tokens.primitive.color.red-dark-300}
light dark
{tokens.primitive.color.red-300}
#6b4037
light dark
{tokens.primitive.color.paper-50}
{tokens.primitive.color.night-950}
light dark
{tokens.primitive.color.brass-600}
{tokens.primitive.color.brass-dark-400}
light dark
#252527
{tokens.primitive.color.paper-dark}
light dark
#575657
#b7b2aa
light dark
#6b6a6a
#8a8780
light dark
#adabaa
#5f5b56
light dark
#e8e6e3
#302e2b
light dark
#958680
#796d68
light dark
#82736d
#8a7c75
light dark
{tokens.primitive.color.red-700}
{tokens.primitive.color.red-dark-400}
light dark
#f3f1ed
#2a2825
light dark
{tokens.primitive.color.green-700}
{tokens.primitive.color.green-dark-400}
light dark
{tokens.primitive.color.orange-700}
{tokens.primitive.color.orange-dark-400}
light dark
{tokens.primitive.color.error-600}
{tokens.primitive.color.error-dark-400}
light dark
{tokens.primitive.color.error-800}
#f07562
light dark
{tokens.primitive.color.teal-700}
{tokens.primitive.color.teal-dark-400}
light dark
{tokens.primitive.color.paper-50}
{tokens.primitive.color.night-950}
light dark
#e7cdc5
#452c24
light dark
rgba(0,0,0,0.32)
rgba(0,0,0,0.50)
light dark
#eeddd6
#362620
light dark
#f1e4df
#2f221e
light dark
#f4eae5
#29201d
light dark
#f6eee9
#251f1c
light dark
#f7f2ee
#221d1b
light dark
#e4e8e5
#232a29
light dark
#e2e9e2
#232b25
light dark
#f0e6da
#32281d
light dark
#f3e1dc
#34231f
light dark
{tokens.primitive.color.red-700}
{tokens.primitive.color.red-dark-400}
light dark
{tokens.primitive.color.teal-700}
{tokens.primitive.color.teal-dark-400}
light dark
{tokens.primitive.color.brass-600}
{tokens.primitive.color.brass-dark-400}
light dark
{tokens.primitive.color.green-700}
{tokens.primitive.color.green-dark-400}
light dark
{tokens.primitive.color.purple-600}
{tokens.primitive.color.purple-dark-300}
light dark
{tokens.primitive.color.gold-700}
{tokens.primitive.color.gold-dark-300}
display h1 h2 h3 h4 body-read drop-cap ui-sans ui-sans-compact ui-mono label-caps kbd print-body chapter-number folio footnote
fontFamily fontSize fontWeight opticalSize lineHeight letterSpacing
{tokens.primitive.font.serif}
4rem
340
144
0.94
-0.01em
fontFamily fontSize fontWeight opticalSize lineHeight
{tokens.primitive.font.serif}
2.5rem
380
72
1.05
fontFamily fontSize fontWeight opticalSize lineHeight
{tokens.primitive.font.serif}
1.75rem
420
48
1.12
fontFamily fontSize fontWeight opticalSize lineHeight
{tokens.primitive.font.serif}
1.25rem
540
24
1.25
fontFamily fontSize fontWeight lineHeight
{tokens.primitive.font.sans}
1rem
650
1.35
fontFamily fontSize fontWeight opticalSize lineHeight measure
{tokens.primitive.font.serif}
1.0625rem
400
18
1.62
{tokens.primitive.size.reading-measure}
fontFamily fontWeight opticalSize lineHeight
{tokens.primitive.font.serif}
420
144
0.82
fontFamily fontSize lineHeight
{tokens.primitive.font.sans}
0.875rem
1.5
fontFamily fontSize lineHeight
{tokens.primitive.font.sans}
0.8125rem
1.5
fontFamily fontSize lineHeight
{tokens.primitive.font.mono}
0.8125rem
1.5
fontFamily fontSize fontWeight letterSpacing textTransform
{tokens.primitive.font.sans}
0.75rem
600
0.12em
uppercase
fontFamily fontSize fontWeight lineHeight
{tokens.primitive.font.sans}
0.75rem
600
1
fontFamily fontSize lineHeight measure
{tokens.primitive.font.serif}
11pt
1.5
60–72 characters
fontFamily fontWeight opticalSize
{tokens.primitive.font.serif}
300
144
fontFamily fontSize letterSpacing
{tokens.primitive.font.sans}
9pt
0.08em
fontFamily fontSize lineHeight
{tokens.primitive.font.serif}
8.5pt
1.35
none xs sm md lg full
{tokens.primitive.radius.none}
{tokens.primitive.radius.xs}
{tokens.primitive.radius.sm}
{tokens.primitive.radius.md}
{tokens.primitive.radius.lg}
{tokens.primitive.radius.full}
xxs xs sm md lg xl section
{tokens.primitive.space.half}
{tokens.primitive.space.1}
{tokens.primitive.space.2}
{tokens.primitive.space.4}
{tokens.primitive.space.6}
{tokens.primitive.space.12}
{tokens.primitive.space.16}
flat shadow-card shadow-page
none
0 1px 2px rgba(0,0,0,0.05), 0 0 0 1px rgba(23,23,26,0.06)
0 .125rem .25rem -.125rem rgba(0,0,0,.08), 0 .5rem .75rem -.375rem rgba(0,0,0,.07), 0 1.25rem 1.75rem -.875rem rgba(0,0,0,.06)
fast base slow ease-out ease-standard caret
{tokens.primitive.duration.fast}
{tokens.primitive.duration.base}
{tokens.primitive.duration.slow}
cubic-bezier(0.16, 1, 0.3, 1)
cubic-bezier(0.2, 0, 0, 1)
{tokens.primitive.duration.caret} step-end
content-max reading-measure sidebar page-inset breakpoint-sm breakpoint-md breakpoint-lg breakpoint-xl
{tokens.primitive.size.content-max}
{tokens.primitive.size.reading-measure}
{tokens.primitive.size.sidebar}
{tokens.primitive.size.page-inset}
{tokens.primitive.breakpoint.sm}
{tokens.primitive.breakpoint.md}
{tokens.primitive.breakpoint.lg}
{tokens.primitive.breakpoint.xl}
base sticky dropdown overlay scrim modal tooltip
0
100
200
300
400
401
500
button-base button-primary button-secondary button-ghost button-text button-destructive input select-native select-rich segmented-control switch search-field card overlay popover tooltip toast banner-info banner-success banner-warning banner-error badge status-dot chip progress skeleton brand-mark favicon loader-caret reading-view
minHeight radius focusRing focusOffset loadingAccessibleName
{tokens.primitive.size.control-height}
{tokens.semantic.radius.sm}
{tokens.primitive.size.focus-width} {tokens.semantic.color.focus-ring}
{tokens.primitive.size.focus-offset}
preserve-action-and-append-progress
default hover active focus disabled loading
background text radius minHeight paddingBlock paddingInline
{tokens.semantic.color.accent}
{tokens.semantic.color.on-accent}
{tokens.semantic.radius.sm}
{tokens.primitive.size.control-height}
{tokens.primitive.space.2}
{tokens.primitive.space.5}
background
{tokens.semantic.color.accent-hover}
background
{tokens.semantic.color.accent-hover}
ring offset
{tokens.primitive.size.focus-width} {tokens.semantic.color.focus-ring}
{tokens.primitive.size.focus-offset}
background text
{tokens.semantic.color.accent-disabled}
{tokens.semantic.color.text-quaternary}
visual accessibleName
{tokens.component.loader-caret.glyph}
preserve-action-and-append-progress
default hover active text base
{tokens.semantic.color.fill-quaternary}
{tokens.semantic.color.fill-tertiary}
{tokens.semantic.color.fill-primary}
{tokens.semantic.color.accent}
{tokens.component.button-base}
default hover active text base
{tokens.primitive.color.transparent}
{tokens.semantic.color.fill-tertiary}
{tokens.semantic.color.fill-primary}
{tokens.semantic.color.accent}
{tokens.component.button-base}
default text decoration base
{tokens.primitive.color.transparent}
{tokens.semantic.color.accent}
underline-on-hover
{tokens.component.button-base}
default hover active text base
{tokens.semantic.color.error}
{tokens.semantic.color.error-strong}
{tokens.semantic.color.error-strong}
{tokens.semantic.color.on-error}
{tokens.component.button-base}
default hover focus error disabled read-only
background border radius minHeight paddingInline
{tokens.semantic.color.field}
{tokens.primitive.size.border-width} {tokens.semantic.color.border}
{tokens.semantic.radius.sm}
{tokens.primitive.size.control-height}
{tokens.primitive.space.3}
border
{tokens.primitive.size.border-width} {tokens.semantic.color.border-strong}
border ring offset
{tokens.primitive.size.border-width} {tokens.semantic.color.focus-ring}
{tokens.primitive.size.focus-width} {tokens.semantic.color.focus-ring}
{tokens.primitive.size.focus-offset}
border helper
{tokens.primitive.size.border-width} {tokens.semantic.color.error}
{tokens.semantic.color.error}
background text
{tokens.semantic.color.paper-sunken}
{tokens.semantic.color.text-quaternary}
background text border
{tokens.semantic.color.paper-sunken}
{tokens.semantic.color.text-primary}
{tokens.primitive.size.border-width} {tokens.semantic.color.hairline}
visual semantics
{tokens.component.input}
native-select
trigger menu semantics
{tokens.component.input}
{tokens.component.popover}
aria-combobox-listbox
background hover selectedBackground selectedText selectedMarker focus disabledText radius
{tokens.semantic.color.fill-quaternary}
{tokens.semantic.color.fill-tertiary}
{tokens.semantic.color.fill-primary}
{tokens.semantic.color.accent}
{tokens.semantic.color.accent}
{tokens.semantic.color.focus-ring}
{tokens.semantic.color.text-quaternary}
{tokens.semantic.radius.sm}
true false focus disabled radius
{tokens.semantic.color.accent}
{tokens.semantic.color.border}
{tokens.semantic.color.focus-ring}
{tokens.semantic.color.text-quaternary}
{tokens.semantic.radius.full}
background underline focusUnderline typography
{tokens.primitive.color.transparent}
{tokens.semantic.color.hairline}
{tokens.semantic.color.focus-ring}
{tokens.semantic.typography.ui-sans}
background edge radius
{tokens.semantic.color.surface}
{tokens.semantic.color.hairline}
{tokens.semantic.radius.none}
background shadow edge radius scrim
{tokens.semantic.color.surface-raised}
{tokens.semantic.elevation.shadow-page}
{tokens.semantic.color.stroke-hair}
{tokens.semantic.radius.lg}
{tokens.semantic.color.scrim}
background shadow edge radius
{tokens.semantic.color.surface-raised}
{tokens.semantic.elevation.shadow-page}
{tokens.semantic.color.stroke-hair}
{tokens.semantic.radius.lg}
background text shadow edge radius typography
{tokens.semantic.color.ink}
{tokens.semantic.color.paper}
{tokens.semantic.elevation.shadow-page}
{tokens.semantic.color.stroke-hair}
{tokens.semantic.radius.sm}
{tokens.semantic.typography.ui-sans}
background shadow edge radius timeout errorTimeout
{tokens.semantic.color.surface-raised}
{tokens.semantic.elevation.shadow-page}
{tokens.semantic.color.stroke-hair}
{tokens.semantic.radius.lg}
{tokens.primitive.duration.toast}
{tokens.primitive.duration.toast-error}
background icon text
{tokens.semantic.color.fill-info}
{tokens.semantic.color.info}
{tokens.semantic.color.text-primary}
background icon text
{tokens.semantic.color.fill-success}
{tokens.semantic.color.success}
{tokens.semantic.color.text-primary}
background icon text
{tokens.semantic.color.fill-warning}
{tokens.semantic.color.warning}
{tokens.semantic.color.text-primary}
background icon text
{tokens.semantic.color.fill-error}
{tokens.semantic.color.error}
{tokens.semantic.color.text-primary}
background text radius typography
{tokens.semantic.color.fill-quaternary}
{tokens.semantic.color.accent}
{tokens.semantic.radius.full}
{tokens.semantic.typography.label-caps}
size radius
{tokens.primitive.size.status-dot}
{tokens.semantic.radius.full}
background text radius typography
{tokens.semantic.color.fill-quinary}
{tokens.semantic.color.text-secondary}
{tokens.semantic.radius.xs}
{tokens.semantic.typography.ui-mono}
track fill indeterminateGlyph
{tokens.semantic.color.fill-tertiary}
{tokens.semantic.color.accent}
{tokens.primitive.glyph.loader}
background edge
{tokens.semantic.color.fill-tertiary}
{tokens.semantic.color.hairline}
glyph tile glyphColor radius
{tokens.primitive.glyph.brand}
{tokens.primitive.color.ink-950}
{tokens.primitive.color.paper-50}
{tokens.semantic.radius.full}
glyph tile glyphColor radius
{tokens.primitive.glyph.brand}
{tokens.primitive.color.ink-950}
{tokens.primitive.color.paper-50}
{tokens.semantic.radius.xs}
glyph color animation
{tokens.primitive.glyph.loader}
{tokens.semantic.color.accent}
{tokens.semantic.motion.caret}
typography measure dropCapTypography dropCapLines
{tokens.semantic.typography.body-read}
{tokens.semantic.layout.reading-measure}
{tokens.semantic.typography.drop-cap}
{tokens.primitive.number.drop-cap-lines}

Overview

This is an editorial, type-led design system - ink on warm paper, one interaction accent (the editor's manuscript red), a real serif worth reading, flat and borderless. It is written to be reused across domains: web/desktop software, terminal apps (TUI/CLI), print & long-form (books, PDF/EPUB), and demos. Where the system needs a concrete example, Writing Agent is the reference implementation (Appendix A) - but the core below is portable.

Voice. A tireless, exacting editor: precise without being cold, literary without being precious. Interfaces should feel like a well-set page - the kind of thing worth reading - not a dashboard bolted onto a screen.

Normative boundary. The YAML manifest is the normative token source. Sections from Overview through Adopting this system define normative design and behavior. Appendices are non-normative implementation notes. If prose and a token disagree, the token wins and the prose must be corrected in the same change.

Principles.

  1. Type is the interface. A serif carries reading and display; UI sans and mono play support. Prose respects an optimal measure (~66ch). Nothing competes with the text.
  2. Ink and one accent. Ink is the base for text and the brand mark. Exactly one chromatic accent - manuscript red #a3341f - drives interaction, links, and marks. Brass is a rare warm counterpoint; everything else is paper and hairlines.
  3. Flat, not boxed. No card-in-card. Group with whitespace and a single hairline. Content surfaces are square (page-like); only controls and overlays carry a small radius.
  4. Borderless + shadow for elevation. Overlays float on one shadow (shadow-page) plus a near-invisible hairline. No hard borders on dialogs, menus, toasts.
  5. Tokens over literals. Every reusable visual, spatial, timing, and layout value is a token. Platform adapters consume resolved values; components never introduce literals.
  6. One primitive per pattern. Each interactive pattern - button, field, overlay, toast, loader - exists once. Forking a primitive is a regression.
  7. Accessible by construction. Every permitted foreground/background and essential component-boundary pair meets its WCAG target; color is never the sole signal and focus is always visible. See Accessibility.

Design tokens & architecture

Tokens are layered so the system ports to any stack without rewrites:

  • Primitive tokens - raw colors, dimensions, durations, and font stacks. Components never reference primitive colors directly; fixed brand assets are the sole exception.
  • Semantic tokens - role-named values (accent, text-secondary, border, error, shadow-page) with explicit light and dark modes where applicable.
  • Component tokens - anatomy and state values (button-primary.hover, input.error). They reference semantic tokens, plus primitive dimensions when the dimension has no semantic meaning.

Naming: role[-variant][-state] - e.g. accent, accent-hover, text-tertiary, banner-error. Lowercase, hyphenated, stable across platforms.

Portability targets (adapter mapping):

Platform Form
Web (CSS) Custom properties: --accent, --text-secondary, --shadow-page
Tailwind theme.extend.colors.accent, boxShadow['page'], etc.
JS / TS Resolved object: tokens.semantic.color.accent, etc.
Figma Variables with Light/Dark modes and matching semantic names
Terminal Palette dict / YAML skin (see Theming → TUI)
Print CSS Paged Media / typesetting stylesheet (see Typography → Print)

The source manifest stores resolved cross-platform values. A theme generator may use OKLCH or color-mix() during authoring, but exported tokens must be concrete values so CSS, Figma, native apps, terminals, and print produce the same result. Adapters must not copy CSS-only syntax into platforms that cannot interpret it.

Colors

Ink on paper with one accent. Deliberately warm - a cream paper (#faf8f4), not the cool blue-white of a typical app - so it reads as print, not chrome.

Anchors

  • Ink #17171a - primary text + brand mark. Never pure black. 16.9:1 on paper.
  • Accent / Manuscript red #a3341f - the one interaction color: buttons, links, focus rings, active nav, selected states, editorial marks. Oxblood, not fire-engine - a red-pencil edit, not an alarm. accent-hover #872a19 for press/hover.
  • Brass #b0812f - warm counterpoint, used sparingly (a rule above a byline, a small highlight). Large text / rules / decorative only (3.28:1 - fails AA for body text). Never an interaction driver.

Surfaces - paper #faf8f4, paper-sunken #f2ede4 (sidebar/recessed), surface #ffffff (cards), and field #fffdfa. Dark: bg #121211, surface #1c1b19, raised/field #211f1c. Dark text is warm paper-white #f3efe6, never #fff.

Semantic / status - separated from the brand so the UI never looks like one giant warning:

Role Light Dark On paper
success #2f7d5a #5aa07f 4.71:1 ✓
warning #a85f1e #d08a3a 4.58:1 ✓
error / destructive #c23b2b #e0604e 5.01:1 ✓
info #3f6f78 #5a97a2 5.27:1 ✓

Error is hotter and more saturated than the oxblood accent so "delete" never reads as "link." Dark-mode accent lifts to #d8664c (4.86:1 on dark cards).

Text hierarchy uses resolved solid colors so contrast does not change with the implementation's compositing behavior:

Token Light Minimum light contrast Use
text-primary #252527 14.4:1 body, headings
text-secondary #575657 6.3:1 supporting, metadata
text-tertiary #6b6a6a 4.62:1 muted labels, placeholders
text-quaternary #adabaa exempt disabled/decorative only - never information

Fills are explicit semantic tokens: fill-primary (selected), fill-secondary (active rows), fill-tertiary (hover), fill-quaternary (secondary controls), and fill-quinary (subtle decoration). Their resolved light/dark values are in the manifest; they are not interaction signals alone.

Categorical / data-viz - indicators only (charts, telemetry), never interaction: red · cyan-slate · brass · green · purple · gold (ordered for distinctness). See Data visualization for scales.

Accessibility

The system targets WCAG 2.2 AA. Verification is automated for every shipped theme and repeated whenever a color, mode, or component-boundary token changes.

Contrast. Text is ≥ 4.5:1 (normal) / 3:1 (large ≥ 24px, or ≥ 18.66px bold); essential UI boundaries and graphics are ≥ 3:1. Test every foreground against every surface on which it is allowed—not only against the canvas.

  • text-tertiary is normal-text safe on every light and dark surface.
  • Brass is large text, rules, charts, or decoration only in light mode.
  • hairline and stroke-hair are decorative. They must never be the only visible boundary or state signal. Interactive boundaries use border or border-strong, both ≥ 3:1 on their supported adjacent surfaces.
  • text-quaternary is restricted to disabled or decorative content. Required instructions, values, status, and placeholder text use text-tertiary or stronger.
  • A new theme must pass the complete contrast matrix before shipping.

Baseline verified pairs. Accent/paper 6.45:1; warning/paper 4.58:1; success/paper 4.71:1; error/paper 5.01:1; dark accent/dark surface 4.86:1. The light control border is 3.30:1 on paper; the dark control border is at least 3.28:1 on supported dark surfaces.

Focus. Every interactive element shows a visible focus ring: 2px focus-ring (accent), 1px offset. Never remove focus outlines; style them.

Target size. Prefer 44×44 CSS px for primary and touch controls. The WCAG 2.2 AA floor is 24×24 CSS px, subject to its spacing and inline exceptions. Icon buttons receive padding to reach 44×44 wherever layout permits.

Keyboard. Everything operable without a mouse. Overlays trap focus (Tab cycles within; Esc closes; focus returns to the opener). Provide a skip link to main content. Logical tab order follows visual order.

Color is never the sole signal. Status pairs color with an icon or label (error field shows an icon + message, not just a red border). Charts add pattern/label, not hue alone.

Motion. Honor prefers-reduced-motion: disable non-essential animation, keep a plain fade. No parallax/auto-play under reduced motion.

Content. Images carry alt; decorative images use empty alt. Form fields have programmatic labels; errors are linked via aria-describedby. Live regions announce toasts and asynchronous results. Loading controls retain their original accessible name and append a progress state such as “Saving, in progress.”

Typography

Three families, strictly separated by role. The serif is the identity.

Fraunces (display + reading - vendored). A characterful old-style serif with optical-size + weight axes, using a variable WOFF2 for each required style (roman, plus italic when the product uses italic text; SIL OFL, offline). Carries both display (wordmark, headings - high opsz, light weight, tight tracking) and reading (body at low opsz, weight 400, line-height 1.62, measure 66ch). Fallback: 'Iowan Old Style', Charter, Georgia, 'Times New Roman', serif.

System sans (UI). Body chrome, labels, buttons, controls, sidebar. Native stack, never vendored. Base 0.875rem / 1.5; compact UI may use 0.8125rem. Small labels use label-caps (0.75rem, uppercase, 0.12em, 600).

JetBrains Mono (code / figures / data). Stack falls back to Cascadia → SF Mono → system mono.

Kbd - native UI face, small caps, never themed.

opticalSize is an abstract token. Web adapters emit the Fraunces opsz axis through font-variation-settings or font-optical-sizing; adapters that do not support variable axes ignore it without changing the role's size or line height.

Reading surfaces constrain prose to the measure (66ch), center it, and open with a 3-line drop cap in accent - the signature reading flourish.

Substitutes. If Fraunces can't be vendored, Newsreader or Source Serif 4 transfer cleanly; the system-serif fallback keeps the editorial feel with zero files. UI sans → any humanist sans (Inter is an acceptable substitute).

Print & long-form (books, PDF/EPUB)

For book, report, and print output the screen scale gives way to a print scale:

  • Page geometry. Symmetric or golden-ratio margins; generous inner margin for binding. Single measure of 60–72 characters. Body at ~11pt / 1.5.
  • Baseline grid. Vertical rhythm on a consistent leading; headings snap to the grid.
  • Running heads & folios. Author/title verso, chapter recto; folio (page number) in label-caps at the foot or outer corner. Suppress on chapter openers and blank versos.
  • Chapter openers. Recto start, sinkage from the top, oversized chapter-number (Fraunces light), optional drop cap or small-caps first line.
  • Detailing. Real small-caps, old-style figures, ligatures; hang punctuation on justified measures; control widows/orphans (min 2 lines); enable hyphenation for justified text.
  • Footnotes / marginalia in footnote (8.5pt), separated by a short rule.
  • Front/back matter. Half-title, title, copyright, dedication, epigraph, TOC; then back matter (acknowledgments, about, colophon).
  • Images & plates. Full-measure or full-bleed; captions in ui-sans / small; figure numbering.
  • Color management. Screen tokens are sRGB; for offset print convert to CMYK and expect the manuscript-red to shift - proof it. Provide bleed (3mm) and trim marks for print PDFs. EPUB uses the screen tokens as-is.

Layout & responsive

Spacing - 4px base: xxs 2 · xs 4 · sm 8 · md 16 · lg 24 · xl 48 · section 64. Group with space before rules.

Grid & containers. Content max is content-max (1100px) on dashboard views; reading surfaces cap at reading-measure (66ch) and center. Desktop sidebar and page inset use the sidebar and page-inset layout tokens.

Breakpoints.

Viewport class Width Behavior
xs < 640 single column; sidebar → top bar/drawer; controls stack
sm 640–879 one or two columns; sidebar remains collapsed
md 880–1127 full sidebar + main; grids up to two columns
lg 1128–1439 full sidebar; grids up to three columns
xl ≥ 1440 content caps; gutters absorb the rest

The manifest values are min-width thresholds: sm 640, md 880, lg 1128, and xl 1440. Use those names consistently in CSS and application code.

Reduce columns at each step - never reflow rows unpredictably. Reading measure never exceeds 66ch regardless of width.

Layering (z-index). base 0 · sticky 100 · dropdown 200 · overlay 300 (toasts) · scrim 400 · modal 401 · tooltip 500. Never hand-pick z-index - use the scale.

Density. Default is comfortable. An optional compact mode tightens row padding and control heights for data-dense demos; it never changes type sizes below legibility or targets below the a11y floor.

Shape & radius

Editorial means crisp. Content surfaces are square; radius appears only on things you click or that float.

Scale Value Use
none 0px cards, content surfaces, reading column, panels
xs 2px chips, small tags
sm 3px buttons, inputs, icon buttons
md 4px nested control groups
lg 6px overlays, dialogs, popovers, menus
full 9999px toggles, pills, status dots, the brand tile

Square content + hairline rules reads like a set page. Do not soften cards "to look friendly" - that erases the identity.

Elevation & depth

One shadow system, three levels:

  • Flat - 95% of surfaces: a single hairline, no shadow.
  • shadow-card - cards/composer that must read against any background: soft 1px + hairline. Usually cards stay flat; elevation is for things that float.
  • shadow-page - the one overlay shadow (layered, downward-weighted, no hard border), paired with stroke-hair (3% currentColor). Every dialog, menu, popover, tooltip, toast uses it.
  • Scrim - rgba(0,0,0,0.32) light / 0.5 dark behind modal overlays.

Motion

Quick and functional. Tokens: fast 120ms (controls), base 180ms, slow 300ms (overlays/views); easings ease-out / ease-standard.

  • Hover: color transition fast. Focus: ring appears immediately (no animate-in).
  • Overlay enter/exit: fade + subtle scale (0.98→1, slow; reverse faster).
  • View transitions: fade, staggered per element.
  • Loader - the Caret: a blinking at motion.caret (see Notifications).
  • Reduced motion: collapse everything beyond a fade.

Edges & borders

Edges belong to the ink family (a whisper of accent warmth), never a separate gray. Four weights + the overlay hairline:

Token Use
hairline dividers, row separators, section rules (the default line)
border resting input/card borders
border-strong hover/active edges, focus-adjacent
focus-ring 2px accent ring on :focus-visible, 1px offset
stroke-hair the near-invisible 3% hairline on floating overlays

Rules: prefer whitespace over lines; one hairline where a divider is truly needed; never nest bordered boxes; tables use hairline row separators, no column borders; the last row drops its border.

Components

Each pattern exists once. Specify only states that apply; do not manufacture meaningless states to complete a checklist.

Primitive Required states
Button default · hover · active · focus · disabled · loading
Field default · hover · focus · disabled · error · read-only
Select/menu closed · open · focus · disabled · option-selected · error
Toggle/segmented off/unselected · on/selected · hover · focus · disabled
Overlay closed · entering · open · exiting
Async feedback idle · loading · success · empty · error

Anatomy and behavior. Every component specification records its parts, keyboard interaction, accessible name/description, focus behavior, content rules, and applicable states alongside its visual tokens. Native semantics are preferred. Custom widgets follow the relevant ARIA Authoring Practices pattern and receive automated keyboard tests.

Buttons

  • Primary - accent fill, on-accent text; hover → accent-hover; flat.
  • Destructive - error fill, on-error text (distinct from primary red).
  • Secondary - accent-muted wash, accent text.
  • Ghost - transparent, hover fill-tertiary.
  • Text / link - no chrome, accent, underline on hover.
  • Disabled - accent-disabled (primary) / reduced opacity; not-allowed.
  • Loading - the visible label may swap to the Caret, but the button retains its action name and exposes progress (for example, “Export, in progress”). The button stays sized and cannot be activated twice. Icons inherit one size; never re-set per instance.

Inputs, selects & forms

Shared control shape. Resting border; hover border-strong; focus full focus-ring + 2px ring (immediate). Background uses the field token. Keep native number spinners unless the product supplies equally discoverable and keyboard-operable increment/decrement controls. Prefer native <select> for simple forms. Use custom .csel only when search, rich options, or controlled cross-platform rendering is a product requirement; it must implement the combobox/listbox keyboard and ARIA contract. Error state below.

SegmentedControl / Switch / SearchField

  • Segmented - small mutually-exclusive sets; fill-primary on the selected segment with accent text/marker. It may visually replace radio controls, but preserves an accessible radiogroup and arrow-key behavior.
  • Switch - bare toggle, full radius, accent when on. It has a visible label or an equivalent accessible name and exposes aria-checked.
  • SearchField - borderless, underline-on-focus, auto-width; never a boxed tile.

Surfaces

  • Card - surface, square, hairline (or shadow-card when floating). No card-in-card.
  • ListRow - flat, flush-left: label / description / action. Prefer spacing to dividers.
  • Code / CodeBlock - JetBrains Mono; inline code 5% ink fill; blocks ride the editor surface. A code frame may carry three window-control dots - editorial traffic lights (manuscript-red · brass · sage), visible in both light and dark (never a low-contrast gray). Wide code (e.g. ASCII diagrams) scrolls horizontally within its own frame - never clipped, with a themed scrollbar.
  • ReadingView - the hero prose surface: measure + reading spec + drop cap.

Brand mark, Masthead & Loaders

  • BrandMark - the pilcrow in Fraunces on an ink tile (full radius), identical light/dark. The favicon is the documented xs-radius exception. Sidebar header and TUI banner use the standard mark. No substitute logo.
  • TUI masthead - a big gradient figlet wordmark (default face colossal), the editorial gradient sweeping manuscript-red → ember → hot-gold (ui.STOPS) and framed top and bottom by a mirrored flame rule. It sits on the left; on a wide terminal a GET STARTED command column sits beside it (stacking beneath on a narrow one, collapsing to a one-line wordmark when the mark would wrap), and it prints every launch. The gradient stops and figlet face are per-theme (each theme owns a distinct face, but all at the same big block scale - the small personality faces were retired in 0.4.1); a fallback face chain preserves the mark if a theme's font is unavailable. /theme <name> reprints the masthead live (on a cleared screen) so a face/palette previews without relaunching.
  • Loader - the Caret - a blinking (accent) for view/content loading, optionally with a live word/token counter. “Loading…” is never shown visually, but an equivalent localized status is always available to assistive technology.
  • Loader - the live-run grid spinner - the dashboard's "now running" activity indicator is a 3×3 grid of rounded cells: a comet lights the 8-cell outer ring clockwise (staggered animation-delays) with a white-hot HDR bloom head (accent + white + a layered box-shadow glow) trailing into the accent; the centre cell stays a dim core. Done = a solid green grid; idle = a static dim grid; reduced-motion = a static legible grid. Pure CSS, brand-colored via --accent/--green; the warm comet deliberately echoes the TUI masthead's flame gradient (one identity across surfaces).

Overlays

All floating surfaces share shadow-page + stroke-hair + lg radius and the layer scale. Taxonomy:

Overlay Trigger / behavior Dismiss
Modal / Dialog blocking; scrim behind; focus-trapped Esc · optional scrim click · × icon
Sheet edge-anchored panel (mobile nav, filters) Esc · scrim · swipe
Popover / Menu anchored to a control (.csel, actions) outside click · Esc · select
Tooltip hover/focus hint; ink fill, paper text blur / mouseout
Toast transient feedback (see Notifications) auto-timeout · ×

Rules: use an × icon when a compact visual close control is appropriate, but give it a localized accessible name such as “Close dialog.” Outside-click dismissal is disabled for destructive confirmation, unsaved work, onboarding, and other flows where accidental dismissal loses effort. Menus flip or shift near viewport edges and remain reachable at 400% zoom.

Notifications & feedback

  • Toast - transient, bottom-right, surface-raised + shadow-page; success/plain vs error. Default timeout is 6s and error timeout 10s. Pause the timer on hover/focus; do not auto-dismiss a toast containing an action or information the user must retain. Provide an accessible close control and live announcement. Show one at a time and queue extras.
  • Banner - persistent, in-flow, full-width: info / success / warning / error, using the corresponding fill-* token + icon + message + optional action. The manifest's resolved fills are equivalent to a 12% authoring tint; do not recompute them at runtime. No accent side-bar—stripes read as decoration. For state that must stay visible (quota, degraded mode).
  • Alert (inline) - a compact banner scoped to a section or form.
  • Badge - small count/label pill (accent-muted + accent, label-caps).
  • Status dot - 7px full dot: success/warning/error/accent (e.g. a live-run pulse). Always paired with a text label (color-not-sole-signal).
  • Progress - thin accent bar on a faint track; indeterminate uses the Caret.
  • Empty / loading / skeleton - EmptyState (centered, one guiding line); loading uses the Caret; skeletons are hairline-outlined blocks at fill-tertiary (no shimmer under reduced motion).

Highlights & selection

  • Text selection - selection (accent @22%) via ::selection.
  • Selected item - fill-primary wash + accent text/left-marker (nav item, list row, segmented segment, .csel option with a ✓).
  • Active row - fill-secondary.
  • Hover - fill-tertiary (never a heavy block).
  • Search / match highlight - accent-tinted background on the matched span; keep text contrast ≥ 4.5:1.
  • "New" / emphasis marker - a small accent badge or dot, plus a label; never color alone.
  • Focus highlight - the focus-ring, distinct from selection.

Error states

Errors are explicit, calm, and never rely on color alone.

  • Field validation - border → error, an icon + helper message in error beneath, linked with aria-describedby; on submit, focus the first invalid field and summarize errors at the top of the form.
  • Inline / section error - an alert (error banner) scoped to the region with a retry action where relevant.
  • Toast error - for async/action failures; error text, longer timeout, live-region announced.
  • Full-page / boundary error - a canonical ErrorState: mark/icon, a plain title, one-sentence cause, and a primary recovery action. Same look for React error boundaries, boot failures, and empty-with-error.
  • Destructive confirm - a modal with type-to-confirm for irreversible actions; the confirm button is destructive.
  • Copy - say what happened and what to do next ("Couldn't reach the model - check your key and retry"), never a raw stack trace.

Iconography

Inline SVGs, one set, no mixing libraries. 14px (0.875rem) standard / 12px compact; 1.5px stroke; inherit currentColor. Editorial glyphs (pilcrow ¶, caret ▍, em-dash -, section §) are typographic, set in the font - not drawn. Icons that carry meaning get an accessible label; icons beside text are aria-hidden. Terminal: Rich box-drawing + the caret motif (no emoji spinners). Heading anchor links (the copy-link affordance) use a muted icon that reveals on heading hover and turns accent on hover - never a loud, always-on colored mark.

Data visualization

  • Categorical - use the mode-specific cat-1cat-6 values in the manifest; stop at 6, then group “other.” Every light and dark mark is ≥ 3:1 against its supported surface. Each series also gets a label/shape, not hue alone.
  • Sequential - use a tested single-hue ramp. Any cell or mark required to understand the data must meet 3:1 against adjacent colors or receive a contrasting outline/direct label. Near-paper tints are decorative only.
  • Diverging - error ↔ neutral ↔ success for signed values.
  • Contrast - essential chart strokes/fills are ≥ 3:1 against their supported surface; axis and legend text use text-tertiary or stronger and meet normal text contrast.
  • Colorblind-safe - for critical distinctions use the Okabe-Ito set (shipped as the highcontrast theme in the reference impl) and rely on shape + label.

Imagery & brand

  • Brand mark - the pilcrow . Clear space ≥ the pilcrow's cap height on all sides; min size 16px (favicon) / 20px (UI). Ink tile + paper glyph, constant across themes. Misuse: don't recolor per theme, don't add effects, don't substitute a mascot.
  • Favicon - ink tile, paper pilcrow, xs radius.
  • Photography/illustration (marketing/book) - full-measure or full-bleed; let imagery carry visual weight so type can stay quiet (the editorial default).

Voice & microcopy

  • Case - sentence case for UI text, buttons, and headings ("Propose angles", not "Propose Angles"). label-caps is the only uppercase, for tiny labels.
  • Buttons - a verb ("Save", "Propose angles", "Export"), not "OK/Submit".
  • Errors - plain, specific, actionable; no blame, no jargon, no stack traces.
  • Empty states - one line of guidance + the primary action.
  • Numbers/dates - tabular figures for aligned numerals; ISO or localized dates consistently; currency/units explicit.
  • Tone - the editor's voice: exact, warm, unfussy.

Localization & RTL

  • Use logical properties (margin-inline, padding-block, start/end), not left/right, so layouts mirror for RTL automatically.
  • Mirror directional icons (chevrons, arrows) in RTL; never mirror the brand mark.
  • Allow text to grow ~30% (German/Finnish) - don't fix widths to English.
  • Provide non-Latin font fallbacks; the 66ch measure is Latin - adjust for scripts with different density (CJK ~ 40–45 characters).
  • Keep number/date/currency formatting locale-aware.

Theming

One identity, recolored - never restructured.

Modes. System (auto) · Light · Dark - one ink + manuscript-red identity; System follows the OS. Preference stored per-client, independent of engine settings.

Named themes. A shared palette catalog recolors the flat editorial layout— same structure, shapes, type, and shadows; only color values change. Theme authoring may derive colors programmatically, but committed manifests contain resolved light/dark values for every semantic color.

Theming contract. A valid theme defines every mode-dependent semantic color, including surfaces, text hierarchy, action states, control borders, focus, status, fills, scrim, and categorical data colors. Body text is ≥ 4.5:1 and essential UI/graphics are ≥ 3:1 on every allowed surface. CI validates the full matrix before shipping; defining only ink, paper, and accent is insufficient.

Terminal (TUI). The default “ink & brass” skin preserves manuscript red as the interaction accent. Brass is decorative/secondary; success, warning, error, and data colors retain their semantic roles. Switch with /theme. Terminal adapters use resolved ANSI colors with tested fallback pairs; missing skin values inherit the complete default semantic map. User skins live in ~/.<app>/skins/ as YAML.

CLI. Inherits the active skin for styled output; plain when piped.

Do's and Don'ts

  • Do anchor on ink + one accent. Add a color only by extending the palette, and only for a semantic/categorical role.
  • Do let type lead: serif display + reading, sans UI, mono data; respect the measure.
  • Do keep content surfaces square; reserve radius for controls/overlays.
  • Do use shadow-page + stroke-hair on every floating overlay; reference the z-index scale.
  • Do reuse primitives; forking one is a regression.
  • Do verify a new color/theme against the AA contrast table.
  • Don't reintroduce blue as the brand. Blue is not this system's color.
  • Don't nest cards; group with whitespace + a hairline.
  • Don't display a generic spinner or visual “Loading…” label—use the Caret; do expose a localized loading status to assistive technology.
  • Don't signal state with color alone; pair an icon/label.
  • Don't remove focus outlines; style them.
  • Don't use brass for body text. Reserve text-tertiary for short muted labels/placeholders rather than primary reading text; never use text-quaternary for information.
  • Don't let a named theme change layout, shape, type, or shadows - colors only.
  • Don't style chrome with a bare class selector (.header, .card) that can also match a component's own sub-parts (e.g. a code-frame's .header) - scope to the element (header.header), or you get doubled borders and stray fills.
  • Don't size an icon in em next to large display type - it inherits the heading's font size and balloons. Use a fixed rem for anchor/inline icons.

Governance & versioning

  • SemVer the system using major.minor.patch. Breaking = renamed/removed token or changed semantic meaning. schemaVersion changes only when the manifest structure changes; version changes for design-system releases.
  • Changelog every token addition, rename, and deprecation. During minor releases keep a deprecated alias for at least one minor cycle; a major release may remove aliases when it includes an explicit migration map.
  • Definition of done for a component: applicable states specified, anatomy documented, AA verified, keyboard/focus/accessibility semantics tested, tokens only, RTL/zoom/reduced-motion covered, and visual regression fixtures added.
  • Single source of truth - the YAML manifest is normative. Generated platform files are build artifacts and are never edited by hand. Prose and reference notes are reviewed for drift in the same pull request.

Adopting this system in a new project

  1. Run a platform adapter against the manifest; do not manually copy values. Resolve light/dark modes and references before emitting platform files.
  2. Vendor Fraunces (or accept the system-serif fallback); wire the sans/mono stacks.
  3. Build the primitives once: Button, Input/Field, native Select plus .csel where justified, Overlay, Toast/Banner, Loader (Caret), BrandMark, ReadingView.
  4. Set modes (System/Light/Dark); if adding a named theme, run the theming contract check.
  5. Verify the complete contrast matrix, keyboard contracts, focus return, target sizes, zoom/reflow, RTL, reduced motion, and accessible loading states.
  6. For print/book output, add the Print & long-form stylesheet.
  7. Add token-reference validation, contrast tests, component accessibility tests, and light/dark visual regression pages to CI.

Non-normative reference material

The appendices record product implementation status. They do not redefine core tokens or grant exceptions silently; any divergence is listed as a known gap.

Appendix A - Reference implementation: Writing Agent

Writing Agent (an autonomous writing system) implements this system across three surfaces that share one identity: a web dashboard (pure-stdlib server + SPA), an interactive TUI (Rich + prompt_toolkit), and a one-shot CLI.

  • Type: Fraunces variable WOFF2 assets for the required roman/italic styles (SIL OFL), served over /static/fonts/; system sans for UI; JetBrains Mono for code/figures. Fully offline; fallback serif if the font doesn't load.
  • Identity: ink on warm paper, manuscript-red accent, brass sparingly; square content surfaces, shadow-page overlays, the pilcrow wordmark, the Caret loader, drop-cap ReadingView on the manuscript.
  • Web views: Studio · Live run (SSE) · Projects · Project (Overview / Activity / Evals / Artifacts / Rejected / Export / Cost) · Telemetry · Skills · Settings. Export offers all six formats + a Rewrite (restyle in a style/persona/emotion). Custom .csel dropdowns, ConfirmModal (type-to-confirm), themed scrollbars.
  • Theming: System / Light / Dark + the shared named-theme catalog (ui.THEMES) - colors-only recolor of the editorial layout, matched between web and TUI.
  • TUI target skin “ink & brass”: manuscript red is the interaction accent; brass is secondary/decorative; semantic status colors remain distinct. The former gold-primary palette and multicolor wordmark are deprecated because they conflict with the cross-surface interaction and brand contracts.
  • Documentation site (Astro + Starlight, repo writingagentdocs): the public docs implement the same identity via Starlight CSS-variable overrides - ink on warm paper, manuscript-red accent + links + hero CTA, Fraunces serif for headings and reading body (loaded from Google Fonts alongside JetBrains Mono), system sans for UI chrome, square hairline surfaces, accent-tinted selection. Code frames carry the three editorial window-dots and horizontally scroll wide content; heading anchor links are muted (accent on hover, fixed 1rem icon); the landing faux-terminal previews the TUI “ink & brass” skin. The target brand asset is the ink-tile pilcrow. Any retained pen-nib logo, favicon, or alternate wordmark is a migration gap—not a permitted brand variant. Header chrome is scoped to header.header - a bare .header collides with the inner nav wrapper and Expressive-Code frame headers (doubled hairlines).

Appendix B - Reference implementation audit & known gaps (2026-07-18)

Fixed in the redesign: justified rich selects use .csel; native scrollbars are themed where supported; native prompt()/confirm() became ConfirmModal; visual “Loading…” became the Caret while retaining an accessible status; custom-select keyboard/ARIA, artifact copy affordances, horizontally scrollable wide tables, and an accessible run badge were added. The normative palette now uses solid text colors, ≥3:1 interactive borders, complete dark semantics, and mode-specific categorical colors.

Known gaps (accepted / documented):

  • Responsive - the dashboard is desktop-tuned; the tokenized sidebar and breakpoint behavior above are the target, not yet fully implemented.
  • Markdown tables in the artifact renderer show raw pipes (rare in reports).
  • title= tooltips are native (not the themed tooltip).
  • Popover collision - the .csel menu does not yet flip near a viewport edge.
  • Number inputs - removed native spinners still need an equally discoverable, keyboard-operable replacement or restoration of the native controls.
  • Brand migration - TUI gold-primary/multicolor wordmark and documentation pen-nib assets must migrate to the normative interaction and pilcrow contracts.
  • Contrast automation - baseline tokens are verified; the complete theme/surface/state matrix must still be enforced in CI.
  • SSE reconnect is best-effort (no exponential backoff).

Appendix C - Changelog and migration

3.0.0 - 2026-07-18

  • Replaced the ambiguous top-level token block with the normative tokens.primitive, tokens.semantic, and tokens.component schema.
  • Added complete light/dark semantic colors, concrete cross-platform values, missing fill tokens, mode-specific data colors, layout dimensions, timeouts, typography roles, and component state tokens.
  • Raised interactive borders and tertiary text to their required contrast floors.
  • Clarified native/custom select policy, loading announcements, close-control naming, toast timing, target sizing, responsive ranges, and chart contrast.
  • Made the pilcrow and manuscript-red interaction contracts consistent across web, TUI, CLI, documentation, and brand guidance.
  • Marked reference implementation notes as non-normative and recorded remaining product migrations explicitly.

Migration from version 2: read former root paths through the new layers: colors.*tokens.semantic.color.*, typography.*tokens.semantic.typography.*, rounded.*tokens.semantic.radius.*, and components.*tokens.component.*. Platform adapters resolve the requested light/dark mode before emitting values. Removed CSS-only derived values must be replaced with the resolved semantic values in this manifest.