Skip to content

主题与设计变量 ​

设计变量由 Ptengine 的设计规范导出,包含在 @ptengine/ui 的全局样式中,不是手写的。

切换明暗 ​

html
<html data-pt-theme="dark"></html>

不写 data-pt-theme 时跟随系统。局部主题孤岛也支持,可以任意嵌套:

html
<div class="pt-dark">这一块永远是暗色</div>

切主题只是换一组变量的值,不重新加载样式表,也不会让组件重新渲染。

变量分两层 ​

原语层是唯一存色值的一层,与主题无关:

css
--pt-green-500-rgb: 37 172 0;

语义层引用原语,透明度只在这一层表达;明暗差异也全在这一层:

css
--pt-text-standard-rgb: var(--pt-black-rgb) / 0.7;
--pt-text-standard: rgb(var(--pt-text-standard-rgb));

每个语义 token 成对导出两种形态:

  • 包装形态 var(--pt-text-standard) —— 开箱即用,绝大多数场景用这个
  • 通道形态 rgb(var(--pt-text-standard-rgb) / 0.5) —— 需要再叠一层透明度时用

通道形态不能叠两次 alpha

本身已经带 alpha 的档位(如 --pt-text-standard 是黑 70%)再叠透明度会产生 非法的双 alpha。带透明度的档位可在设计变量表中查看。

覆盖变量 ​

改一个变量会影响它下面的整棵子树:

css
:root {
  --pt-brand: rgb(0 120 255);
  --pt-radius: 6px;
}

组件只引用语义层,所以覆盖语义变量一定生效;直接改原语则会波及所有引用它的语义档位。

定制单个组件 ​

Shadow DOM 默认开启,定制点是显式开放的:

css
/* 组件级 CSS 变量 */
pt-button {
  --pt-button-radius: 999px;
}

/* ::part() 改内部元素 */
pt-modal::part(panel) {
  max-width: 720px;
}

每个组件开放了哪些变量和 part,见组件页的 API 表。

为什么不直接开放内部选择器

影子树的内部结构不在兼容承诺范围内 —— 今天的 .base > .content 明天可能就变了。 ::part() 与 CSS 变量是显式的契约,改它们才算 breaking change。

在 JS 里取色值 ​

页面加载 @ptengine/ui 的全局样式后,可从元素的计算样式读取变量:

ts
getComputedStyle(document.documentElement).getPropertyValue('--pt-text-standard').trim();

交互状态映射 ​

同一种状态在不同类的组件里不一定走同一条路径:hover 有状态层、描边变化、行背景三种写法,禁用有 整体半透明与换禁用档 token 两种写法。下表按组件分列 packages/ui 与 packages/widgets 里的实际实现, 新组件照同类组件的那一行写,评审时也按这一行比对。表里只列了最早一批、写法有代表性的组件,不是全量 —— 后来的组件(切换按钮、单选、滑块、标签页、菜单与命令项、组合框等)按上面的归类沿用同一套写法,细节以各自 CSS 为准。

状态组件表达方式token
悬停 / 按下pt-button(default、link 以外的变体)、pt-switch 轨道状态层:在原样式上叠一层中性色,不替换背景;按下换更深一档--pt-interaction-hover / --pt-interaction-press
深色表面上的悬停 / 按下pt-button variant="default"反转状态层--pt-interaction-reversal-hover / --pt-interaction-reversal-press
悬停pt-button 的 ghost-destructive / secondary-destructive叠状态层的同时,悬停 / 按下时内容变危险色--pt-text-danger-standard
悬停pt-button variant="link"只出下划线,不叠层—
悬停输入类:pt-input、pt-textarea、pt-select只换描边色,不叠层--pt-border-neutral
悬停pt-checkbox方框描边加深--pt-border-strong
悬停pt-table 行直接把行背景设成 hover 色;没有按下态--pt-interaction-hover
当前项(键盘 / 父级指定)pt-option(父级设 [active])行背景设成 hover 色。pt-option 的高亮只来自 [active]:指针悬停、按下都没有样式,pt-select 打开时把它定位到已选项,之后只随键盘移动--pt-interaction-hover
悬停 / 按下(可选)列表项加 .list-item-hoverable(utils/list-item.css,目前没有组件启用)行背景 hover 色,按下换 press 色--pt-interaction-hover / --pt-interaction-press
键盘焦点pt-button、pt-checkbox、pt-switch(.focus-ring):focus-visible 焦点环:2px 留白 + 2px 环(box-shadow,不用 outline)--pt-interaction-focus
键盘焦点输入类:pt-input、pt-textarea(:has(:focus-visible))、pt-select 触发器(:focus-visible),都是 .focus-ring-inset贴边 1px 焦点环,不留白 —— 控件本身有描边--pt-interaction-focus
勾选 / 开启pt-checkbox 方框、pt-switch 轨道中性强调色填充,不用品牌色--pt-bg-strong
禁用pt-button、pt-switch、pt-input、pt-textarea、pt-select整体 opacity: 0.5,不换色;不生成状态层,hover 描边也不变—
禁用pt-checkbox方框与标签换禁用档,不降透明度--pt-bg-disable / --pt-text-disabled
禁用pt-option文字换禁用档,行不响应指针--pt-text-disabled
校验失败(invalid)pt-input、pt-textarea、pt-select、pt-checkbox描边换危险色(hover 时保持)--pt-bg-danger-standard

