主题与设计变量
设计变量由 Ptengine 的设计规范导出,包含在 @ptengine/ui 的全局样式中,不是手写的。
切换明暗
<html data-pt-theme="dark"></html>不写 data-pt-theme 时跟随系统。局部主题孤岛也支持,可以任意嵌套:
<div class="pt-dark">这一块永远是暗色</div>切主题只是换一组变量的值,不重新加载样式表,也不会让组件重新渲染。
变量分两层
原语层是唯一存色值的一层,与主题无关:
--pt-green-500-rgb: 37 172 0;语义层引用原语,透明度只在这一层表达;明暗差异也全在这一层:
--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。带透明度的档位可在设计变量表中查看。
覆盖变量
改一个变量会影响它下面的整棵子树:
:root {
--pt-brand: rgb(0 120 255);
--pt-radius: 6px;
}组件只引用语义层,所以覆盖语义变量一定生效;直接改原语则会波及所有引用它的语义档位。
定制单个组件
Shadow DOM 默认开启,定制点是显式开放的:
/* 组件级 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 的全局样式后,可从元素的计算样式读取变量:
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(白字,明暗主题下都不反转)。完整清单见设计变量表。