surface
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesesurface
surface
Colour in an interface is mostly the absence of colour. Get the neutrals and the elevation right and one accent will do the rest.
界面中的颜色大多体现为色彩的缺席。先把中性色和层级处理好,一个强调色就足以完成其余设计。
Tokens first
Token 优先
Name colours by role, never by value. A palette that survives both themes has roughly this shape:
| Token | Role |
|---|---|
| The outermost background, behind the app frame |
| The app surface |
| Cards, popovers, anything lifted one step |
| Hover fill for rows and items |
| Default border |
| Stronger border, focus rings, dividers that must be seen |
| Primary text and icons |
| Secondary text |
| Tertiary, meta, placeholders |
| Faint: decorative, disabled, dividers in text |
| One. For the primary action and focus, and nothing else |
| Status, muted, with a text-safe variant each |
Everything else derives. Components never reference a hex; they reference a token. Dark mode is a second set of values for the same tokens, not a second set of components.
颜色应按角色命名,绝不要按具体数值命名。适用于两种主题的色板大致如下:
| Token | 角色 |
|---|---|
| 最外层背景,位于应用框架后方 |
| 应用表面 |
| 卡片、弹出层,以及任何抬高一层的内容 |
| 行和项目的悬停填充 |
| 默认边框 |
| 更强的边框、焦点环,以及必须清晰可见的分隔线 |
| 主要文字和图标 |
| 次要文字 |
| 三级文字、元信息和占位内容 |
| 微弱色:装饰内容、禁用状态,以及文字中的分隔线 |
| 仅一个。用于主要操作和焦点状态,不得用于其他位置 |
| 状态色,应保持克制;每种都要配一个适合文字使用的变体 |
其余颜色全部派生。组件绝不引用十六进制值,只引用 Token。深色模式是同一组 Token 的第二套取值,而不是第二套组件。
Neutrals
中性色
Near-black and near-white, both slightly warm or slightly cool, consistently. Pure and are too hard; a dark theme on with text at reads as considered where / reads as unset.
#000#fff#080808#ededed#000#fffA workable dark set:
page #080808 · frame #0d0d0d · raised #141414 · hover #161616
line #1c1c1c · line-2 #262626
fg #ededed · fg-2 #9b9b9b · fg-3 #6b6b6b · fg-4 #454545And light:
page #f2f2f0 · frame #fbfbfa · raised #ffffff · hover #f1f1ef
line #ebebe8 · line-2 #dcdcd8
fg #151515 · fg-2 #5f5f5c · fg-3 #8d8d89 · fg-4 #b8b8b3Steps are small on purpose. Surfaces differ by 2–4% lightness; that is enough for the eye to separate them without stripes. Test by taking a screenshot and squinting: you should see the structure, not a patchwork.
Build neutrals in OKLCH when generating them, so that steps are perceptually even and the hue stays constant as lightness changes.
接近黑色和接近白色,两者都要略偏暖或略偏冷,并保持一致。纯 和 太刺眼;以 为背景、以 为文字的深色主题会显得经过精心设计,而 / 组合则像是未经设置。
#000#fff#080808#ededed#000#fff一套可用的深色配色:
page #080808 · frame #0d0d0d · raised #141414 · hover #161616
line #1c1c1c · line-2 #262626
fg #ededed · fg-2 #9b9b9b · fg-3 #6b6b6b · fg-4 #454545以及一套浅色配色:
page #f2f2f0 · frame #fbfbfa · raised #ffffff · hover #f1f1ef
line #ebebe8 · line-2 #dcdcd8
fg #151515 · fg-2 #5f5f5c · fg-3 #8d8d89 · fg-4 #b8b8b3这些色阶刻意保持很小的差距。不同表面之间的亮度只相差 2–4%;这已足以让眼睛区分层次,而不会形成条带状割裂。截一张图并眯起眼睛检查:你应该看到的是结构,而不是拼凑的色块。
生成中性色时使用 OKLCH,这样各色阶在感知上更加均匀,且随着亮度变化,色相保持不变。
Dark mode is not inverted light mode
深色模式不是反转的浅色模式
- Light mode lifts things with shadows. Dark mode lifts things with lighter surfaces and borders. A shadow on is invisible.
#0d0d0d - Dark surfaces get a 1px inset top highlight at 4% white to read as lit from above: .
box-shadow: 0 1px 0 rgba(255,255,255,.04) inset - Saturated colours glow on dark. Desaturate the accent 10–20% and lower its lightness for dark mode.
- Pure white text on pure black vibrates. Lower both.
- Images and illustrations may need a subtle dim () or a dark-mode variant.
opacity: .9 - on the root so form controls, scrollbars and the browser UI match.
color-scheme: dark
- 浅色模式通过阴影抬起元素。深色模式则通过更亮的表面和边框来建立层级。在 上使用阴影是看不见的。
#0d0d0d - 深色表面增加 1px 内嵌顶部高光,并使用 4% 白色,使其看起来像被上方光源照亮:。
box-shadow: 0 1px 0 rgba(255,255,255,.04) inset - 饱和颜色在深色背景上会发光。深色模式下将强调色降低 10–20% 饱和度,并降低其亮度。
- 纯白文字配纯黑背景会产生视觉颤动。不要使用纯白和纯黑,应适当降低二者的对比反差。
- 图片和插图可能需要轻微降低亮度(),或使用深色模式变体。
opacity: .9 - 在 root 上设置 ,使表单控件、滚动条和浏览器 UI 与主题保持一致。
color-scheme: dark
Borders vs shadows
边框与阴影
| Theme | Card / popover edge |
|---|---|
| Dark | |
| Light | |
Shadows are one layer, soft, with negative spread, in the direction of a single light source. Never (a glow), never multiple stacked blurs, never a coloured shadow in product UI.
0 0 20pxBorders are 1px. Not 0.5px (it renders inconsistently), not 2px unless it is a focus ring.
| 主题 | 卡片或浮层边缘 |
|---|---|
| 深色 | |
| 浅色 | |
阴影只有一层,应保持柔和、采用负扩散,并朝向单一光源的方向。不要使用 (光晕),不要叠加多层模糊,也不要在产品 UI 中使用彩色阴影。
0 0 20px边框为 1px。不要使用 0.5px(渲染不一致),除非是焦点环,否则也不要使用 2px。
Alpha
Alpha
Overlays, hover fills and dividers on unknown backgrounds use alpha, so they work on any surface:
- Hover fill: foreground at 4–6%
- Pressed fill: foreground at 8–10%
- Divider: foreground at 8–12%
- Backdrop behind dialogs: page colour at 60–70%, with at most
backdrop-filter: blur(4px) - Glass surfaces () are expensive; one per screen, never animated, never nested
backdrop-filter
在未知背景上,叠加层、悬停填充和分隔线使用 Alpha,以便适配任何表面:
- 悬停填充:前景色的 4–6%
- 按下填充:前景色的 8–10%
- 分隔线:前景色的 8–12%
- 对话框背后的遮罩:页面颜色的 60–70%,并最多使用
backdrop-filter: blur(4px) - Glass 表面()的渲染成本较高;每个屏幕最多使用一个,不要制作动画,也不要相互嵌套
backdrop-filter
The accent
强调色
One hue. It goes on the primary button, the focus ring if the neutral ring is too quiet, links in running text, and the active state of a control. It does not go on icons, headings, borders, backgrounds or badges.
If everything is the accent, nothing is. Count the accent-coloured elements on a screen; if it is more than three, remove some.
Status colours are muted (a green at ~55% chroma, not a traffic light) and always paired with a word or icon. Colour is never the only signal.
只使用一种色相。它用于主按钮、当中性色焦点环过于低调时替代焦点环、正文中正在阅读的链接,以及控件的激活状态。不会用于图标、标题、边框、背景或徽章。
如果所有元素都是强调色,就等于没有强调色。统计屏幕中使用强调色的元素;如果超过三个,就移除其中一些。
状态色应保持克制(绿色的彩度约为 55%,而不是交通灯色),并且始终搭配文字或图标。颜色绝不能成为唯一的信号。
Contrast
对比度
- Body and secondary text: 4.5:1 against its surface.
- Large text (18px+ or 14px+ bold) and UI boundaries (icons, borders on inputs): 3:1.
- Tertiary text is allowed to sit at 3:1 only when it is not needed to use the interface.
- Faint () is decoration. Never put information in it.
--fg-4 - Check both themes. A grey that passes on white will fail on .
#0d0d0d
Placeholders count as text if they carry the only label; then they must pass. Better: always have a visible label.
- 正文和次要文字:与其表面对比度达到 4.5:1。
- 大号文字(18px+ 或 14px+ 粗体)和 UI 边界(图标、输入框边框):达到 3:1。
- 三级文字仅在不影响界面使用的情况下,才可降至 3:1。
- 微弱色()仅用于装饰,绝不能承载信息。
--fg-4 - 两种主题都要检查。在白色上通过的灰色,在 上就会失败。
#0d0d0d
如果占位文本(placeholder)是唯一的标签,就应按文字处理,因此必须通过对比度要求。更好的做法是始终提供可见标签。
Gradients, noise, texture
渐变、噪点与纹理
Product UI: none, or one very quiet radial behind a hero region at 2% opacity to give the page a light source. Marketing: allowed, and still one.
Noise or dither over a gradient hides banding and adds material; keep it at 3–6% opacity and tile a small SVG rather than a large PNG.
A backdrop pattern (grid, ticks, dots) at the level sits behind the frame and stops at its edge; it never runs under content.
--line产品 UI:通常不使用;若要使用,仅可在 hero 区域背后放置一个非常克制的径向渐变,以 2% 不透明度营造页面受光感。营销页面:可以使用,但仍然只保留一个。
在渐变上叠加噪点或抖动可以隐藏色带并增加材质感;不透明度保持在 3–6%,并平铺小型 SVG,而不是大型 PNG。
背景图案(网格、刻度、圆点)使用 级别,并位于框架后方且在框架边缘处停止;绝不能延伸到内容下方。
--lineSelection, focus, scrollbars
选区、焦点与滚动条
css
::selection { background: var(--fg); color: var(--frame); }
:focus-visible { outline: 1px solid var(--fg-2); outline-offset: 2px; }
* { scrollbar-width: thin; scrollbar-color: var(--line-2) transparent; }These three lines are the difference between a themed app and a page with a theme applied to it.
css
::selection { background: var(--fg); color: var(--frame); }
:focus-visible { outline: 1px solid var(--fg-2); outline-offset: 2px; }
* { scrollbar-width: thin; scrollbar-color: var(--line-2) transparent; }这三行代码,是真正具有主题感的应用与仅套用了主题的页面之间的区别。
Theme switching
主题切换
- Decide the theme before first paint with an inline script reading storage and . A flash of the wrong theme is a bug.
prefers-color-scheme - Store the choice; respect the system when there is no choice.
- Transition on the root over ~400ms so the switch is not a strobe. Or use a View Transition to reveal the new theme from the toggle; either way, under reduced motion it is instant.
background-color - Both themes are first-class. If the team only ever looks at dark, light will rot, and vice versa. Review in both.
- 在首次绘制前,使用内联脚本读取存储和 ,提前确定主题。错误主题闪现属于 bug。
prefers-color-scheme - 保存用户的选择;用户未选择时遵循系统设置。
- 在 root 上用约 400ms 过渡 ,避免切换时产生频闪。也可以使用 View Transition,从切换按钮处揭示新主题;无论采用哪种方式,在减少动态效果(reduced motion)模式下都应立即完成切换。
background-color - 两种主题同等重要。如果团队只查看深色主题,浅色主题就会逐渐失修,反之亦然。两种模式都要审查。
Marks of a cheap surface
表面显得廉价的迹象
| Sign | Fix |
|---|---|
| |
| Cards with border and shadow and a different background | one of the three, two at most |
Glow shadows ( | soft directional shadow or none |
| Every card a different tint | one surface token |
| Accent on icons and headings | neutrals; accent on the one action |
| Grey text at 2.5:1 "because it looks lighter" | |
| Gradient buttons | flat fill, border-only secondary, ghost tertiary |
| Dark mode with the same shadows as light | borders and inset highlights |
| 迹象 | 修正方式 |
|---|---|
| |
| 同时具有边框、阴影和不同背景的卡片 | 三者择一,最多使用两种 |
发光阴影( | 柔和的方向性阴影,或不使用阴影 |
| 每张卡片使用不同色调 | 使用一个表面 Token |
| 图标和标题使用强调色 | 使用中性色;仅在唯一的主要操作上使用强调色 |
| 2.5:1 的灰色文字“因为看起来更浅” | 使用符合对比度要求的 |
| 渐变按钮 | 主按钮使用纯色填充,次按钮仅用边框,第三级按钮使用 ghost 样式 |
| 深色模式沿用浅色模式的阴影 | 使用边框和内嵌高光 |
From the catalogue
资源目录
- Radix Colors — scales with a defined step for each role, in both themes.
- Open Props — sensible defaults for shadows and neutrals.
- Realtime Colors — see a palette on a real layout before committing.
- Contrast checkers — check it before someone else does.
- Dark Mode Design — sites that got dark right.
- fffuel — SVG noise and grain generators.
- More at stealth.pm/assets.
- Radix Colors — 在两种主题下都为每种角色提供定义明确的色阶。
- Open Props — 为阴影和中性色提供合理默认值。
- Realtime Colors — 在实际布局中查看色板,再决定是否采用。
- Contrast checkers — 先检查对比度,别等别人发现。
- Dark Mode Design — 将深色模式处理得好的网站。
- fffuel — SVG 噪点和颗粒生成器。
- 更多内容见 stealth.pm/assets。