import {
FLOW_TONES,
flowToneStyle,
type FlowToneName,
} from "@/lib/flow-tone"
/** 内置色调,顺序即 `FLOW_TONES` 的声明顺序,右侧显示它对应的主题变量 */
const ROWS = (Object.keys(FLOW_TONES) as FlowToneName[]).map((name) => ({
name,
value: FLOW_TONES[name],
}))
/** 任意颜色:主题变量、十六进制、oklch 都行,这里用 chart-1 演示 */
const CUSTOM_TONE = "var(--chart-1)"
/**
* 默认示例:内置色调的对照表。每行在自己的元素上写 `--flow-tone`,里面的色块只是
* `bg-(--flow-tone)`,看到什么颜色就说明变量已经继承下去;最后一行演示直接传主题变量字符串。
*/
export default function FlowToneDemo() {
return (
<div className="w-full max-w-sm rounded-xl border border-edge bg-background p-4 shadow-xs">
<div className="flex items-baseline justify-between gap-2 pb-3">
<div className="min-w-0">
<p className="text-sm font-medium">色调</p>
<p className="truncate text-xs text-muted-foreground">
名字换成 --flow-tone 变量
</p>
</div>
<p className="shrink-0 font-mono text-[11px] text-muted-foreground">
{ROWS.length} 内置
</p>
</div>
<ul className="flex flex-col">
{ROWS.map((row) => (
<li
key={row.name}
className="flex items-center gap-2.5 py-1"
style={flowToneStyle(row.name)}
>
<span
aria-hidden="true"
className="size-3 shrink-0 rounded-sm bg-(--flow-tone)"
/>
<span className="font-mono text-xs">{row.name}</span>
<span className="ms-auto truncate font-mono text-[11px] text-muted-foreground">
{row.value}
</span>
</li>
))}
</ul>
<div
className="mt-2 flex items-center gap-2.5 border-t border-dashed border-edge pt-3"
style={flowToneStyle(CUSTOM_TONE)}
>
<span
aria-hidden="true"
className="size-3 shrink-0 rounded-sm bg-(--flow-tone)"
/>
<span className="text-xs">任意颜色</span>
<span className="ms-auto truncate font-mono text-[11px] text-muted-foreground">
{CUSTOM_TONE}
</span>
</div>
</div>
)
}
安装
pnpm dlx shadcn@latest add https://yiui.dev/r/flow-tone.json它是所有带颜色的组件共用的底层工具,安装下面任何一个组件时都会自动带上,一般不需要单独装。
用法
flowToneStyle(tone) 把色调名换成一条 CSS 变量,返回可以直接铺在元素上的 style 对象:
import { flowToneStyle } from "@/lib/flow-tone"
export function ToneDot() {
// 变量写在外层,里面的色块只需要读 --flow-tone
return (
<div style={flowToneStyle("violet")}>
<span className="block size-3 rounded-sm bg-(--flow-tone)" />
</div>
)
}带颜色的原语都只读 --flow-tone,CSS 变量沿 DOM 继承,所以在哪一层设置它,里面没有自己指定 tone 的原语就一起跟着变——自己写的 SVG、进度条、色点同样可以直接用 bg-(--flow-tone)、stroke-(--flow-tone) 取色。
不传 tone 时返回 undefined,元素上不会写入这条变量,即沿用外层继承来的色调。
内置的十个名字对应如下变量,前四个跟随颜色主题,后六个由本条目提供亮 / 暗两套取值:
| 名称 | 取值 |
|---|---|
primary | var(--primary) |
foreground | var(--foreground) |
muted | var(--muted-foreground) |
destructive | var(--destructive) |
blue | var(--flow-blue) |
green | var(--flow-green) |
amber | var(--flow-amber) |
rose | var(--flow-rose) |
violet | var(--flow-violet) |
cyan | var(--flow-cyan) |
映射表也导出了,需要遍历或做选择器时直接读:
import { FLOW_TONES } from "@/lib/flow-tone"
FLOW_TONES.blue // "var(--flow-blue)"toneClasses 汇总了几种常用的取色写法,字符串里已经写全类名,Tailwind 能正常扫描到:lit(淡底色 + 描边,点亮节点)、stroke、flow(流动的虚线纹理)、line(淡化连线)、fill。
换成品牌色
六个 --flow-* 写在全局样式的 :root 与 .dark 里,改这两处就换掉了全站的多彩色调:
:root {
--flow-blue: oklch(0.56 0.2 258);
--flow-green: oklch(0.62 0.16 150);
--flow-amber: oklch(0.7 0.16 70);
--flow-rose: oklch(0.62 0.21 5);
--flow-violet: oklch(0.56 0.22 292);
--flow-cyan: oklch(0.66 0.12 215);
}
.dark {
--flow-blue: oklch(0.72 0.15 255);
/* 其余五个同理,暗色一般调亮一档、降一点彩度 */
}这几个变量不随颜色主题(data-theme)变化。想让品牌色跟着颜色主题走,就在每套主题的选择器下各写一份;只想给某一块区域换色,直接在这块区域的元素上传 flowToneStyle("var(--brand)") 即可,不必动全局。
哪些组件在用
组件的 tone 属性都走这套变量,装了它们就等于装了 flow-tone:
- 图形与动效:flow-diagram、orbit、ripple、shuffle-grid、glyph-field、code-stack、globe
- 表单与输入:value-slider、xy-pad、angle-dial、segmented-control、toggle-switch、tone-picker
- 数据与内容:timeline、checklist、text-animate
- 区块:settings-01
API
type FlowToneName =
| "primary"
| "foreground"
| "muted"
| "destructive"
| "blue"
| "green"
| "amber"
| "rose"
| "violet"
| "cyan"
/** 内置名,或任意 CSS 颜色:"#f97316"、"oklch(0.7 0.2 150)"、"var(--brand)" 都可以 */
type FlowTone = FlowToneName | (string & {})
/** 返回 { "--flow-tone": 颜色 };不传 tone 返回 undefined */
function flowToneStyle(tone?: FlowTone): CSSProperties | undefined
/** 内置名到主题变量的映射 */
const FLOW_TONES: Record<FlowToneName, string>
/** 常用的取色类名:lit / stroke / flow / line / fill */
const toneClasses: Record<"lit" | "stroke" | "flow" | "line" | "fill", string>注意事项
- 取色只能写 CSS 变量形式:
bg-(--flow-tone)、stroke-(--flow-tone)、text-(--flow-tone)。变量是运行时才有的自定义属性,Tailwind 不会为它生成bg-flow-tone这样的类名。 - 传内置名以外的字符串会原样写进
--flow-tone,不做校验;写错了颜色不会报错,只是取不到色,可以用FLOW_TONES先判断一下是不是内置名。 - 只有
primary、foreground、muted、destructive跟随亮暗与颜色主题;六个--flow-*是固定取值,暗色另有一套,需要跟着主题变就自己按主题选择器覆盖。 - 文件是纯函数,没有
"use client",返回的只是内联style对象,服务端组件里可以直接用;不需要任何 npm 依赖。
源码
import type { CSSProperties } from "react"
/**
* 内置色调。
* - `primary`、`foreground`、`muted`、`destructive` 直接引用主题变量,跟随颜色主题;
* - `blue` 等多彩色调引用 `--flow-blue` 这类变量,由 registry 条目的 `cssVars` 提供亮 / 暗两套取值,
* 在自己的全局样式里改写它们即可换成品牌色。
*/
type FlowToneName =
| "primary"
| "foreground"
| "muted"
| "destructive"
| "blue"
| "green"
| "amber"
| "rose"
| "violet"
| "cyan"
/** 色调:内置名,或任意 CSS 颜色(`"#f97316"`、`"oklch(0.7 0.2 150)"`、`"var(--brand)"` 都可以)。 */
type FlowTone = FlowToneName | (string & {})
const FLOW_TONES: Record<FlowToneName, string> = {
primary: "var(--primary)",
foreground: "var(--foreground)",
muted: "var(--muted-foreground)",
destructive: "var(--destructive)",
blue: "var(--flow-blue)",
green: "var(--flow-green)",
amber: "var(--flow-amber)",
rose: "var(--flow-rose)",
violet: "var(--flow-violet)",
cyan: "var(--flow-cyan)",
}
/**
* 把色调换成 `--flow-tone` 这个 CSS 变量。
* 所有带颜色的原语都只读 `--flow-tone`,变量沿 DOM 向下继承,所以在任何元素(包括你自己写的 SVG)
* 上设置它,里面没有指定 `tone` 的原语都会跟着变。不传 `tone` 返回 `undefined`,即沿用外层的色调。
*/
function flowToneStyle(tone?: FlowTone): CSSProperties | undefined {
if (tone === undefined) return undefined
const color = Object.hasOwn(FLOW_TONES, tone)
? FLOW_TONES[tone as FlowToneName]
: tone
return { "--flow-tone": color } as CSSProperties
}
// Tailwind 只识别完整的类名,所以每种用法都把类写全
const toneClasses = {
/** 节点被点亮:淡底色 + 描边 */
lit: "fill-(--flow-tone)/10 stroke-(--flow-tone)",
stroke: "stroke-(--flow-tone)",
/** 常驻连接上流动的虚线纹理 */
flow: "stroke-(--flow-tone)/40",
/** 指定了色调的连线:淡淡染上色调 */
line: "stroke-(--flow-tone)/30",
fill: "fill-(--flow-tone)",
} as const
export { FLOW_TONES, flowToneStyle, toneClasses }
export type { FlowTone, FlowToneName }