判断新组件该走哪一行:有实底或透明底、整块可点的(按钮、开关)用状态层;靠描边界定范围的(输入框、 下拉触发器)只动描边,叠层会把输入区整块染灰;本身没有底色的行直接换背景。禁用时现有的整块控件都用 opacity,复选框与列表行则逐个部件换禁用档 token —— 两种写法目前并存,新组件跟随最接近的同类组件。

状态层:hover / pressed ​

组件内部用两个类实现(packages/ui/src/utils/state-layer.css):

  • state-layer:::after 叠一层 --pt-interaction-hover(亮色下黑 8%,暗色下白 8%), :active 时换成 --pt-interaction-press(10%)。实色按钮 hover 变深、透明表面 hover 出现浅灰,一条规则通用。
  • state-layer-inverse:深色表面用,叠的是 --pt-interaction-reversal-*(亮色下白 8% / 10%; 暗色下该表面反白,它自动变成黑 8% / 10%)。pt-button 的 default 变体(--pt-bg-strong 底)用的就是它, 其余非链接变体用 state-layer。

叠层用 opacity 过渡,时长是 --pt-duration-fast(150ms);圆角继承宿主;禁用态(:disabled 或 aria-disabled="true")不生成叠层。有描边的宿主声明 --pt-state-layer-border: 1px,让叠层外扩到边框外沿。

业务代码可以按上面的 token 自己写 ::after。@ptengine/ui 的全局样式表不导出这两个类。

选中:bg-selected ​

「选中」是持续状态,和一闪而过的 hover 分开:用 --pt-bg-selected 作为实底。 目前用在 AI 输入框引用面板里已选的条目上。

  • 亮色下 --pt-bg-selected 与 --pt-interaction-hover 同为黑 8%,不要因为色值相同就混用 —— 语义不同,设计规范调整其中一个时另一个不会跟着变。
  • 单选列表(pt-option)的选中只用行尾勾选标记表达,不铺底色、文字不变色也不加粗 (见排版 · 字重)。

彩色按功能发放 ​

kit 的默认强调色是中性色 --pt-bg-strong(亮色下近黑,暗色下反白):pt-button 的默认变体、勾选的复选框、打开的开关都是它。 彩色不做装饰,每一种颜色只对应一种功能:

颜色功能语义 token组件里的用法
品牌色需要突出品牌的主操作--pt-brand、*-brand-*仅 pt-button variant="brand"
危险(红)破坏性操作、校验失败、错误信息*-danger-*destructive 系按钮;输入类组件的 invalid 描边;pt-badge variant="destructive"
成功(绿)成功、正向结果--pt-text-success、--pt-border-success、--pt-bg-positive-*pt-badge variant="success"
警告(橙)需要注意、但不阻断*-warning-*pt-badge variant="warning"
信息(蓝)中性提示*-information-*pt-badge variant="information"
AI标识 AI 生成或与 AI 相关的内容*-ai-*pt-badge variant="ai";AI 输入框的引用标签
链接文字链接--pt-link-blue、--pt-link-green组件未使用(pt-button variant="link" 用正文色 + 下划线)
图表区分数据系列,不挪作状态色--pt-chart-0 … --pt-chart-5组件未使用

成功色的背景档叫 positive

文字和描边是 --pt-text-success / --pt-border-success,背景却是 --pt-bg-positive-*,没有 --pt-bg-success-*。 这是设计规范导出时的命名,照抄时注意。

每种功能色成套提供:text-<色> 与更浅一档的 text-<色>-standard,bg-<色>-standard / -subtle / -subtler (由强到弱的底色),以及 border-<色>。徽标这类「浅底 + 深字」用 bg-*-subtler 配 text-<色>; 实底按钮用 bg-*-standard 配 --pt-text-stable(白字,明暗主题下都不反转)。完整清单见设计变量表。

相关 ​

Apache-2.0 协议开源