跳到正文

主题

本页译自英文。 阅读英文页面

状态:已在 crates/zeyn/assets/zeyn.css 中实现。

默认 UI 套件从 :root 上的 CSS 自定义属性读取每一种颜色、字体、尺寸和圆角。组织只需覆盖其中少数几个属性即可改变外观,无需编辑 zeyn.css,也无需编写选择器。

1. 主题如何应用

服务器先提供 zeyn.css,然后在应用有额外样式表时,再提供包含令牌(token)覆盖的样式表。覆盖文件只包含 :root 块:

:root {
  --z-brand: #7a2448;
  --z-font: "Source Sans 3", "Segoe UI", system-ui, sans-serif;
  --z-radius: 8px;
  --z-density: 0.75;
}

覆盖文件的规则:

  • 必须从应用自身的源(origin)提供。内容安全策略(CSP)为 style-src 'self',因此内联 <style> 块和第三方样式表都会被阻止。
  • 字体必须已安装在用户的机器上,或从应用自身的源提供。套件本身不加载任何网络字体。
  • 在 zeyn.css 之后加载,这样相同选择器按顺序决定胜负。
  • 设置第 2 节中的基础令牌。第 3 节中的派生令牌会自动跟随。只有当计算出的值不适合你的品牌时,才覆盖派生令牌。

浅色、深色与应用主题

每个应用的菜单中都有一个主题选择器,位于语言选择器旁边。它提供 System(跟随 prefers-color-scheme)、Light、Dark 以及应用定义的任何主题:

App::new("Billing")
    .theme(Theme::new().brand("#7a2448").radius(6))
    .themes([
        Theme::light(),
        Theme::dark(),
        Theme::named("sand", [("--z-bg", "#f5f1ea"), ("--z-surface", "#fffdf8")]).label(t!("theme.sand")),
    ])
    .default_theme("system")
  • App::theme 是组织的外观,对所有主题都生效。
  • App::themes 列出用户可选择的主题。没有它时,列表为 Light 和 Dark。当两者都在列表中时,会提供 System。Theme::named(...) 以浅色主题为基础构建;.dark_base() 则以深色主题为基础。
  • App::default_theme 是尚未做出选择的用户所使用的主题。未设置时为 system。
  • App::theme_picker(false) 隐藏选择器;所有人都使用默认主题。
  • 只有一个主题会锁定应用:.themes([Theme::named("acme", tokens)]) 或 .theme(Theme::dark())。

