ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Yeti 主题定制指南:用 Token 而非 CSS 驱动整套视觉体系

Yeti 主题定制指南:用 Token 而非 CSS 驱动整套视觉体系 Yeti 主题定制指南用 Token 而非 CSS 驱动整套视觉体系【免费下载链接】yetiA CSS-first, native, zero-build layout and styling framework for web designers.项目地址: https://gitcode.com/gh_mirrors/fo/yetiYeti 是一个 CSS-first、原生、零构建的布局与样式框架它的主题系统建立在一个核心原则上通过设置设计 Tokencustom properties来换肤而不是直接编辑组件的 CSS。读完本文你将掌握--yeti-group-name命名体系、hue→color 的派生机制、流体缩放比例尺、明暗双主题的切换方式以及如何编写一个能通过官方校验器、可随框架升级而长期存活的自定义主题文件。全文以 docs/guides/theming.md 为主线结合 src/tokens/、src/themes/ 与测试代码给出源码级佐证。主题化的基本姿势设置 Token而非修改 CSSYeti 的所有公开 Token 都是命名为--yeti-group-name的自定义属性完整清单在 Tokens 参考页。你只需要在自己的样式表中、在加载 yeti.css 之后把 Token 设置在:root上所有读取它们的组件、布局和排版都会立刻跟随link relstylesheet href/css/yeti.css style :root { --yeti-hue-primary: 160; --yeti-ratio: 1.25; --yeti-font-sans: Inter, system-ui, sans-serif; } /style这段代码同时改了三样东西品牌色把主色 hue 从默认的 250 蓝色改为 160 青绿、整个字阶比例、正文字体。注意 Token 的组group概念——hue、ratio、font是三个不同的组这正对应 Token 目录 src/tokens/ 中按组划分的文件color.css色、scale.css比例尺、space.css间距与圆角、components.css组件级覆盖等。Hues in, colors out色相输入颜色输出Yeti 颜色体系的第一条设计原则是你设置的是色相hue不是颜色color。颜色输入共有七个Token默认值说明--yeti-hue-primary250品牌色相默认蓝色所有 primary 色系由此派生--yeti-hue-secondary300次要色相紫色--yeti-hue-success145成功色相绿色--yeti-hue-warning80警示色相琥珀色--yeti-hue-alert25危险色相红色--yeti-hue-neutralvar(--yeti-hue-primary)中性色相微妙地染着页面、文字与边框默认跟随主色--yeti-chroma0.15强调色的饱和度见下文 oklch 解读从这些输入出发Yeti 在运行时为每个色相派生出一整套颜色角色color roles--yeti-color-primary及其-subtle、-soft、-strong、-text变体用于放置在其上的--yeti-on-primary以及页面级角色--yeti-color-surface、--yeti-color-text、--yeti-color-border和它们的变体。这也是为什么 hue 和 color 使用不同的组名——hue 是你设置的输入color 是 Yeti 在明暗两种模式下推导出的输出。源码 src/tokens/color.css 完整揭示了这套派生机制每个颜色角色通过一条明度阶梯lightness ladder从色相计算而来阶梯在明/暗两套取值之间用light-dark()成对声明Ladder (lightness light / dark): subtle 0.95/0.25, soft 0.85/0.35, base 0.52/0.70, strong 0.42/0.80, text 0.35/0.85.具体到 primary 一列src/tokens/color.css--yeti-color-primary-subtle: light-dark(oklch(var(--_yeti-l-subtle-light) var(--_yeti-c-subtle) var(--yeti-hue-primary)), oklch(var(--_yeti-l-subtle-dark) var(--_yeti-c-subtle) var(--yeti-hue-primary))); --yeti-color-primary: light-dark(oklch(var(--_yeti-l-base-light) var(--yeti-chroma) var(--yeti-hue-primary)), oklch(var(--_yeti-l-base-dark) var(--yeti-chroma) var(--yeti-hue-primary))); --yeti-on-primary: light-dark(var(--_yeti-on-light), var(--_yeti-on-dark));内部还按角色对色度做了缩放color.csssubtle只用chroma × 0.4、soft用chroma × 0.7、text用chroma × 0.8而中性色的色度被压到0.02——所以背景/边框/正文这类中性角色几乎不带彩色页面看起来干净。每一条派生规则旁边的注释给出了浅色模式下的十六进制等价色如 primary 的#006bbb方便你对照已经熟悉的色值。另外property把 hue 与 chroma 注册为number类型color.css这样一旦误传了颜色名等非法值只会回退到初始值而不会把整条派生链全部无效化。直接覆盖派生颜色推导是默认行为但你随时可以覆盖任何派生颜色。--yeti-color-primary: #0a7;会赢过针对这一个角色的推导而所有读取它的角色比如焦点环--yeti-color-focus其默认值就是var(--yeti-color-primary)见 tokens.json 中 color 组也会跟着变化。在哪里设置 Token:root与任意元素有两个位置规则需要记住Hue、chroma 与比例尺输入只在:root上生效。因为 Yeti 在根元素上计算所有派生 Tokencolor.css 与 scale.css 的全部推导都在:root内完成你在某个局部元素上改--yeti-hue-primary无法触发重算。强制配色方案color-scheme与覆盖派生 Token如--yeti-color-primary可以在任意元素上生效。所以想让某个区块使用不同的强调色正确做法是把它的派生颜色设在该区块上而不是设它的 hue.section-accent { --yeti-color-primary: #0a7; --yeti-color-primary-strong: #087; --yeti-color-focus: #0a7; }读懂 oklch 值Yeti 把所有颜色写作oklch(lightness chroma hue)这是现代 CSS Color 4 的感知均匀色彩空间。三个分量的含义Lightness明度取值 0黑到 1白。与 hex 或 HSL 不同oklch 的明度在视觉上是均匀分布的——两个明度相同的颜色无论色相如何看起来一样亮。Chroma色度/饱和度0 是灰色约 0.15 是一个清晰但不张扬的强调色超过 0.2 后某些色相在普通屏幕上无法显示浏览器会把它拉回可显示范围。因此默认--yeti-chroma: 0.15主题若想更克制可以降到0.05如 sharp 主题想更鲜艳则要留意高色度色相的裁切风险。Hue色相一个角度25红、80琥珀、145绿、250蓝、300紫。每个颜色 Token 在yeti.css注释里给出的 hex 等价色是浅色模式下的值可用作对照基准。Light and dark明暗双主题随访客偏好自动切换:root上声明了color-scheme: light dark见 color.css因此 Yeti 会跟随访客的系统偏好而每个颜色 Token 只需用light-dark()写一次明暗两套值就同时生效——这也正是上一节阶梯值成对出现的原因两个方案互为镜像。要为整页或单个面板强制某一种配色只需在该元素上设置color-scheme: light或color-scheme: dark其内部所有颜色会整体翻转。测试 test/browser/themes.spec.js 专门验证了这一点soft 主题在emulateMedia({ colorScheme: light })与dark两种模式下渲染出的 box-shadow 颜色确实不同。The scale类型与空间共享一条几何比例尺Yeti 的字号和间距共用一条几何比例尺geometric scale由两个旋钮覆盖大多数需求:root { --yeti-base: 1.0625rem; /* body size, every viewport */ --yeti-ratio: 1.25; /* each step is 1.25 times the last */ }--yeti-base正文基准字号--yeti-ratio相邻两级的倍率。这两个值默认都不声明declared: false见 tokens.json一旦设置它们会同时钉住各自那一对的窄屏/宽屏端点——源码里体现为--yeti-base-min: var(--yeti-base, 1rem)这类写法scale.css。流体缩放六个端点 Token什么都不动的情况下两者都是流体的--yeti-base从--yeti-base-min默认1rem在--yeti-viewport-min默认320px到--yeti-base-max默认1.125rem在--yeti-viewport-max默认1280px之间线性增长--yeti-ratio同样从--yeti-ratio-min默认1.2小三度流到--yeti-ratio-max默认1.333纯四度。结果就是宽屏上标题比正文张开得更明显。:root { --yeti-base-min: 1rem; --yeti-base-max: 1.125rem; --yeti-ratio-min: 1.2; --yeti-ratio-max: 1.333; --yeti-viewport-min: 320px; --yeti-viewport-max: 1280px; }设置上面这六个-min/-max/-viewport-Token 可以塑形流体区间设置--yeti-base与--yeti-ratio这两个旋钮则会把它关掉钉死为单一值。实现上src/tokens/scale.css 用一个归一化系数--_yeti-t表示视口从窄到宽的进度0 到 1有趣的是它用tan(atan2(a, b))来在calc()中实现两个长度的除法——这是 Baseline 2025 兼容底线上唯一可行的纯 CSS 做法。随后--_yeti-step-0即 base每一级乘以一次 ratiopow()计算幂得到step -2到step 5的完整阶梯。一致的命名space、text、radius 同一套名字尺寸名统一为xs sm md lg xl 2xl 3xl其中md是基准级。同一名字在间距、字号、圆角上代表同一级--yeti-space-lg、--yeti-text-lg、--yeti-radius-lg都是step 1或圆角的1 × base。对应关系如下全部默认值见 tokens.json 与 space.css间距--yeti-space-xs…3xl依次映射step -2到step 4--yeti-space-md等于正文大小字号--yeti-text-xs…3xl同样映射step -2到step 4外加--yeti-text-displaystep 5比任何标题都大圆角--yeti-radius-sm为0.25 × base输入框、代码、徽章--yeti-radius-md为0.5 × base卡片、对话框--yeti-radius-lg为1 × basehero 面板--yeti-radius-full为9999px胶囊/圆形。每个 space Token 还有一个-static孪生兄弟如--yeti-space-lg-static值是窄屏端点的静态值用于那些绝不能随视口缩放的极少数间隙。静态值与宽屏端点值同样来自 scale.css 里的-static/-max阶梯scale.css。字体不内置任何 Web FontYeti 不随包分发任何 Web 字体。--yeti-font-sans与--yeti-font-mono默认分别是system-ui, sans-serif与ui-monospace, monospace系统字体栈见 tokens.json 的 font 组。换用你自己的字体只需设置 Token字体文件的加载方式link、font-face或任何你偏好的方式完全由你决定:root { --yeti-font-sans: Inter, system-ui, sans-serif; --yeti-font-mono: JetBrains Mono, ui-monospace, monospace; }Make a theme把 Token 集合固化成独立主题文件前面所有操作都是在style块里内联设置 Token。一个主题就是同一个思路的产物化一份只包含:root上 Token 值的样式表加载在yeti.css之后让它的值胜出link relstylesheet href/css/yeti.css link relstylesheet href/css/themes/soft.css主题文件的纪律非常严格——只有:root选择器可选地按media (prefers-color-scheme: …)拆分用于只在某一配色方案下生效的值只有--yeti-*公开 Token 属性。官方校验器validateThemes见 test/tools/validate.test.js 的测试用例会拒绝设置类或元素选择器的主题例如:root { --yeti-button-radius: 999px; } .button { color: red; }报错 themes may only set --yeti-* tokens on :root (found .button)设置未被识别的 Token例如--yeti-nope: 1报错 theme sets --yeti-nope, which is not a public token设置非--yeti-*的属性如color: red嵌套的media、无块体的裸layer语句等畸形结构validate.test.js。也就是说主题是数据token 值不是代码规则集这正是它可移植的原因——它能原封不动地跨过 Yeti 的版本升级而存活。组件级皮肤 Token除了 hue、chroma、比例尺之外每个组件还会发布少量属于自己的 Token 作为皮肤表面--yeti-button-radius、--yeti-card-padding、--yeti-badge-radius等等。完整的组件级清单见 Tokens 参考页其默认值都定义在 src/tokens/components.css 中并且每个默认值都指向某个全局 Token--yeti-button-radius: var(--yeti-radius-md); --yeti-button-weight: var(--yeti-weight-strong); --yeti-button-padding: 2; /* 内边距 该尺寸 space 步的 2 倍 */ --yeti-badge-radius: var(--yeti-radius-full); /* 默认就是胶囊形 */ --yeti-badge-weight: var(--yeti-weight-strong); --yeti-card-radius: var(--yeti-radius-md); --yeti-card-padding: var(--yeti-space-md); --yeti-card-gap: var(--yeti-space-sm); --yeti-card-border: var(--yeti-color-border); --yeti-card-surface: var(--yeti-color-surface-raised); --yeti-field-gap: var(--yeti-space-xs); --yeti-table-stripe: var(--yeti-color-surface-sunken); --yeti-table-border: var(--yeti-color-border);这种组件 Token 默认引用全局 Token的设计意味着改一个全局值如--yeti-radius-md所有引用它的组件按钮、卡片、输入框会联动而主题只需覆盖个别组件 Token如把按钮改成胶囊即可不必触碰组件 CSS。一个小主题改变很多一个很小的主题就能带来肉眼可见的整体变化——换掉强调色、打开圆角、把按钮变成胶囊:root { --yeti-hue-primary: 30; --yeti-radius-md: 1rem; --yeti-radius-lg: 1.5rem; --yeti-button-radius: var(--yeti-radius-full); }由于--yeti-hue-primary: 30会同时重算 primary 的全部派生角色而--yeti-radius-*会被按钮、卡片、徽章等组件 Token 引用这四行就完成了换色 改圆角体系 按钮胶囊化三件事。开箱即用的两个主题soft 与 sharpYeti 在dist/themes/中随包发布两个主题文件作为范本源码就在 src/themes/soft.css 与 src/themes/sharp.css。soft圆润、温暖、宽敞——胶囊按钮、大圆角卡片并把 chroma 从默认 0.15 降到 0.12:root { --yeti-hue-primary: 30; --yeti-hue-secondary: 340; --yeti-chroma: 0.12; --yeti-ratio-max: 1.4; --yeti-radius-md: 1rem; --yeti-radius-lg: 1.5rem; --yeti-button-radius: var(--yeti-radius-full); --yeti-button-weight: var(--yeti-weight-normal); --yeti-badge-radius: var(--yeti-radius-full); --yeti-card-radius: var(--yeti-radius-lg); --yeti-card-padding: var(--yeti-space-lg); --yeti-card-border: var(--yeti-color-primary-subtle); --yeti-card-surface: var(--yeti-color-surface-raised); --yeti-shadow-sm: 0 2px 8px var(--yeti-shadow-color); --yeti-control-radius: var(--yeti-radius-md); }sharp方正、等宽、紧凑——零圆角、2px 粗边框、低色度、硬阴影甚至把无衬线字体指向等宽字体栈:root { --yeti-font-sans: var(--yeti-font-mono); --yeti-base-max: 1rem; --yeti-ratio-max: 1.2; --yeti-chroma: 0.05; --yeti-radius-sm: 0; --yeti-radius-md: 0; --yeti-radius-lg: 0; --yeti-radius-full: 0; --yeti-border-width: 2px; --yeti-weight-strong: 700; --yeti-color-border: var(--yeti-color-text); --yeti-control-border: var(--yeti-color-text); --yeti-card-surface: var(--yeti-color-surface); --yeti-shadow-sm: 4px 4px 0 var(--yeti-color-text); --yeti-table-border: var(--yeti-color-text); }浏览器端测试 test/browser/themes.spec.js 对这两个主题做了真实渲染断言soft 下按钮的左上圆角 高度的一半即胶囊形、卡片圆角 20px、且 box-shadow 颜色随配色方案翻转sharp 下按钮圆角为 0、卡片边框为 2px。两个主题都无需任何特殊标记——主题改变的是组件的外观永远不改变你使用组件时该用的元素或属性这是 Yeti 换肤哲学的最后一块拼图外观与语义解耦。小结换肤 设置--yeti-*Token在:root上、加载 yeti.css 之后输入是色相6 个 hue 1 个 chroma输出是颜色每个色相一套-subtle/-soft/-strong/-text阶梯 on-*对比色 页面角色全部用oklchlight-dark()一次声明、双模式生效比例尺由 base/ratio 两个旋钮和六边形-min/-max/-viewport-端点控制space、text、radius 共享同一套xs…3xl命名主题文件是纯 Token 数据受官方校验器约束因此可移植、可升级参照 src/themes/soft.css 与 src/themes/sharp.css 即可快速上手完整的 Token 目录见 docs/tokens.md。【免费下载链接】yetiA CSS-first, native, zero-build layout and styling framework for web designers.项目地址: https://gitcode.com/gh_mirrors/fo/yeti创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表