跳到正文
册页

基础

色彩

一张白纸做底,一个近黑的记号,三种线的粗细,以及状态色。

这套系统是单色的。底是纸白,记号是近黑,文件里剩下的唯一彩色是状态——它绑定在状态上,永远不绑定品牌。过去由色相承担的事,现在由字重、线条和反色承担。

文字只有两级,再往下就没有了。下限是 --ink-3-aa,它在白底上以 6.7:1 通过 WCAG AA;比它高的每一级都更深。会落到这条线以下的浅色,不是这套系统可以做的选择。

暗色模式是同一批名字的换值,不是第二套配色。这正是组件从不直接读原始 token 的原因:它读的是语义别名,模式属性一翻,别名自己就重新解析了。

接着读高度

色彩 token

色彩 token tokens
样例Token值暗色备注
--paper#ffffff#0d0d0d—
--paper-2#f7f7f5#161616—
--barvar(--stone)var(--stone)—
--bar-2var(--stone)var(--stone)—
--ink#101010#f4f4f2Two 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-3rgba(16, 16, 16, 0.55)rgba(244, 244, 242, 0.55)—
--ink-3-aa#5c5c5c#9a9a96—
--forest#3a3a3a#c9c9c5The 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.
--mistvar(--stone)var(--stone)—
--stone#f2f1ee#1f1f1f—
--clayvar(--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-inkvar(--clay)var(--clay)—
--feature-surface#262626#1a1a1aThe 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-featurevar(--paper)var(--ink)—
--on-photovar(--paper)#fbfbfa—
--photo-caption-surfacevar(--ink)var(--paper)—
--photo-shadow0 1px 6px rgb(0 0 0 / 0.72)0 1px 6px rgb(0 0 0 / 0.72)—
--rule#edebe6rgba(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#dbdbd6rgba(244, 244, 242, 0.26)—
--rule-hardvar(--ink)var(--ink)—
--redvar(--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-inkvar(--clay)var(--clay)—
--red-softrgba(16, 16, 16, 0.07)rgba(244, 244, 242, 0.12)—
--on-redvar(--paper)var(--paper)—
--ok#3d6b34#84b47b─── Status ─── the only chroma in the system, and it is bound to state.
--ok-softrgba(61, 107, 52, 0.13)rgba(132, 180, 123, 0.16)—
--warn#8a5a00#d9a55d—
--warn-softrgba(138, 90, 0, 0.13)rgba(217, 165, 93, 0.16)—
--danger#a83214#ee8b68—
--danger-softrgba(168, 50, 20, 0.14)rgba(238, 139, 104, 0.16)—
--scrimcolor-mix(in srgb, var(--ink) 62%, transparent)color-mix(in srgb, #000 72%, transparent)—
--backgroundvar(--paper)相同─── Surfaces ───────────────────────────────────────────────────────────
--background-elevatedvar(--paper-2)相同—
--code-backgroundvar(--bar)相同—
--nav-backgroundvar(--paper)相同—
--card-backgroundvar(--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.
--foregroundvar(--ink)相同─── Text ───────────────────────────────────────────────────────────────
--foreground-mutedvar(--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-colorvar(--rule-2)相同─── Lines ──────────────────────────────────────────────────────────────
--border-subtlevar(--rule)相同—
--accentvar(--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-hovervar(--red-ink)相同—
--accent-mutedvar(--red-soft)相同—
--accent-washvar(--red-soft)相同—
--accent-foregroundvar(--on-red)相同—
--accent-on-darkvar(--red)相同—
--accent-on-mutedvar(--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.
--successvar(--ok)相同─── Status ─── bound to state, never to brand. ─────────────────────────
--success-washvar(--ok-soft)相同—
--warningvar(--warn)相同—
--warning-washvar(--warn-soft)相同—
--danger-washvar(--danger-soft)相同—
--infovar(--ink-2)相同—
--info-washvar(--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).
--overlayvar(--scrim)相同—
--diagram-line-softvar(--ink-3-aa)相同—

数据

这是整套系统唯一一处必须回答一个单色方案宁愿不被问到的问题的地方:没有色相可以花,怎么把六条序列分开?答案是纹理——每张图都提供填充样式,而下面这条色阶是第二层编码,不是第一层。八级是交错排列的,所以相邻序列在明度上尽可能拉开(ΔE 21,下限是 15),而且每一级在自己的底色上都过 3:1。彩色色板能通过的六项检查里,有两项在这里是刻意不通过的,而且是明说而不是藏着:色度下限(这些都是灰)和明度带(--series-1 就是墨色本身)。真的需要色相的使用者,是去设 data-chart-palette="chroma",而不是自己挑十六进制;如果图表要穿的是一个品牌而不是八个分类,那就是 data-chart-palette="accent",它把同样这八个名字从 --clay 上派生出来。--chart-fill 和 --chart-texture 是全系统唯一一对在两种底色上取值不同的 token:墨色以 14% 压在纸白上是一条读得出的带,纸白以 14% 压在近黑上什么都不是。

数据 tokens
样例Token值暗色备注
--series-1#101010CanvasText─── Data ─────────────────────────────────────────────────────────────── The series ramp a chart paints with, and the one place the White Reset has to answer a question a monochrome system would rather not be asked: how do you tell six lines apart with no hue left to spend? The answer is that hue was never the primary carrier here — TEXTURE is. Every chart primitive ships a fill variant (hatched, dotted, lines, stripped, duotone) and the series ramp below is the SECOND encoding, not the first. That ordering is what makes the set legible to a colour-blind reader, in greyscale print, and under forced-colours, none of which a hue-first categorical palette survives. The eight steps are interleaved rather than sequential — light, dark, light, dark — so neighbouring series sit as far apart on the lightness ramp as eight steps allow. Adjacent separation is ΔE 21.0 (OKLab ×100) against a 15 floor, and every step clears 3:1 on its own ground. Two checks a chromatic palette would pass fail here BY DESIGN, and are stated rather than hidden: the chroma floor (these are greys) and the lightness band (--series-1 is ink, and ink is the darkest mark the system has). Texture plus a legend is the relief, and both are mandatory — `Chart.Legend` is not optional above one series. A consumer who genuinely needs hue does not hand-pick hexes: it sets `data-chart-palette="chroma"` (see themes.css), which re-points these eight names onto a categorical palette validated against both grounds — or `"accent"`, which ladders the same eight names out of `--clay` when what the charts have to carry is a brand rather than eight categories.
--series-2#565656CanvasText—
--series-3#202020CanvasText—
--series-4#6a6a6aCanvasText—
--series-5#313131CanvasText—
--series-6#7e7e7eCanvasText—
--series-7#434343CanvasText—
--series-8#939393CanvasText—
--series-track#eeece7CanvasThe empty ground a part-of-whole mark is drawn against — the unfilled remainder of a radial bar, the track behind a gauge. Not --rule: a track is a field, and a field that reads as a hairline disappears at radius.
--chart-fill0.140.45How solid a translucent mark is, and the one pair of tokens in the file that is NOT the same number on both grounds. A wash is `ink at N% over the page`. On paper, ink at 14% lands around #e3e3e3 — a legible, recessive band. Run the same 14% the other way and paper-white over near-black lands around #2c2c2c, which on a #0d0d0d ground is not a band, it is nothing. Perceived lightness is not linear and the two directions do not meet in the middle, so a single number cannot serve both: the dark side needs roughly twice the alpha to read as the same weight of mark. Every area fill, texture and flow band in the chart package reads these rather than carrying its own literal.
--chart-texture0.421—
--chart-surfacevar(--paper)相同─── Data ───────────────────────────────────────────────────────────── What a chart is allowed to read. The series ramp itself stays a primitive — a chart names a SERIES by index, and an index has no role to alias — but everything around the marks does have one, and naming it here is what keeps a chart from reaching for --rule or --ink directly. --chart-cursor is deliberately heavier than --chart-grid: the crosshair has to be findable against the grid it crosses, and at hairline weight on a dashed grid it was not.
--chart-gridvar(--rule)相同—
--chart-cursorvar(--rule-2)相同—
--chart-axisvar(--ink-3-aa)相同—
--chart-trackvar(--series-track)相同—
--chart-patternvar(--rule)相同The decorative plate behind the plot — a dot field, a hatch, a grid of plus marks. One step quieter than the grid, because it sits BEHIND data and must never compete with it for the same read.

深度

其中四个刻意什么都不画:--shadow-sm、--shadow 和 --shadow-lg 解析为 none,--shadow-color 解析为 transparent。这套系统里的 box-shadow 永远不带模糊;--lift 是取代高度阶梯的那道硬墨偏移。

深度 tokens
样例Token值暗色备注
--shadow-offsetvar(--clay)var(--clay)—
--shadow-colortransparent相同─── 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. --lift below is the sanctioned offset idiom for anything that must read as raised.
--shadow-smnone相同—
--shadownone相同—
--shadow-lgnone相同—
--lift3px 3px 0 0 var(--shadow-offset)相同The one depth cue the system does have: a hard 3px ink offset, no blur, in the accent rather than a grey because --shadow-offset points at --clay. PUBLISHED, NOT CONSUMED. No component in this package sets either one — there is not a single shadow-(--lift) in src/. What the library actually separates with is a hairline, a change of ground, and the reversal that Card variant="plate" reaches for. These two exist so a consumer building a raised plate of their own has the system's answer instead of inventing a blur; do not read them as describing what the library does.
--lift-sm2px 2px 0 0 var(--shadow-offset)相同—
--disabled-opacity0.5相同—

焦点

焦点 tokens
样例Token值暗色备注
--focus-on-featurevar(--on-feature)var(--on-feature)—
--focusvar(--red)相同─── Focus ───
--focus-w2px相同—
--focus-off3px相同—
--ringvar(--focus)相同─── Focus + motion ─── the site's long-lived names onto the primitives.
--ring-offsetvar(--focus-off)相同—