基础
快速开始
装上它,选一张样式表,渲染第一个控件。
组件是编译好发出来的。你 import 它们,而不是把它们抄进自己的项目;也没有哪个 CLI 会往你的源码树里写一个 Button 交给你维护。这就是这个包做的取舍:升级是改一个版本号,而不是四十个从此归你的文件上的一份 diff。
四个 peer dependency,其中只有两个是 React。react 与 react-dom,^19.0.0,是必需的;motion 和 recharts 声明为可选,它们是一个入口点的代价——@misoto22/folio/charts 两个都要 import,没装上就解析不到。@misoto22/folio/diagrams 两个都不要。组件需要的其余东西——Radix、cmdk、sonner、Remix Icon、react-day-picker、tailwind-merge——都是真正的 dependency,随包一起下来。Node 24 或更新,而且这个包只有 ESM:exports map 里带的是 import 条件,没有 require,所以 CommonJS 构建解析不到它。
这一页剩下的部分,正是 README 写得最薄的那一块:exports map 里的十二个入口,你真正要的是哪一个,以及每一个漏掉了什么。
安装
一个包。样式和组件是两次分开的 import——没有任何东西替你把 CSS 拉进来,只 import 了组件的项目渲染出的是没有样式的标记,而不是一个报错。
pnpm add @misoto22/folioCSS 入口点
真正要选的其实是两套配方。styles.css 是整张编译好的样式表:Tailwind、原始 token、语义角色、主题轴、长文样式、动效关键帧,以及附在末尾的自带 @font-face 规则。一次 import,不用再做别的决定,Tailwind 也一并带过来。
另一套配方是给已经自己编译 Tailwind、不想要第二份 utility 的应用的。它是三次 import——tokens.css、semantic.css、keyframes.css——而在选它之前值得知道的,是这三张表没有带上什么。
data-mode="dark" 和 data-density="compact" 声明在 tokens.css 里,活得下来。另外五条轴——data-surface、data-radius、data-rules、data-type、data-motion——只声明在 themes.css 里,那是一个单独的导出,既不在 README 的配方里,也不在上面那段代码里。没有它就写 data-radius="sharp",属性会落到元素上,然后什么都不改变。字体在 fonts.css,同样是单独的;长文正文样式在 article.css。要哪一张,就按名字自己加上。
- @misoto22/folio
- 组件、类型,以及 `cn`、`CONTROL_BASE`、`CONTROL_BORDER`、`isInvalid`、`BRAND`。
- /charts
- 图表,也是那两个可选 peer 存在的唯一理由——它 import 了 `recharts` 和 `motion/react`。放在自己的入口后面,所以一个只渲染 Badge 的页面两样都不必付。
- /diagrams
- 五个图形渲染器,以及读者用来查看它们的画布、工具条、图例、缩略图和检视面板。除 React 之外不需要任何 peer。
- /styles.css
- 基础组件需要的全部,已编译:Tailwind、token、语义角色、主题轴、长文样式和关键帧这几层,以及自带的字体。一次 import 走完的那条路。网站样式表不在里面。
- /website.css
- `@misoto22/folio/website` 的样式,只作用于组合组件本身。渲染组合组件时,把它和 `styles.css` 或那几张可移植样式表一起 import。
- /website-base.css
- 给「整个页面就是一个网站」准备的文档级默认样式——body 排版、标题外边距、全站焦点环。按需引入:只是嵌入组合组件的应用不要引入它。
- /tokens.css
- 原始 token,外加默认的暗色换值和 compact 密度轴。
- /semantic.css
- 组件真正读的那些角色——`--background`、`--foreground-muted`、`--border-color`。
- /keyframes.css
- 动画,以及把它们停下来的那一条减少动效规则。
- /themes.css
- 剩下的五条主题轴。不在那套三层配方里;只要你设了其中任何一条,就得把它加上。
- /fonts.css
- 自带的 @font-face 规则。已经附在 `styles.css` 里面了。
- /article.css
- 长文正文的排版样式,给 `Article` 组件用。
- /tokens.json + /tokens
- 同一批 token,作为数据——JSON 给构建脚本,带类型的 `TOKENS` 记录和 `TokenName` 联合类型给 TypeScript。
Tailwind 互操作
语义色刻意没有被提升成 Tailwind 的颜色 utility。它们是用任意属性语法消费的——text-(--ink)、bg-(--paper)、border-(--rule)——所以新增一个角色或给它换色,是 semantic.css 里的一处改动,别的地方一处都不用动。没有 bg-paper 这个 class,以后也不会有。
如果你自己编译 Tailwind,把 @source 指向这个包的 dist,好让库组件内部用到的 utility 也在你的构建里生成出来,并且把 dark 变体声明成对属性而不是对 class 的匹配。data-mode 放在 <html> 上是有意的:它可以由一段内联脚本在首次绘制之前写好,没有 class 列表要对齐,也不会闪过错误的主题。
/* An app that already compiles Tailwind takes the token layers only, and
skips a second copy of the utilities. */
@import 'tailwindcss';
@import '@misoto22/folio/tokens.css';
@import '@misoto22/folio/semantic.css';
@import '@misoto22/folio/keyframes.css';
/* Generate the utilities the library's own components use. */
@source '../node_modules/@misoto22/folio/dist';
@custom-variant dark (&:is([data-mode='dark'] *));<!-- The mode is an attribute on <html>, so it can be written before first
paint and never flashes the wrong theme. -->
<html data-mode="dark">第一个组件
一个有标签、必填的字段,以及提交它的那个按钮。Field 会把 ARIA 接线——aria-describedby、aria-required、aria-invalid——接到它包住的那唯一一个元素子节点上,这也正是为什么手写一个 <label>、一个输入框和一个错误 div,是这套系统唯一帮不上忙的写法。一个 Field 里塞两个控件,两个都接不上,而且没有声音。
错误写在 Field 的 error 属性里,而不是摆在它的 hint 旁边:它们是同一个槽位,不是上下叠着的两条消息;而且传了 error 就已经在控件上设了 aria-invalid——所以别再同时传 invalid。
// Once, at your app root — the compiled stylesheet carries the tokens,
// the utilities the components use, and the vendored faces.
import '@misoto22/folio/styles.css'
import { Button, Field, Input } from '@misoto22/folio'
export function SignIn() {
return (
<form className="flex flex-col gap-4">
<Field label="Email" required>
<Input type="email" />
</Field>
<Button type="submit">Continue</Button>
</form>
)
}