design-tokens-and-theming

设计 token 系统与主题

YouTeacher 落地页把每一个视觉决策——颜色、字体、间距、圆角、阴影——都收在一个 CSS 自定义属性文件里,src/styles/design-tokens.css,声明在 :root 上。这个文件自称是唯一真相来源(single source of truth),其余样式都从它读取,而不是各自硬编码数值。

Token 层

token 分成几族:

  • 品牌色。 主色是暖橙(--color-brand),配有更浅的 hover 与更深的 active 变体;另有一个板岩蓝的次要色和它的浅色版本。
  • 中性色。 正文、次要文字、反白文字;页面与卡片背景,各自都有独立的深色对应值;边框色;以及一对「赞助/精选」色。
  • 排版。 两个字体族——--font-brand(Rubik,用于标题)与 --font-body(Inter),各带系统回退字体。一套固定的字号阶梯从 --font-size-h1 一路到正文、小正文、caption;三档字重(regular/medium/semibold);一组行高;以及两个负值字间距,用于收紧大标题。
  • 间距。 一套编号阶梯(--space-1--space-20),以像素为单位,从 4px 到 80px。
  • 圆角与阴影。 圆角从小到全圆(一个用于胶囊形的大值),以及三级投影。

如何被消费

src/app/globals.css 先引入 Tailwind,再引入 token 文件,在 :root 上用 token 设定基础的 font-familybackground-colorcolor,做 border-box 重置,并把标题元素(h1h4)映射到字号与字重 token 上——其中最大的两级标题(h1h2)还用上行高与字间距 token。组件则通过 CSS Modules 消费同一批变量:例如 Header.module.css 引用的是 var(--color-bg)var(--space-4)var(--radius-md) 和字体 token,而不是字面值。这正是设计能「一处修改、处处生效」的原因——token 是设计指南与组件之间的接口。

深色模式

主题是分层的,操作系统偏好与用户显式选择都被尊重,且用户选择优先:

  • 自动。 一个 @media (prefers-color-scheme: dark) 块覆盖中性色 token——但它被限定在 :root:not([data-theme="light"]),所以只有在用户没有手动锁定浅色时,跟随系统才生效。
  • 手动。 另一条 :root[data-theme="dark"] 规则无条件设置同样的深色值,因此显式选择深色时,即使在浅色系统下也会胜出。

手动路径依赖根元素上的 data-theme 属性;页头带有一个主题切换控件来驱动它。深色块重定义中性色 token(文字、背景、边框),并把板岩蓝的次要色换成它的浅色值;主品牌橙色和结构性 token(字体、间距、圆角)在两种主题下保持不变。

响应式字号

字号阶梯以「移动优先 + 覆盖」的方式实现:token 文件里的 @media (max-width: 767px) 块缩小标题字号并收紧两个较大的间距档,于是每个读取标题 token 的组件都会一起缩小,无需逐组件写媒体查询。组件仍会在布局需要处加自己的断点——例如页头在 480px 以下收紧内边距与 logo,而 globals.csspointer: coarse 下强制 44px 的最小触控目标。

为什么是这个形状

一个 token 文件、处处读取,意味着改一个颜色或间距只需一处编辑;三路主题规则让应用默认尊重操作系统、又允许用户覆盖;而移动优先的覆盖让一套字号阶梯在各种屏幕上保持一致,而不是把尺寸散落到各个组件里。

about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →

design-tokens-and-theming