主题与深浅色

CSS 变量驱动的完整主题体系,如何做到首屏无闪烁、跨项目复用。

最后更新

Known 组件库、后台应用、营销官网三处共用一套主题约定:CSS 自定义属性 + 单一 data-theme 属性 + prefers-color-scheme 回落 + localStorage 持久化。不共享代码、只共享变量命名。

变量分层

:root {
  /* 品牌 */
  --brand: 靛蓝; --brand2: 亮靛; --brand-ink: 靛深;
  --teal; --violet; --amber; --rose; --green;

  /* 代码面板(两主题共用深色) */
  --code-bg; --code-line; --code-fg;

  /* 字体与圆角 */
  --disp; --sans; --mono; --r; --maxw;
}

:root, [data-theme="light"] {
  /* 瓷白 porcelain */
  --bg; --bg2; --surface; --surface2;
  --ink; --ink2; --muted; --faint;
  --line; --line2; --brand-soft; --chrome;
  --ok-fg; --warn-fg; --no-fg;
  --ok-bg; --warn-bg; --no-bg; --info-bg;
}

[data-theme="dark"] {
  /* 墨蓝 ink-navy;同名变量覆盖 */
}

语义层(--brand --ok-fg --line)与具体值分离,切主题只换值不换类。

首屏无闪烁

App.razor 的 <head> 里有一段 pre-paint 脚本(营销站示例):

<script>
  (function () {
    try {
      var t = localStorage.getItem('known-theme');
      if (!t) t = matchMedia('(prefers-color-scheme:dark)').matches ? 'dark' : 'light';
      document.documentElement.setAttribute('data-theme', t);
    } catch (e) {
      document.documentElement.setAttribute('data-theme', 'light');
    }
  })();
</script>

这段必须早于任何 <link rel=stylesheet>——浏览器拿到 head 就同步执行, CSS 变量按最终主题的 <link> 应用,页面渲染时主题已定。这就是无闪烁的关键。

try/catch 兜的是 localStorage 在隐私模式或某些 WebView 下抛异常的场景; 兜底走 light 保证页面始终有主题、不裸奔。

组件里怎么用

组件 razor 不判断"当前是深色还是浅色",一律写语义变量:

.known-button.primary {
  background: var(--brand);
  color: #fff;
}
.known-card {
  background: var(--surface);
  border: 1px solid var(--line);
}

主题切换是浏览器层的事:CSS 变量值变了、组件自动跟随、组件逻辑无感。

LocalService

Known 框架侧的 LocalService 提供:

  • Info.IsDark — 只读,当前主题。
  • ToggleThemeAsync() — 切换并写入 localStorage('known-theme'),触发 Changed 事件。
  • Changed — 供需要"主题变了要重画 canvas"的场景订阅。

后台应用与 Known 组件里做主题按钮时优先用 LocalService,营销站因为是静态 SSR 只能靠 site.js 里的原生 JS 孤岛自己 toggle data-theme——两种形态共享同一份 known-theme localStorage key, 用户在任一子域切了主题、另一子域打开也保持。

有意不做

  • 主题市场 / 用户自定义色板:Batch 5 之后;当前只有 light / dark 两态。
  • 跟随系统自动切换(不持久化):可选实现;当前只有 localStorage 未写入时才回落系统偏好。

与其他概念

  • 代码面板在两主题下都用 --code-bg(深色),是因为代码本身在深色下阅读体验最好、 这个决定属定案、不随主题翻转。
  • 组件库的状态色(--ok-* --warn-* --no-*)在深浅色下值不同、语义相同; 见 /docs/guides/layout-composition 的实例。
微信公众号二维码 扫码关注公众号
QQ 交流群二维码 扫码加入 QQ 群