基础
色彩
一张白纸做底,一个近黑的记号,三种线的粗细,以及状态色。
这套系统是单色的。底是纸白,记号是近黑,文件里剩下的唯一彩色是状态——它绑定在状态上,永远不绑定品牌。过去由色相承担的事,现在由字重、线条和反色承担。
文字只有两级,再往下就没有了。下限是 --ink-3-aa,它在白底上以 6.7:1 通过 WCAG AA;比它高的每一级都更深。会落到这条线以下的浅色,不是这套系统可以做的选择。
暗色模式是同一批名字的换值,不是第二套配色。这正是组件从不直接读原始 token 的原因:它读的是语义别名,模式属性一翻,别名自己就重新解析了。
色彩 token
| Token | 值 | 暗色 | 备注 | |
|---|---|---|---|---|
--paper | #ffffff | #0d0d0d | — | |
--paper-2 | #f7f7f5 | #161616 | — | |
--bar | var(--stone) | var(--stone) | — | |
--bar-2 | var(--stone) | var(--stone) | — | |
--ink | #101010 | #f4f4f2 | Two text steps and nothing lighter. --ink-3-aa is the floor — it clears AA on white at 6.7:1, and every step above it is darker. --ink-3 is the same weight expressed as alpha, for rules and washes that must sit over a photograph as well as over paper. | |
--ink-2 | #4a4a4a | #b4b4b0 | — | |
--ink-3 | rgba(16, 16, 16, 0.55) | rgba(244, 244, 242, 0.55) | — | |
--ink-3-aa | #5c5c5c | #9a9a96 | — | |
--forest | #3a3a3a | #c9c9c5 | The second editorial voice is not a hue — it is one more neutral step, sitting between --ink and --ink-2, so a byline separates from body copy without introducing colour. | |
--mist | var(--stone) | var(--stone) | — | |
--stone | #f2f1ee | #1f1f1f | — | |
--clay | var(--ink) | var(--ink) | Monochrome collapses the pointer onto the mark: the accent IS ink. What used to be carried by hue is now carried by weight, rule and reversal — an ink pill, an ink underline, an ink-filled plate. | |
--clay-ink | var(--clay) | var(--clay) | — | |
--feature-surface | #262626 | #1a1a1a | The one reversed plate, and the one place the ramp needs a tenth step. Set to var(--ink) it was the page's darkest possible mark used as a field, which on a white page reads as a hole punched through it rather than as a panel. Two steps lighter keeps the reversal — 15.1:1 for paper-white type on it — and matches the dark side. | |
--on-feature | var(--paper) | var(--ink) | — | |
--on-photo | var(--paper) | #fbfbfa | — | |
--photo-caption-surface | var(--ink) | var(--paper) | — | |
--photo-shadow | 0 1px 6px rgb(0 0 0 / 0.72) | 0 1px 6px rgb(0 0 0 / 0.72) | — | |
--rule | #edebe6 | rgba(244, 244, 242, 0.14) | Three rule weights, because a monochrome page has no colour left to separate one band from the next: hairline, edge, and hard. | |
--rule-2 | #dbdbd6 | rgba(244, 244, 242, 0.26) | — | |
--rule-hard | var(--ink) | var(--ink) | — | |
--red | var(--clay) | var(--clay) | Historic role names, kept so a consumer written against the previous palette keeps resolving. Their hue is now ink; the role remains "the single editorial pointer". | |
--red-ink | var(--clay) | var(--clay) | — | |
--red-soft | rgba(16, 16, 16, 0.07) | rgba(244, 244, 242, 0.12) | — | |
--on-red | var(--paper) | var(--paper) | — | |
--ok | #3d6b34 | #84b47b | ─── Status ─── the only chroma in the system, and it is bound to state. | |
--ok-soft | rgba(61, 107, 52, 0.13) | rgba(132, 180, 123, 0.16) | — | |
--warn | #8a5a00 | #d9a55d | — | |
--warn-soft | rgba(138, 90, 0, 0.13) | rgba(217, 165, 93, 0.16) | — | |
--danger | #a83214 | #ee8b68 | — | |
--danger-soft | rgba(168, 50, 20, 0.14) | rgba(238, 139, 104, 0.16) | — | |
--scrim | color-mix(in srgb, var(--ink) 62%, transparent) | color-mix(in srgb, #000 72%, transparent) | — | |
--background | var(--paper) | ─── Surfaces ─────────────────────────────────────────────────────────── | ||
--background-elevated | var(--paper-2) | — | ||
--code-background | var(--bar) | — | ||
--nav-background | var(--paper) | — | ||
--card-background | var(--paper) | Kept as an alias rather than removed: the White Reset retired the card surface, so an elevated variant has nothing left to point at. Consumers written against it keep resolving, on paper. | ||
--foreground | var(--ink) | ─── Text ─────────────────────────────────────────────────────────────── | ||
--foreground-muted | var(--ink-2) | — | ||
--on-dark | #fbfbfa | Text sitting on a PHOTOGRAPH — a hero, a lightbox, a scrim. A photograph is a dark ground in BOTH themes, so this must not follow the paper/ink swap: as var(--paper) it inverted to near-black in dark mode, which is how headings disappeared into their own images. A literal, not var(--ink). | ||
--brand-plate | #ffffff | The ground under a THIRD-PARTY brand mark, and for the same reason: a foreign logo is drawn against one fixed backdrop and shipped flat, with no light-ink sibling. A plate that followed the swap erased it in dark mode. Not for our own marks, which are authored against the theme. | ||
--border-color | var(--rule-2) | ─── Lines ────────────────────────────────────────────────────────────── | ||
--border-subtle | var(--rule) | — | ||
--accent | var(--red) | ─── Accent ───────────────────────────────────────────────────────────── One pointer, and in a monochrome system it points at ink — law 7. Every surface that marks a CHOICE reads from here rather than from --ink directly: a primary button, a checked box, the active tab, the current page, a slider's filled range, the selected day. That distinction is the whole point of having a pointer at all. While the accent IS ink the two render identically, which is why it went unnoticed that half the system was reading the primitive — and why re-pointing this one token now re-skins the system without a component being touched. | ||
--accent-hover | var(--red-ink) | — | ||
--accent-muted | var(--red-soft) | — | ||
--accent-wash | var(--red-soft) | — | ||
--accent-foreground | var(--on-red) | — | ||
--accent-on-dark | var(--red) | — | ||
--accent-on-muted | var(--red-ink) | Accent text sitting on an accent-muted panel, which --accent cannot do: it clears AA on paper by 4.58:1, and any tint of itself underneath pushes it below 4.5. This names the situation so the choice is not left to whoever writes the next panel. | ||
--success | var(--ok) | ─── Status ─── bound to state, never to brand. ───────────────────────── | ||
--success-wash | var(--ok-soft) | — | ||
--warning | var(--warn) | — | ||
--warning-wash | var(--warn-soft) | — | ||
--danger-wash | var(--danger-soft) | — | ||
--info | var(--ink-2) | — | ||
--info-wash | var(--red-soft) | — | ||
--photo-scrim | #101010 | The photo scrim is always dark by design, so it takes the ink literal rather than the semantic token (which inverts). | ||
--overlay | var(--scrim) | — |
深度
其中三个刻意解析为空。这套系统里的 box-shadow 永远不带模糊;--lift 是取代高度阶梯的那道硬墨偏移。
| Token | 值 | 暗色 | 备注 | |
|---|---|---|---|---|
--shadow-offset | var(--clay) | var(--clay) | — | |
--shadow-color | transparent | ─── Depth ──────────────────────────────────────────────────────────── Law 2 — a box-shadow is never blurred. The system has no elevation ramp; depth is a hard offset in ink, applied per component. These three resolve to nothing on purpose, so a component ported from a shadowed system stays flat instead of drawing a blur the White Reset does not have. Reach for --lift instead, which is the offset idiom expressed once. | ||
--shadow-sm | none | — | ||
--shadow | none | — | ||
--shadow-lg | none | — | ||
--lift | 3px 3px 0 0 var(--shadow-offset) | The one depth cue the system does have: a hard 3px ink offset, no blur. Used by a plate that must read as sitting above the page. | ||
--lift-sm | 2px 2px 0 0 var(--shadow-offset) | — | ||
--disabled-opacity | 0.5 | — |
焦点
| Token | 值 | 暗色 | 备注 | |
|---|---|---|---|---|
--focus-on-feature | var(--on-feature) | var(--on-feature) | — | |
--focus | var(--red) | ─── Focus ─── | ||
--focus-w | 2px | — | ||
--focus-off | 3px | — | ||
--ring | var(--focus) | ─── Focus + motion ─── the site's long-lived names onto the primitives. | ||
--ring-offset | var(--focus-off) | — |