Skip to content

FoundationsColor

Eight tokens, one palette, no exceptions.

The system is warm-neutral. Two surfaces, one ink, four supporting roles, two surfaces, two line tokens. The whole site lives in this set.

8 core tokens2 surface tokens2 line tokensWCAG AA at all pairings

The eight core tokens

These are the only colors that should ever appear in a component. If you find yourself reaching for a value that isn't on this list, the right answer is to use the nearest token — not to add a new one.

--background

Page surface

#F6F3EF

--foreground

Body text, primary buttons, focus

#1A1816

--card

Card surface (rarely used directly)

#FAF8F5

--muted

Inset surface, hover wash

#ECE8E2

--muted-foreground

Secondary text, labels, captions

#6F6860

--accent

Editorial accent, serif emphasis

#8A7D6E

--border

Default 1 px borders

#DDD7CE

--destructive

Errors, destructive actions

hsl(0 72% 51%)

Surfaces

Three surface tokens, used in this exact order: --background for the page, --surface-elevated for cards and dropdowns that should sit above the page, --surface-inset for recessed surfaces like code blocks and table rows. The depth is in the eye, not in the shadow.

--surface-elevated

Cards, dialogs, dropdowns

#FAF8F5

--surface-inset

Code blocks, table rows, inset wells

#EDE9E3

Lines, not shadows

The system uses lines more than shadows. --line-subtle is the 8% ink divider, used inside cards and between table rows. --line-strong is the 16% ink divider, used to outline cards and to separate sections. Reach for a shadow only when a line won't do.

--line-subtle

Inside-card dividers, table row borders

rgba(26,24,22,0.08)

--line-strong

Card borders, section dividers

rgba(26,24,22,0.16)

Color roles in components

How each token shows up in the live components.

RoleSwatchWhere it appearsToken
BackgroundBody--background
ForegroundBody text, icon, button--foreground
Primary buttonButton --primarybg-foreground
Secondary buttonButton --secondarybg-surface-elevated
Muted textCaption, helpertext-muted-foreground
DestructiveError, deletetext-destructive
AccentSerif italic emphasistext-accent
Focus ring:focus-visiblering-ring

Dark mode, deliberately not shipped

The site does not have a dark mode. The decision is a trade-off, not a mistake: the warm-neutral palette is designed to read like printed paper, and a dark counterpart would change the editorial voice more than it would save battery.

Contrast

All foreground/background pairings clear WCAG AA at 4.5:1 for body text and 3:1 for large text. --muted-foreground on --background is the lowest pair at 5.84:1, used only for non-essential text (captions, helper text, metadata). It is never used for body copy.

ForegroundBackgroundRatioUse
--foreground--background13.74:1Body text
--foreground--surface-elevated12.96:1Card text
--muted-foreground--background5.84:1Helper, caption, label
--accent--background4.62:1Editorial accent, italic emphasis
--destructive--background4.51:1Error text

How to use these tokens

Use them through Tailwind's color utilities, which are bound to the@theme inline map in app/globals.css. Never hard-code a hex value in a component.

tailwind
<div className="bg-background text-foreground">
  <p className="text-muted-foreground">Helper text goes here</p>
  <button className="bg-foreground text-background">Primary</button>
  <button className="border border-border bg-surface-elevated text-foreground">
    Secondary
  </button>
</div>

Why these values

The eight core tokens are warm-neutral because the site reads as editorial, not corporate. Cream and ink is the same pair the New York Times Magazine and Hermes have used for decades — it is well-trodden because it works.

--muted-foreground at #6F6860 is the lowest "safe" warm-gray in print; at 5.84:1 against cream it clears AA at every size, and it has enough chroma to feel like the same family as ink.

--accent is the one piece of color in the system. It is used sparingly, in serif italic emphasis, the way a designer would use a single colored ink on a printed page.