@xwink/ui

    Tokens

    组件库的单一事实源是 packages/ui/core/src/styles/tokens.css:纯 :root 自定义属性 + 明暗 / 对比度 / 尺寸维度的重定义块。组件内 var(--ui-*) 不写内联 fallback,全局调整只改这里。

    色彩

    Token默认值用途
    --ui-bg#f9fafb页面底色
    --ui-surface#ffffff卡片 / 控件面
    --ui-surface-muted#f4f4f5次级面(表头 / 行悬停 / 内联代码底)
    --ui-surface-hover#e5e7ebhover / 抬升面
    --ui-fg#1f2937正文
    --ui-fg-strong#111827标题 / 强调文字
    --ui-fg-subtle#4b5563说明性正文(比正文轻、比次要文字重)
    --ui-fg-muted#5b6371次要文字 / placeholder
    --ui-border#d1d5db常规边框
    --ui-border-muted#e5e7eb分隔线 / 弱边框
    --ui-primary#d9a06c主题色(主操作 / 选中态 / 聚焦)
    --ui-primary-fg#ffffff主题色之上的文字
    --ui-secondary#c78d55次级色(与主题色组成品牌强调)
    --ui-secondary-fg#ffffff次级色之上的文字
    --ui-primary-gradient自动生成primary → secondary 的品牌强调渐变
    --ui-danger#dc2626危险语义
    --ui-danger-fg#ffffff危险色之上的文字
    --ui-success#16855a成功语义
    --ui-success-fg#ffffff成功色之上的文字
    --ui-warning#b45309警告语义
    --ui-warning-fg#ffffff警告色之上的文字
    --ui-info#2563eb信息语义
    --ui-info-fg#ffffff信息色之上的文字

    派生值

    跟随 primary / danger 自动换主题,宿主一般不需要单独覆写:

    Token说明
    --ui-ring聚焦环颜色(primary 70% 透明混合)
    --ui-focus-ring聚焦环阴影整体
    --ui-danger-ring危险态聚焦环
    --ui-shadow浮层阴影
    --ui-shadow-primary主题色阴影(gradient 按钮悬停)

    尺寸

    控件尺寸标尺(mini / small / medium(默认) / large / super)以 --ui-size-<size>-<维度> 形式集中定义(高度 / 水平内边距 / 字号 / 圆角四维度),组件只消费下面这组 --ui-control-*(默认 medium 档):

    Token默认值用途
    --ui-radius16px容器级圆角(卡片 / 弹窗 / 面板);紧凑档(mini / small)跟该档控件圆角
    --ui-control-height36px控件统一高度(按钮 = 输入框 = 下拉,天然等高)
    --ui-control-padding-x12px控件水平内边距
    --ui-control-font-size14px控件字号
    --ui-control-radius10px控件级圆角

    五档标尺值见 tokens.css 的 --ui-size-*;data-size="small" 环境与组件 size prop 共用同一套值。

    叠放层级

    按用途预分档,组件只引用 token、不写数字。同一档里后打开的在上面(浮层都 portal 到 body,按 DOM 顺序叠),所以可交互的层共用一档:对话框里打开的菜单、菜单里打开的对话框,两个方向的嵌套都对。

    Token值放什么
    --ui-z-sticky40吸顶顶栏、侧边章节导航
    --ui-z-float60常驻悬浮窗(聊天浮窗)
    --ui-z-layer100可交互层:弹层、菜单、下拉、对话框
    --ui-z-toast200全局提示
    --ui-z-tooltip300悬停提示
    --ui-z-drag400拖放反馈、假光标

    用法:class="z-(--ui-z-layer)"。组件内部的局部层级(表格吸顶表头)不走这里。

    四维适配

    全部通过重定义 token 实现,组件代码零分支。本站「调试面板」可实时切换验证:

    • 主题色:全仓默认品牌只在 tokens.css 定义;强调渐变由 --ui-primary → --ui-secondary 自动生成。确有独立品牌的宿主才在 app 级入口覆写主题色、次级色及各自前景色(见开始使用),不在页面级 style 里写。
    • 明暗:[data-theme="dark"] 下使用 zinc 基调,并把品牌色收敛为低亮度铜棕 #9b6a48 → #7b482f,避免琥珀铜在暗色背景上过度抢眼;html 标属性即启用。
    • 对比度:@media (prefers-contrast: more) 下加深边框,并把 --ui-fg-subtle 与 --ui-fg-muted 并成同一档——本体是媒体查询,不占 html 属性;本站调试面板以 data-contrast="more" 强制预览同一组值。默认档(不开高对比)本身就要达标:四档文字在 --ui-bg / --ui-surface / --ui-surface-muted 上都过 WCAG AA 的 4.5:1,由 scripts/gate/ui-token-contrast.test.mts 钉住。
    • 尺寸:[data-size="small"](全局紧凑)切换控件尺寸组;组件 size prop 做实例级覆盖(mini / small / medium / large / super)。

    换色

    画大面积色块的组件都接 color,换色逻辑全库只有 colorVariables 一份:主色实底与渐变按钮(XButton)、开关(XSwitch)、勾选(XCheckbox / XRadioGroup)、滑条(XSlider)、进度(XProgress)、工具组配方的选中块(XSegmented / XToggle / XToggleGroup)。

    • 取值:primary(缺省,沿用宿主主色)/ danger / success / warning / info 换成对应语义 token,跟着明暗档走;十六进制走品牌色算法(与 useBrandColor 同一套:前景按对比度选、副色降一档),明暗共用。其余 CSS 写法不认、不改写:前景色要在服务端也算得出来。
    • 改写的是组件根上的一族变量:--ui-primary / -fg / --ui-secondary / -fg,外加渐变、焦点环、主色阴影这几个派生值(PRIMARY_DERIVED_VARIABLES)。自定义属性在声明它的元素上求值,:root 上的派生值已按根的主色算好,子树里只改 --ui-primary 它们不跟着变,所以在同一处重新声明。组件内部照旧只画 --ui-primary 一族,没有 color === 'danger' 这类分支。
    • 工具组配方的项缺省灰底(主色留给导航与主按钮),给了 color(含 primary)才换成该颜色的主色版。
    • 小面积的强调(徽标 XBadge 的语义档、链接色、焦点描边)不在此列:它们是语义档位本身,不是「同一块换个颜色」。

    宿主自写有主色实底的组件时同样继承 ColorProps、根上绑 colorVariables(color):

    vue
    <script setup lang="ts">
    import { colorVariables, type ColorProps } from '@xwink/ui'
    
    const props = defineProps<ColorProps>()
    </script>
    
    <template>
      <span :style="colorVariables(props.color)" class="bg-(--ui-primary) text-(--ui-primary-fg)">
        <slot />
      </span>
    </template>
    

    状态过渡

    悬停、按下、选中、聚焦、展开这类状态变化全库用一个默认 TRANSITION(ui-transition duration-150 ease-out)。ui-transition 是 tailwind.css 里的 @utility:过渡颜色类、透明度、阴影、位移与滤镜;减弱动效只留颜色类——这个偏好针对的是运动,变色不晃也不移,全关反而让切换生硬。150ms ease-out 是跟手与看得出过渡之间的取值。

    • 不写 Tailwind 的 transition-* 原子类:它们都写 transition-property,后写的整串替换前面的,叠在默认上不是追加而是互相覆盖(经 cn() 时 twMerge 也只留最后一个)。要多过渡的属性追加进变量:cn(TRANSITION, '[--ui-transition-extra:width]'),多个用逗号。
    • 时长、缓动只写各自的属性,经 cn() 后写的赢:duration-300、按下更跟手的 active:duration-75。
    • 入场、呼吸这类展示动效是关键帧,不走它。

    消费方式

    组件用 Tailwind 箭头变量原子类直接引用 token——刻意不用包级 @theme 注册,layer 归属永远跟宿主 utilities 走:

    html
    <div class="bg-(--ui-surface) text-(--ui-fg) border border-(--ui-border)">
      <span class="text-(--ui-fg-muted)">次要文字</span>
    </div>
    

    宿主写自己的样式时照同样方式引用,四维适配自动继承。