容器
Article - 长文
长文阅读面——Markdown 能产出的一切,都套上这套系统的字体、颜色和线。
用法
什么时候用它
import { Article } from '@misoto22/folio'说明
长文阅读面。
Markdown 管线能产出的一切——标题、正文、列表、表格、引文、代码、图注、脚注、MathML——都套上这套系统的字体、颜色和线。样式写在 article.css 里而不是一串 class 里,因为它的输入不是 JSX:标记是以字符串到达的,没有任何组件可以挂 class,所以约定只能是元素名本身。
那个文件也单独发布为 @misoto22/folio/article.css,所以一个自带 Markdown 管线的站点可以只拿阅读面、不拿组件——这正是它存在的场景。三个站各写一份正文样式表,就是三份会各自漂移的正文样式表。
两种用法都行:传 html 渲染字符串,或者直接给 children 写真元素——二选一,绝不能都给。两个都到了时以 html 为准,并且开发环境会把这件事说出来:html="" 也依然是 html,正因如此,整整一页 children 可以一声不响地丢掉。两者混排的文章——正文中间插一张 Diagram——按顺序渲染各个块,每块各给一个 Article。在一个 Article 里面,这些规则赢过组件的工具类:article.css 是不分层引入的,而 Tailwind 的工具类待在 @layer utilities 里,不分层的规则不论两边优先级如何都胜过分层的规则。这是刻意的机制而不是意外——正是它让一个 Markdown 段落(一个带 m-0 的 Text)把外边距让给文章的节奏。一个必须在文章里保住某个属性的组件,得用内联样式,而不是类。
结构
| 部件 | 说明 |
|---|---|
| 正文栏必填 | as 给什么就渲染什么——article、section 或 div——并打上 data-folio-article,article.css 里的每一条规则都限定在它上面。46rem 的行宽,自己不带任何行内外边距,所以父容器把它放哪它就在哪。 |
| 块必填 | 直接子元素,节奏就住在这里:每个块上方留白,标题上方更多,第一个块上方没有。往下多包一层的块就落在这条规则之外,也就拿不到它的间距。 |
| 渲染好的 HTML | html,用 dangerouslySetInnerHTML 写进去。给了它,children 就完全不渲染——信任边界在产出这个字符串的那条管线上,因为这里没有任何东西会在半路拦下一个 script 标签。 |
| 导语 | p.lead——那段引言,--fs-item 字号、满值 --ink。由作者或管线标出来,绝不靠推断:样式表不会把碰巧排在最前面的那一段提拔上来。 |
| 超宽的块 | figure、table 和 .folio-wide 是仅有的三样获准越出行宽的东西,因为六列的表格和一张有主体的图,在 46rem 下都读不成。 |
实践建议
推荐
- 居中要自己来。它设的是行宽,不是布局——没有 auto 外边距,所以在宽页面上它会一直贴着行首那一侧,直到某个父容器把它居中。
- 每个块都要是直接子元素:节奏是用子选择器写的,所以在一串段落外面套一个 <div>——哪怕是 display:contents 的,它去掉的是盒子而不是节点——会让这些段落全部丢掉自己的间距。
- 让管线把宽表格包进 .folio-table-scroll。表格本来就获准越出行宽,自己又没有可滚动的容器,所以八列顶开的是整个页面的横向滚动。
- 在字符串到达之前就做净化,并把做净化的那道边界标出来:html 是当作 innerHTML 设进去的,所以一个没净化就走到这个属性的 CMS 字段,就是一处排好了行宽的存储型 XSS。
避免
- 不要同时传 html 和 children——html 赢,children 被丢掉。现在开发环境下会把这件事说出来,可 html="" 仍然算 html,所以一条什么都没渲染出来的管线,会连带把 children 一起拖下水。既有正文又有组件的文章,是前后排的两个 Article,不是一个装下两者。
- 不要指望嵌进来的组件的工具类在文章里还站得住:article.css 是不分层引入的,无论特异性如何都赢过 Tailwind 的 @layer utilities,所以只要这些规则也设了同一个属性,组件那边就被覆盖。非要保住某个属性的组件,得用内联样式,或者换一个样式表够不到的标签。
- 不要嵌到六级标题:h5 和 h6 被设成了等宽大写的 11px 眉题,而不是更小一号的标题,所以文档恰好是在最需要层级的那个深度上丢掉了字体层级。
示例
the reading column
整片阅读表面,样式是从元素选择器写出来的、不是从类名——因为输入通常不是 JSX,而是从 Markdown 流水线出来的一段字符串,没有组件可以挂类名。用它之前要知道两件事。它的节奏靠的是直接子元素选择器,所以在一串段落外面裹一个 div,哪怕是 display:contents 的,也会让它们全体失去间距。还有,它设定的是版心宽度,不是布局:它自己没有 auto 外边距,所以在一个宽页面里,它会一直贴着起始边,直到某个父元素把它居中。导语由作者用 p.lead 标出来,绝不靠推断,因为一份「谁排在最前就把谁提上去」的样式表,在一篇以图片开头的文章上立刻就错了。
A heading, in the editorial serif
The opening paragraph is marked, not guessed. It is set at the item size in full ink, which is the one place a paragraph is allowed the top of the ladder.
Body copy sits on the reading measure and nothing wider, with a link, some inline code and a strong word in it.
Depth is a hairline and a change of ground, never a blur.The White Reset, law 2
- The list marker is a hairline dash, not a filled disc.
- A filled circle in this system means a status dot.
a markdown block
这边写的正文,接着一段不是这边写的字符串,在同一根列里。Markdown 渲染出来的是一个片段、不是一层容器,而这恰恰是它能成立的原因:Article 的节奏靠的是直接子元素选择器,所以两者之间夹任何东西,都会让每一个块失去间距。它也是这一对里安全的那一半——Markdown 解析成组件,整条路径上没有 dangerouslySetInnerHTML,而 Article 的 html 属性设的是 innerHTML,只能用于可信的标记。headingLevelStart 是 2,因为这根列已经以一个标题开场;留在默认值上,这条评论自己的 # 会成为第二个一级标题。
Notes from the review
One question came back on the packaging, and the answer is worth keeping with the code rather than in the thread it was asked in.
Why the tokens ship as CSS
Because a consumer that already compiles Tailwind should not have to run our build to get --ink. The three portable layers — tokens, semantic, keyframes — are importable on their own:
import '@misoto22/folio/tokens.css'- The compiled bundle is for an app with no build of its own
- The layers are for one that has
past the measure
有三样东西被允许越过 46rem 的版心,因为它们在版心里根本读不了:figure、表格,以及任何被流水线标成 folio-wide 的东西。表格自己没有可以滚动的地方,所以六列会把整个页面推得往旁边跑,除非表格被包进 folio-table-scroll——让流水线去做这个包裹,别指望内容一直很窄。把下面那张表往旁边拖,围着它的那根列纹丝不动。也注意标题的深度:h5 和 h6 被排成等宽的引题,而不是更小的标题,所以一份嵌到六层的文档,恰恰在它最需要层级的那个深度上失去了层级。
What each release changed
The table is wider than the column it sits in, which is the point: it scrolls inside its own wrapper instead of taking the page with it.
| Version | Released | Components | Tokens | Bundle | Tag |
|---|---|---|---|---|---|
| 0.4.0 | 2026-09-02 | 58 | 214 | 38.9 kB | v0.4.0 |
| 0.3.1 | 2026-08-24 | 52 | 209 | 36.1 kB | v0.3.1 |
| 0.3.0 | 2026-08-11 | 49 | 205 | 35.4 kB | v0.3.0 |
无障碍
- 默认渲染成 <article>,所以整篇内容是读者可以直接跳到的地标。
- 每个标题都带 scroll-margin,锚点跳过去时不会被固定的顶栏盖住。
- 样式是不分层引入的,所以在文章里它们赢过组件的分层工具类——正是这一点让一个 Markdown 段落把外边距让给文章的节奏。