令牌在应用启动时检查。App::check(因此 router 和 serve 也会)会报告未知的令牌名称、可能跳出其声明的值(;、花括号、\、<、>、url()、无效的主题名称、未知的默认主题,以及某个主题使第 5 节中任何颜色对低于 WCAG 2.2 AA 的情况。只有 #rgb 和 #rrggbb 值可以检查对比度;其他颜色语法则被信任。

所有主题都放在同一个样式表 /_zeyn/theme.css 中,它在启动时构建一次,并在套件的样式表之后加载。所选主题保存在 zeyn_theme cookie 中;对于已登录且应用配有视图存储(App::datagrid_views)的用户,还会保存在该存储中,因此它会跟随用户到新设备。服务器在 <html> 上渲染主题,所以首次绘制就已正确,无需内联脚本:

标记结果
<html>System:跟随 prefers-color-scheme
<html data-theme="light">始终为浅色
<html data-theme="dark">始终为深色
<html data-theme="light" data-zeyn-theme="sand">应用主题 sand,基于浅色主题

在选择器中切换主题只会改变这些属性和 color-scheme meta,然后调用 POST /_zeyn/theme。不会重新加载页面。

若要在手写的覆盖文件中为深色主题提供它自己的值,请重复套件使用的选择器:

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --z-bg: #0e1012; }
}
:root[data-theme="dark"] { --z-bg: #0e1012; }

密度

--z-density 缩放间距单位。1 为常规,0.75 为紧凑。<html data-density="compact"> 无需样式表即可设置为 0.75。控件和表格行永远不会小于 24 px,因此紧凑模式仍满足 WCAG 2.2 的目标尺寸要求。

虚拟表格的行高由服务器固定(Paging::virtual_rows(px),默认 32 px),因为运行时依据它计算滚动位置。密度不会改变它。

2. 基础令牌

这些是组织预期要设置的令牌。

令牌用途浅色深色
--z-brand唯一的品牌色。主按钮、应用名称条、当前菜单项、聚焦行、焦点环#0f6674相同值;见 --z-accent
--z-on-brand实心 --z-brand 表面上的文字#ffffff#ffffff
--z-font界面字体栈"Segoe UI Variable Text", "Segoe UI", system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, "Noto Sans", sans-serif相同
--z-font-mono代码、API 示例ui-monospace, "Cascadia Mono", "SF Mono", "Segoe UI Mono", Menlo, Consolas, monospace相同
--z-font-size正文0.875rem(14 px)相同
--z-font-size-sm表格单元格、标签、筛选器0.8125rem(13 px)相同
--z-font-size-xs列标题、菜单分组标题、页面信息0.75rem(12 px)相同
--z-radius基础圆角半径,0 到 12 px。所有组件都以它为基准缩放4px相同
--z-density间距倍数,1 为常规,0.75 为紧凑1相同
--z-bg面板后方的页面背景#f4f5f6#121416
--z-surface面板、表格、窗口、输入框#ffffff#1a1d20
--z-surface-2表格表头、斑马纹行、窗口页脚、只读输入框#f7f8f9#202326
--z-hover行、菜单项、按钮的悬停背景#eceef1#2a2e33
--z-border分隔线和面板轮廓#dcdfe3#33383e
--z-border-strong输入框和按钮的轮廓、表头分隔线。相对 --z-surface 必须达到 3:1#7d848d#7a818a
--z-text正文和值#1a1d21#e4e6e9
--z-text-muted标签、表头、次要文字。相对 --z-surface-2 必须达到 4.5:1#5b626b#a2a9b1
--z-danger错误、必填标记、危险行#b3261e#f28b82
--z-warning警告行#8a5300#e6b45a
--z-success成功行#1e6b3a#6fcf8f
--z-info信息提示(toast)#2b5d8a#8ab8e6

3. 派生令牌

由基础令牌通过 color-mix() 和相对颜色语法计算得出。基础令牌变化时它们会自动更新。

令牌用途浅色深色
--z-accent在表面上用作文字或细线的品牌色:链接、当前菜单项、聚焦行的竖条var(--z-brand)oklch(from var(--z-brand) 0.8 c h),即品牌色相配固定的浅色调。不支持相对颜色语法的浏览器使用 color-mix(in oklab, var(--z-brand) 45%, #fff)
--z-primary主按钮填充var(--z-brand)var(--z-accent)
--z-on-primary主按钮文字var(--z-on-brand)#121416
--z-primary-hover主按钮悬停品牌色 86% 混合黑色强调色 84% 混合白色
--z-primary-active主按钮按下品牌色 74% 混合黑色强调色 70% 混合白色
--z-brand-subtle当前菜单项和聚焦表格行背后的淡色强调色以 --z-tint 叠加在 --z-surface 上相同公式
--z-focus焦点环var(--z-accent)var(--z-accent)
--z-danger-subtle、--z-warning-subtle、--z-success-subtle、--z-info-subtle行和消息的背景状态色以 --z-tint 叠加在 --z-surface 上相同公式
--z-tint淡色的强度9%20%
--z-space间距单位calc(4px * var(--z-density))相同
--z-control按钮和输入框高度max(24px, 8 * --z-space)(32 px)相同
--z-row分页和无限滚动表格的行高max(24px, 8 * --z-space)(32 px)相同
--z-gap屏幕各部分之间的间隙3 * --z-space(12 px)相同
--z-radius-sm、--z-radius-lg小型部件(筛选器、代码)、窗口--z-radius 的 0.75x 和 1.5x相同
--z-shadow-2仅用于窗口和 toast。表格行永远没有阴影柔和的双层阴影更深
--z-backdrop模态窗口后方rgb(20 24 28 / 40%)rgb(0 0 0 / 60%)
--z-dir阅读方向的符号,用于没有逻辑属性形式的偏移(标记起始或结束侧的内阴影、滑动变换):calc(2px * var(--z-dir))1,在 dir="rtl" 下为 -1相同

由服务器标记设置的元素级令牌:--z-width(窗口宽度)、--z-flex(列占比)、--z-row-height(虚拟表格行高),以及组件库使用的 --z-stack-gap、--z-split、--z-progress、--z-columns、--z-level 和 --z-pop-x / --z-pop-y(弹出层位置)。

图表系列颜色是 components.css 中的 --z-chart-1 到 --z-chart-6,在深色主题中取值更浅。每一个相对 --z-surface 都达到 3:1。可像覆盖任何基础令牌一样覆盖它们。

4. 选择品牌色

  • --z-brand 在浅色主题中承载白色文字(按钮、应用名称条)。请选择相对 --z-on-brand 至少达到 4.5:1 的颜色。大多数中等到深色的品牌色都能通过。对于黄色这类浅色品牌色,还要把 --z-on-brand 设为深色。
  • 在深色主题中,套件会把品牌色调亮用于文字、焦点环和主按钮,而应用名称条保留实心的品牌色。不同的品牌在两个主题中都会显得经过设计,无需第二种颜色。
  • 让状态色与品牌色保持区分。红色品牌搭配红色错误行会让错误更难被发现;这种情况下,请把 --z-danger 调向另一种红色,或通过覆盖 --z-brand-subtle 让品牌色不出现在表格里。
  • 以第 5 节的表格为目标检查你的颜色。任何低于阈值的颜色对都不符合 WCAG 2.2 AA。

5. 已验证的对比度

在运行的 showcase 中于 Chromium 里测量:每个令牌都被解析为其渲染后的 sRGB 值(包括 color-mix() 和相对颜色),然后计算 WCAG 2.x 对比度。阈值:文字为 4.5:1,UI 组件轮廓和焦点指示器为 3:1。

颜色对浅色深色要求
--z-text 在 --z-bg 上15.4914.774.5
--z-text 在 --z-surface 上16.9113.544.5
--z-text 在 --z-surface-2 上15.9112.634.5
--z-text 在 --z-hover 上14.5510.934.5
--z-text 在 --z-brand-subtle 上(聚焦行)14.799.274.5
--z-text 在 --z-danger-subtle 上14.739.764.5
--z-text 在 --z-warning-subtle 上14.829.314.5
--z-text 在 --z-success-subtle 上14.769.334.5
--z-text-muted 在 --z-bg 上5.657.784.5
--z-text-muted 在 --z-surface 上6.177.134.5
--z-text-muted 在 --z-surface-2 上(表头)5.806.654.5
--z-text-muted 在 --z-hover 上5.315.764.5
--z-text-muted 在 --z-danger-subtle 上(弱化行)5.375.144.5
--z-text-muted 在 --z-warning-subtle 上5.414.904.5
--z-accent 在 --z-surface 上(链接)6.629.334.5
--z-accent 在 --z-bg 上6.0610.184.5
--z-accent 在 --z-brand-subtle 上(当前菜单项)5.796.394.5
--z-on-primary 在 --z-primary 上6.6210.184.5
--z-on-primary 在 --z-primary-hover 上8.8211.264.5
--z-on-primary 在 --z-primary-active 上11.1212.264.5
--z-on-brand 在 --z-brand 上(应用名称条)6.626.624.5
--z-danger 在 --z-surface 上(字段错误)6.547.094.5
--z-danger 在 --z-danger-subtle 上5.695.114.5
--z-warning 在 --z-surface 上6.338.904.5
--z-warning 在 --z-warning-subtle 上5.556.124.5
--z-success 在 --z-surface 上6.528.874.5
--z-info 在 --z-surface 上6.928.134.5
--z-focus 在 --z-surface 上6.629.333
--z-focus 在 --z-bg 上6.0610.183
--z-focus 在 --z-brand-subtle 上5.796.393
--z-border-strong 在 --z-surface 上(输入框轮廓)3.784.303
--z-border-strong 在 --z-surface-2 上3.554.013

--z-border 仅作装饰(用于已带有文字的条目之间的分隔线),不要求达到 3:1。

6. 其他无障碍行为

  • 每个可交互元素都显示 2 px 的 :focus-visible 焦点环,颜色为 --z-focus。表格行把焦点环画在行内部,因此不会被滚动容器裁切。
  • 按钮、输入框、排序表头、筛选框、分页按钮和窗口关闭按钮在任何密度下都至少为 24 x 24 px。
  • prefers-reduced-motion: reduce 会移除 toast 的进入动画和所有过渡。
  • 在 forced-colors 模式下,图标使用 CanvasText,主按钮和聚焦行使用 Highlight,依赖背景淡色的状态(当前菜单项、行状态)则改用轮廓或边框。