内容
<picture>
<source media="(prefers-color-scheme: dark)" srcset="brand/liquefy-logo-dark.svg">
<img alt="liquefy-ui" src="brand/liquefy-logo.svg" width="264">
</picture>
一款基于TypeScript的UI库,通过WebGL光学、物理弹簧和可访问的React基本元素提供高度透明的Liquid Glass。

> 这是一个独立的开源项目,与苹果公司(Apple Inc.)无关。它引用了公开的设计原则,同时为Web提供了原始实现。
上述内容都不是视频效果:那是通过`backdrop-filter`应用到实时背景的WebGL位移图,而形状拉伸和超调的方式是弹簧读取指针速度。两者都默认启用。你可以在 **[liquefy-ui.com](https://liquefy-ui.com)** 上亲自体验,点击链接进行拖动。
## 软件包
| 软件包 | 用途 |
| --- | --- |
| `@liquefy-ui/react` | React组件、主题和提供者 |
| `@liquefy-ui/core` | 无依赖的WebGL、弹簧和运动 |
| `@liquefy-ui/icons` | 可树形摇动的React SVG图标 |
| `@liquefy-ui/mcp` | MCP服务器,回答来自真实API的组件问题 |
## 快速开始
```bash
pnpm add @liquefy-ui/react @liquefy-ui/core @liquefy-ui/icons
```
```tsx
import { LiquefyProvider, LiquidButton } from '@liquefy-ui/react'
import { SparklesIcon } from '@liquefy-ui/icons'
import '@liquefy-ui/react/styles.css'
export function App() {
return (
<LiquefyProvider theme="system" tint="#8f8f8f">
<LiquidButton iconBefore={<SparklesIcon />}>
创建魔法
</LiquidButton>
</LiquefyProvider>
)
}
```
## Next.js 和 React 服务器组件
每个组件都需要状态、引用或WebGL镜头,因此整个软件包都位于RSC边界的客户端。已发布的捆绑包带有 `'use client'` 指令,因此可以直接导入到服务器组件中,无需包装文件。只有事件处理程序需要移动:不能从服务器组件跨越到客户端组件的函数,因此任何带有 `onClick` 或本地状态的内容都应位于其自己的 `'use client'` 组件中。
`apps/next-example` 是一个可行的Next.js 16应用路由器应用,其页面是服务器组件,其构建在CI中运行。如果客户端边界曾经回归,那个构建将失败而不是你的。
## Tailwind CSS v4
导入 `tailwind.css` 而不是 `styles.css`,在Tailwind之前:
```css
@import '@liquefy-ui/react/tailwind.css';
@import 'tailwindcss';
```
这声明了级联层顺序 - 因此 `className="rounded-full"` 在 `LiquidButton` 上实际上生效 - 并将 `--lq-*` 令牌桥接到Tailwind的主题中,分别为 `bg-liquid-accent`、`text-liquid-muted`、`rounded-liquid`、`shadow-liquid`、`ease-liquid` 等。该桥使用 `@theme inline`,这使得这些实用程序在运行时按主题解析。
## 面向编码代理
| 内容 | 位置 |
| --- | --- |
| MCP服务器 | `claude mcp add liquefy-ui -- npx -y @liquefy-ui/mcp` — 八种工具从真实导出中回答,无需网络,无需依赖 |
| `llms.txt` | [`/llms.txt`](https://liquefy-ui.com/llms.txt) 和 [`/llms-full.txt`](https://liquefy-ui.com/llms-full.txt),从源代码生成 |
| 每个组件的页面 | [`/llms/liquid-button.md`](https://liquefy-ui.com/llms/liquid-button.md),以及 `icons.md`、`core.md` 和 `mcp.md` — 普通Markdown,因为文档站点是一个哈希路由的SPA,无法被没有JavaScript的获取器读取 |
| shadcn注册表 | `npx shadcn@latest add https://liquefy-ui.com/r/liquid-button.json` |
MCP工具是 `get_conventions`、`list_components`、`get_component`、`search_components`、`get_component_source`、`get_tokens`、`list_icons` 和 `get_core_api`。这些工具背后的目录是从源代码在构建时生成的,并且只列出包入口点重新导出的名称 - 因此代理永远不会被告知导入不存在的内容。
注册表复制真实的组件源代码到您的项目中,而不是重新导出,导入被重写为 `@/components/ui`、`@/lib` 和 `@/hooks`。复制的树保留 `@liquefy-ui/core` 和 `@base-ui/react` 来自npm,但不保留 `@liquefy-ui/react`,因此复制的 `LiquefyProvider` 永远不会与打包的竞争。每个复制的文件都写入其自己的 `'use client'` 指令,因此RSC应用不需要后续编辑。
## 文档
运行 `pnpm dev`,然后:
| 路由 | 内容 |
| --- | --- |
| `#/` | 首先是游乐场,然后是框架和代理工具兼容性,然后是组件样本 |
| `#/playground` | 每个 `LiquefyProvider` 属性作为实时控件,应用于整个站点 |
| `#/components` | 每个组件的索引,带有实时演示和完整的属性表 |
| `#/docs` | 介绍、安装、提供者、主题、`styles` 属性、运动 |
| `#/docs/frameworks` · `#/docs/tailwind` · `#/docs/ai-tooling` | 集成 |
| `#/docs/accessibility` · `#/docs/performance` · `#/docs/troubleshooting` | 实践 |
十二个文档页面位于 `apps/docs/src/docs/pages/*.tsx`,在 `apps/docs/src/docs/docs-nav.tsx` 中注册 - 每个页面一个 `DocEntry`,分为侧边栏类别。 33个组件页面来自 `apps/docs/src/docs/catalog-*.tsx`,shadcn注册表发布 37 个从同一来源构建的项目。旧的 `#/guides/*` 链接重定向到其对应的 `#/docs/*` 等价项。
## 样式覆盖:`styles` 属性
每个组件都接受一个 `styles` 属性用于一次性覆盖,因此可以不使用样式表。它是 `style` 的超集:
```tsx
<LiquidButton
styles={{
color: 'accent', // 颜色单词解析为 var(--lq-accent)
p: 3, // 间距键计数 --lq-space 单位
w: { base: '100%', md: 240 }, // 响应式,按断点
boxShadow: '$shadow', // $token → var(--lq-token),在字符串的任何位置
_hover: { bg: '$glass-soft' },
_dark: { opacity: 0.92 },
'&:has(svg)': { gap: 2 }, // 原始选择器以 & 开头,at-rules 以 @ 开头
}}
>
创建魔法
</LiquidButton>
```
| 功能 | 备注 |
| --- | --- |
| CSS属性 | 每个驼峰属性,加上 `--custom-properties`。数字变为 `px`,与 `style` 匹配。 |
| 间距缩写 | `p`、`px`、`py`、`pt`/`pr`/`pb`/`pl`,以及 `m` 等价项。数字计算 `--lq-space` 单位(默认为 `4px`,在提供者上设置 `spacing`)。`gap` 和朋友使用相同的比例。 |
| 其他缩写 | `w`、`h`、`size`、`minW`/`maxW`/`minH`/`maxH`、`bg`、`radius`。`radius` 驱动 `--lq-radius`,因此按下-挤压始终会使角动画。 |
| 令牌 | 字符串中的 `$name` 解析为 `var(--lq-name)`。颜色属性还接受裸词 `accent`、`tint`、`foreground`、`muted`、`placeholder`、`text`、`line`。 |
| 响应式 | `{ base, sm, md, lg, xl }`,按升序排列,无论您如何编写。 使用 `breakpoints` 在 `LiquefyProvider` 上覆盖宽度。 |
| 状态 | `_hover`、`_focus`、`_focusVisible`、`_active`、`_disabled`、`_checked`、`_selected`、`_expanded`、`_open`、`_invalid`、`_readOnly`、`_placeholder`、`_first`、`_last`、`_odd`、`_even` 和 `_dark` / `_light`(它们涵盖显式主题和 `theme="system"`)。 |
静态值通过 `style` 属性传递,因此常见情况不会添加样式表和水合问题。一旦出现状态或断点,整个对象就会移动到生成的类中 - 否则内联声明将优先于要覆盖的规则。该类**未分层**,而组件样式表位于 `@layer liquefy-ui`,因此覆盖项在层叠顺序中获胜,而不是 `!important` 或特异性。
优先级是 `styles` 覆盖组件自己的自定义属性,而 `style` 覆盖所有属性 - `style` 仍然是最后的避难所。
`transform` 和 `backdrop-filter` 每帧由果冻弹簧写入,不能通过 `styles` 覆盖;开发构建如果尝试则会警告。包装组件并样式化包装器。
使用相同的系统构建自己的组件:
```tsx
import { useLiquidStyles, type LiquidStyleProps } from '@liquefy-ui/react'
export const Panel = ({ className, style, styles, ...props }: LiquidStyleProps & JSX.IntrinsicElements['div']) => {
const root = useLiquidStyles('my-panel', { className, style, styles })
return <div className={root.className} style={root.style} {...props} />
}
```
服务器渲染:`useInsertionEffect` 在服务器上不会运行,因此请使用 `getLiquefyStyleSheet()` 将收集的规则刷新到文档头部。静态 `styles` 不需要任何操作 - 它们已经内联。
`LiquefyProvider` 采用 `className` 和 `style` 但不采用 `styles` - 它拥有 `styles` 读取的配置。 `LiquidToastProvider` 不采用任何一种:它不渲染自己的根元素,只渲染toast视口。 `slotStyles` 保留用于每个部分样式化(`{ header, body, footer }`),目前尚未实现。
完整的版本 - 每个缩写、令牌引用、断点和状态键,加上自定义组件和服务器渲染 - 位于 `#/docs/styles-prop`,令牌系统本身位于 `#/docs/theming`。
## 开发
两个不同的Node版本,它们不是同一个数字:
- **消费者** 需要 Node 20.19 或更新版本,这就是 `engines` 所说的。CI通过在确切的版本上安装打包的tarballs并从中进行服务器渲染来证明这一点。
- **此仓库** 需要 Node 22.13 或更新版本,因为pnpm 11 需要。
```bash
corepack enable
pnpm install
pnpm dev
```
`pnpm check` 是CI运行的内容:`build`,然后 `typecheck`、`test` 和 `verify:rsc`。
构建首先出现,因为 `apps/next-example` 故意与构建的包进行类型检查,而不是源代码 - 它是 `'use client'` 横幅的金丝雀,所以在干净的checkout中,类型不存在,直到构建完成。
该应用还保留了一个版本,而仓库不保留:`typescript` 在那里停留在 5.x,而其他一切都在 7.x。 Next 16.2 无法找到 TypeScript 7 - `next build` 报告 *"您没有安装所需的包(s)并退出 1"*,所以这个固定版本使金丝雀保持活动。 当Next支持 7 时提升它,而不是之前;依赖机器人将提供,但CI会拒绝它。
| 命令 | 用途 |
| --- | --- |
| `pnpm dev` | 启动文档和游乐场 |
| `pnpm preview` | 提供已构建的文档 |
| `pnpm typecheck` | 类型检查每个包 |
| `pnpm test` | 生成生成的文件,然后运行整个套件 |
| `pnpm build` | 构建npm工件和文档 |
| `pnpm check` | 构建、类型检查、测试和验证RSC指令 - CI运行的内容 |
| `pnpm generate` | 从源代码生成MCP目录、shadcn注册表和 `llms` 文件 |
| `pnpm verify:rsc` | 断言构建的捆绑包带有 `'use client'` |
| `pnpm pack:packages` | 将三个面向消费者的包打包到 `.tarballs/` |
| `pnpm prepare` | 将 `.githooks/` 指向 `.githooks/` - 由 `pnpm install` 为您运行 |
| `pnpm smoke` | 打包、安装tarballs到空npm项目,并从中渲染 |
| `pnpm changeset` | 为下一次发布描述更改 |
| `pnpm version-packages` | 在本地应用更改集 |
| `pnpm release` | 构建并发布(Release工作流运行) |
| `pnpm downloads` | 报告每个已发布的包的下载次数 |
### 测试
`pnpm test` 运行所有操作:单元测试、jsdom下的DOM测试和一组不变性测试,因为它们捕获的失败是沉默的。
| 套件 | 持有 |
| --- | --- |
| `packages/react/test` | `styles` 引擎、每个叠加层的键盘行为、提供者的属性和令牌、每个控件的角色/名称/状态契约以及它们的所有服务器渲染 |
| `packages/core/test` | 弹簧积分器,以及运动引擎在每个帧上编写的自定义属性 - 样式表读取确切的名称 |
| `packages/mcp/test` | 协议握手、通知处理以及所有答案来自生成的目录 |
| `apps/docs/test` | 每个演示和文档页面都渲染,每个导入行都命名为真实导出,每个组件都有文档,每个 `#/` 链接都解析,每个路由都被视为自己的页面 |
| `test/` | 公开的导出表面,每个主题的令牌集、shadcn注册表的安装图、生成的 `llms` 文件、包清单、提交消息约定、每个哈希路由报告的页面analytics,以及退休拼写从未返回 |
其中一些值得了解:
- `test/public-api.test.mjs` 按名称列出每个导出。更改该列表会故意失败测试:这意味着编写更改集,而不是在消费者的构建中注意到。
- `test/tokens.test.mjs` 断言light和dark声明相同的 `--lq-*` 集。令牌添加到一个主题而非另一个主题会破坏该主题,而其他一切都会失败。
- `test/metadata.test.mjs` 如果项目名称的已弃用拼写(带有 `i` 的拼写,而此拼写带有 `e`)出现在任何地方,则失败。两个曾经共存,生成的链接曾经指向错误的 >链接。
### 分支
`rc` 是开发发生的地方。从它分支,打开一个拉取请求返回到它,这就是更改的整个循环。 `main` 是发布分支,保留尚未在 `rc` 中完成的内容。
```
feature/… ──▶ rc ──▶ main
└── 发布到npm
```
两个分支都只接受拉取请求 - 直接推送、强制推送或删除 - 两个分支都要求以下CI作业在合并之前通过。
| 作业 | 它回答什么 |
| --- | --- |
| 提交消息 | 每个新提交 - 以及拉取请求标题的squash merge将使用 - 是否遵循约定? |
| 类型检查、测试和构建 | `pnpm check` 通过了吗? |
| 测试 Node 24 | 套件在当前版本上通过吗? |
| 消费者安装 Node 20.19 | 打包的tarballs是否使用npm安装并渲染,对版本 `engines` 声明的内容? |
| 包清单和类型 | `publint` 和 `@arethetypeswrong/cli` 接受什么npm提供? |
所以发布序列是:先将版本拉取请求合并到 `rc`,然后将 `rc` 合并到 `main`。获取错误时,`release.yml` 停止发布并不行。
### 浏览器
Vercel部署只构建`@liquefy-ui/docs`及其导入内容 - `apps/next-example`仅作为CI的RSC边界守卫存在,而非部署内容 - `vercel.json`将其过滤掉以免拖慢每次预览。
该项目连接到此仓库,因此`main`部署到生产环境,而其他分支则获得自己的预览网址。 不需要`vercel deploy`。
`vercel.json`还将`liquefy-ui.vercel.app`发送到`liquefy-ui.com`,重定向状态码为308。 Vercel将该主机名分配给项目,没有办法将其交还,因此站点在项目被重命名或移动后仍能通过地址栏中复制的链接访问。 重定向仅匹配主机并保留路径,因此部署特定的预览网址不受影响,可以直接打开。
## 组件
33个组件和44个图标,每个组件都有实时演示和完整的属性表,位于组件站点:运行 `pnpm dev` 并打开 `#/components`。
| 类别 | 组件 |
| --- | |
| 输入 | `LiquidButton`、`LiquidIconButton`、`LiquidCheckbox`、`LiquidRadioGroup` / `LiquidRadio`、`LiquidSwitch`、`LiquidSlider`、`LiquidTextField`、`LiquidTextArea`、`LiquidSelect`、`LiquidSegmented`、`LiquidRating` |
| 数据显示 | `LiquidAvatar` / `LiquidAvatarGroup`、`LiquidBadge`、`LiquidChip`、`LiquidTooltip`、`LiquidTable`家族、`LiquidList`家族、`LiquidDivider` |
| 反馈 | `LiquidAlert`、`LiquidProgress`、`LiquidSpinner`、`LiquidSkeleton`、`LiquidToastProvider` / `useLiquidToast`、`LiquidDialog` |
| 表面 | `LiquidSurface`、`GlassCard`、`LiquidAccordion` / `LiquidAccordionItem` |
| 导航 | `LiquidTabs`家族、`LiquidBreadcrumbs`、`LiquidPagination`、`LiquidMenu`、`LiquidDrawer`、`GlassDock` / `DockItem` |
| 基础 | `LiquefyProvider`、`useLiquefyConfig`、`useLiquidGlass`、`useLiquidStyles`、`getLiquefyStyleSheet`、`defaultBreakpoints` |
当WebGL不可用时,组件自动回落到透明的CSS材质。特效在所有地方都默认为开启;使用提供者的`motion`和`transparency`属性来降低它们。
使用 `theme="dark"`、`theme="light"` 或 `theme="system"` 在 `LiquefyProvider` 上控制外观。
## 发布
每个软件包都从 `@liquefy-ui` 范围内公开发布,并附有 npm 源证明声明。**没有任何发布令牌**:每个软件包都将 `liquefy-ui/liquefy-ui` 和 `release.yml` 列为其受信任的发布者,因此工作流将其 OIDC 身份交换为短期凭证,注册表会签名结果。消费者可以使用 `npm audit signatures` 进行检查。
以下三点确保了发布过程的正常进行:
1. `release.yml` 保持其名称和 `id-token: write` 权限。重命名文件后,每个发布都会失败,直到所有四个软件包的受信任发布者都更新完毕,位置在 Settings → Trusted Publisher。
2. Settings → Actions → General → Workflow permissions 中启用了 *允许 GitHub Actions 创建和批准拉取请求*。Changesets 打开版本拉取请求,如果没有此权限,则会失败,错误信息为 `GitHub Actions 不允许创建或批准拉取请求`。
3. 每个软件包都包含一个 `repository` 字段,这是 npm 在签名发布前所需的。`test/metadata.test.mjs` 确保这一点。
发布流程如下:`pnpm changeset` 伴随更改,合并到 `rc`,合并 `version.yml` 提出的 **chore: version packages** 拉取请求,然后打开 `rc → main`。合并后发布。
软件包版本独立。曾经有一个 `linked` 组将 `core`、`icons` 和 `react` 綁定到一个版本号,理论上这是为了防止复制的注册表树和 npm 树可能出现不一致的情况 —— 但注册表只固定 `core`,并根据 `core` 自己的清单派生出该范围,因此没有任何内容读取对齐。它的存在只是在第三个包更改时重新发布两个未更改的包。
发布仅在 CI 中有效,这也是如此 —— 笔记本电脑没有任何 OIDC 身份可被注册表信任。本地运行的是预演:
```bash
pnpm smoke # 打包,安装(使用 npm),渲染 — 在其他操作之前
pnpm changeset
pnpm version-packages # 检查版本号和变更日志,然后推送
```
使用 **pnpm** 打包,绝不使用 `npm pack`:只有 pnpm 重写其自己的 `workspace:^` 规范为实际范围,而 npm 遇到未知协议时会直接退出,没有任何提示。`pnpm release` 和 `pnpm smoke` 都做了正确的事情。
一个值得注意的陷阱,因为它消耗了一个版本。`changeset publish` 调用工作区的包管理器 —— 这里是 `pnpm publish` —— 而 pnpm 根本不支持源证明。`publishConfig.provenance` 是一个仅 npm 支持的字段,因此它被接受并被忽略,发布结果是未签名的,且没有任何警告。受信任的发布者实际产生了声明;仅凭该字段没有任何作用。
## 设计笔记
- **真正的边缘折射**:WebGL 着色器烘焙了一个圆角矩形透镜位移图,通过 `backdrop-filter` 中的 SVG `feDisplacementMap` 应用于实时背景(带有每通道色散)。Chromium 完整渲染了它;WebKit 和 Gecko 优雅地回退到模糊的 CSS 材质。
- **共享的 WebGL 上下文**:浏览器限制了实时的 WebGL 上下文(每个页面约 16 个),因此每个组件都通过单个隐藏的 GL 画布进行绘制,并将其内容复制到自己的 2D 画布中。任何数量的 glass 组件都可以共存。
- **弹性物理效果**:缩放、倾斜和旋转运行在故意欠阻尼的弹簧上。指针速度被注入弹簧中,因此快速扫描使表面摇摆,按下/释放产生多个可见的过冲。着色器接收与 `u_wobble` 相同的能量,并同步晃动边缘。
- 叠加层着色器渲染 SDF 形状的边缘光,带有 RGB 色散、虹彩、指针发光、按下时产生的涟漪和移动的光泽 —— 仅在交互和衰减期间,从不连续。
- Glass 主要用于交互和导航层,而不是主要内容。
- **无障碍功能来自 Base UI**:Dialog、Drawer、Menu、Select、Tooltip、Tabs 和 Accordion 都构建在 `@base-ui/react` 之上,它提供了焦点捕获和恢复、滚动锁定、Escape 处理、漫游 tabindex、类型预测和碰撞感知定位。liquefy-ui 保留了光学和弹簧,并停止重新实现容易出错的部分。在 `packages/react/test/keyboard.test.tsx` 中断言键盘行为,而不是假设。
- React 和 React DOM 是对等依赖项,防止重复的 React 包。
- 运动和透明度 **默认始终启用**,独立于 OS 无障碍设置(macOS “减少动画” / “减少透明度”会在所有桌面浏览器中静默翻转两个媒体查询)。在 `LiquefyProvider` 上使用 `motion={false}` / `transparency={false}` 按子树切换它们;想要遵循 OS 设置的应用程序可以传递 `motion={!matchMedia('(prefers-reduced-motion: reduce)').matches}` 或使用核心级别的 `respectReducedMotion` / `respectReducedTransparency` 选项。
## 许可证
MIT
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器