Web styling system — the token framework and engineering constraints
Web 样式体系——token 框架与工程约束
The GUI has no designer supply; styles are written by an agent and reviewed. Without a machine-checkable token system and coding rules, colors/radii/motion drift as literals across components, and dark mode grows into conditional branches scattered inside components.
English
Problem
The GUI has no designer supply; styles are written by an agent and reviewed. Without a machine-checkable token system and coding rules, colors/radii/motion drift as literals across components, and dark mode grows into conditional branches scattered inside components.
Decision
| # | Decision | Content |
|---|---|---|
| 1 | Visual baseline = Chat alignment | Every value comes from the Chat front-end survey (brand blue --accent: #3964fe, gray scale, bubble/sidebar geometry, shadow tiers…); deviation is allowed but must be recorded in the web-styling.md deviation table |
| 2 | Two token layers, not three | The baseline repo uses static→alias→specific three layers; at our size this compresses to "a semantic layer holding real values directly (comments cite the base palette source) + a handful of component-specific slots (--bg-sidebar/--bubble-bg)" — two layers, all living in web-ui/src/style/global.css |
| 3 | Font sizes/spacing are not tokenized | Same decision as the baseline repo: font sizes are written in px inside components and always paired with a line height (16/24, 14/22, 12/18); spacing uses multiples of 4; tokenization covers only colors/radii/motion/font stacks/shadows |
| 4 | Borders and interaction states use the opacity scheme | Borders rgba(0,0,0,.04/.1), hover/active rgba(38,49,72,.06/.1) — they hold when layered on any elevation background, no new solid grays |
| 5 | Dark mode happens only in the token table | :root holds light real values + [data-theme='dark'] overrides the same-named variables; component CSS has zero theme selectors; when a non-token value genuinely must vary by theme, use the "CSS variable bridge" (the component defines a local variable, the theme block only overrides the variable) |
Engineering constraints
- CSS Modules + clsx, no component library, no tailwind: each component has a same-named
.module.cssin the same directory; class names are camelCase, single-adjective state classes are attached via clsx; components passclassNamethrough. composesis banned;:globalonly pierces third-party/cross-package class names and never defines new global classes; global utility classes live only in global.css and stay in the single digits (currently.scrollable).- PostCSS plugins are currently zero (vite has no postcss config; flat CSS suffices — adopting nested/custom-media requires recording it in web-styling.md first); CSS Modules type declarations use the wildcard declare in
css-modules.d.ts(re-evaluate typed-css-modules per-file generation past 20 components). - Dynamic styles go through the CSS variable bridge: JS writes only variables (
style={{'--x': v}}), rules stay in CSS; assembling style objects in TSX for theme/state branches is banned. - Transitions are always
var(--dur*) var(--ease)and only transition opacity/transform/background-color/shadow; scroll containers uniformly use.scrollable(writing::-webkit-scrollbarinside components is banned).
The execution shape for agents
The spec is maintained as a review checklist (web-styling.md §3, 12 items): each item is a decidable "see X, reject" — not a style suggestion — and writing styles and reviewing styles share the same table.
Entry points for common tasks (operational checklists):
- Styling a new component: same-named
.module.cssin the same directory, self-check against web-styling.md §3 item by item; colors/radii/motion reference only §1 tokens. - Adding a token: first add a row to the web-styling.md §1 table (light value + dark column + base palette source comment) → update both the global.css
:rootand[data-theme='dark']blocks → only then reference it in a component. - Deviating from a visual-baseline constant (the geometry/shadow values of web-styling.md §2): record a row in the §5 deviation table first (date/item/reason), then land the code.
- A non-token value that must vary by theme (gradient endpoints and the like): the component defines a local CSS variable and the theme block only overrides the variable (the variable bridge); component CSS keeps zero
[data-theme]selectors.
Division of labor with web-styling.md
| Content | Home |
|---|---|
| The five framework rules, engineering constraints, why two layers / why font sizes are not tokenized | This RFC (changing it = a new superseding RFC) |
| Per-token authoritative values (dark included), visual-baseline constants (sidebar/bubble/session-row/input-card geometry), the RPC four-quadrant direction-marker visual vocabulary, the 12 coding rules, the deviation record | web-styling.md (living document, evolves with the implementation) |
| Value evidence (deepseekchat file:line) | The survey archive has served its purpose; git history keeps it |
Consequences
Styles converge machine-checkably: colors/radii/motion/shadows reference only the §1 tokens of web-styling.md, dark mode is a single attribute-selector override table, and review runs off the same 12-item checklist the author self-checks against. The cost accepted: font sizes/spacing rely on the paired-line-height and multiples-of-4 disciplines rather than tokens, and any framework change requires a superseding RFC.
Alternatives considered
| Rejected | One-line reason |
|---|---|
| Tokenizing font sizes/spacing | The baseline repo demonstrates convergence without it (the paired-line-height discipline substitutes); a bloated token table dilutes the authority of the color tokens |
Dark mode via prefers-color-scheme or in-component branches | Attribute-selector whole-table override keeps components oblivious; system preference can be layered onto the toggle later without touching the token mechanism |
中文
Problem
GUI 无设计师供给,样式由 agent 编写并 review;没有一套机器可检查的 token 体系与编码规范,颜色/圆角/动效会在组件间字面量漂移,暗色主题会长成组件内散落的条件分支。
Decision(框架五条)
| # | 决策 | 内容 |
|---|---|---|
| 1 | 视觉基线 = Chat 对齐 | 取值全部来自对 Chat 前端的调研(品牌蓝 --accent: #3964fe、灰阶、气泡/侧边栏几何、阴影分级……);允许偏离但须在 web-styling.md 偏离表记录 |
| 2 | token 两层不三层 | 基线仓是 static→alias→specific 三层;我们体量下压成「语义层直接持实值(注释标 base 色板出处)+ 极少数组件专属槽位(--bg-sidebar/--bubble-bg)」两层,全部住 web-ui/src/style/global.css |
| 3 | 字号/间距不 token 化 | 基线仓同款决策:字号在组件里写 px 且成对写行高(16/24、14/22、12/18),间距用 4 的倍数;token 化只覆盖颜色/圆角/动效/字体栈/阴影 |
| 4 | 边框与交互态用透明度制 | 边框 rgba(0,0,0,.04/.1)、hover/active rgba(38,49,72,.06/.1)——叠加在任意层级的背景色上都成立,不新造实色灰 |
| 5 | 暗色只在 token 表做 | :root 亮色实值 + [data-theme='dark'] 覆盖同名变量;组件 CSS 零主题选择器;确需按主题换非 token 值时用「CSS 变量桥」(组件定义局部变量、主题块只覆写变量) |
工程约束
- CSS Modules + clsx,无组件库、无 tailwind:每组件同目录同名
.module.css;类名 camelCase、状态类单形容词由 clsx 挂载;组件透传className。 - 禁
composes;:global仅穿透第三方/跨包类名,不定义新全局类;全局工具类只住 global.css 且个位数(现状.scrollable)。 - PostCSS 插件现状为零(vite 无 postcss 配置,平铺 CSS 即够用;引入 nested/custom-media 前需先记入 web-styling.md);CSS Modules 类型声明用
css-modules.d.ts通配 declare(组件数超 20 再评估 typed-css-modules 逐文件生成)。 - 动态样式走 CSS 变量桥:JS 只写变量(
style={{'--x': v}}),规则留在 CSS;禁止 TSX 内拼样式对象做主题/状态分支。 - 过渡一律
var(--dur*) var(--ease)且只过渡 opacity/transform/背景色/阴影;滚动容器统一.scrollable(组件内禁写::-webkit-scrollbar)。
给 agent 的执行形态
规范以 review 对照打勾清单形态维护(web-styling.md §3,12 条):每条是可判定的「见 X 即打回」,不是风格建议——写样式与 review 样式共用同一张表。
常见事项的入口(操作清单):
- 写新组件样式:同目录同名
.module.css,对照 web-styling.md §3 逐条自查;颜色/圆角/动效只引 §1 token。 - 加一个 token:先进 web-styling.md §1 表补一行(亮色值+暗色列+base 色板出处注释)→ global.css
:root与[data-theme='dark']两块同步 → 再在组件里引用。 - 偏离视觉基线常数(web-styling.md §2 的几何/阴影值):先在 §5 偏离表记一行(日期/项/理由)再落码。
- 需要按主题变化的非 token 值(渐变端点等):组件定义局部 CSS 变量、主题块只覆写变量(变量桥),组件 CSS 保持零
[data-theme]选择器。
与 web-styling.md 的分工
| 内容 | 归属 |
|---|---|
| 框架五条、工程约束、为何两层/为何不 token 化字号 | 本 RFC(修改框架须由新 RFC 取代本文) |
| token 逐项权威值(含暗色)、视觉基线常数(侧边栏/气泡/会话行/输入卡片几何)、RPC 四象限方向符视觉词汇、编码规范 12 条、偏离记录 | web-styling.md(活文档,随实现演进) |
| 取值证据(deepseekchat file:line) | 调研归档已完成使命,git 历史留档 |
Consequences
样式收敛到机器可检查:颜色/圆角/动效/阴影只引 web-styling.md §1 token,暗色是单一属性选择器覆盖表,review 与自查共用同一张 12 条清单。接受的代价:字号/间距靠成对行高与 4 倍数纪律而非 token;动框架本身须由新 RFC 取代本文。
Alternatives considered
| 放弃项 | 一句话理由 |
|---|---|
| 字号/间距 token 化 | 基线仓实证不 token 化也能收敛(成对写行高纪律替代);token 表膨胀降低颜色 token 的权威性 |
暗色用 prefers-color-scheme 或组件内分支 | 属性选择器整表覆盖让组件零感知;系统偏好可后续在 toggle 层适配,不动 token 机制 |