跳到正文
册页

基础

快速开始

装上它,选一张样式表,渲染第一个控件。

组件是编译好发出来的。你 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 里的十二个入口,你真正要的是哪一个,以及每一个漏掉了什么。

接着读与 AI 协作色彩

安装

一个包。样式和组件是两次分开的 import——没有任何东西替你把 CSS 拉进来,只 import 了组件的项目渲染出的是没有样式的标记,而不是一个报错。

Shell
pnpm add @misoto22/folio

CSS 入口点

真正要选的其实是两套配方。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 列表要对齐,也不会闪过错误的主题。

CSS
/* 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'] *));
HTML
<!-- 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。

TSX
// 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>
  )
}