# Article The long-form reading surface — everything a Markdown pipeline emits, in this system’s type. - Group: Surfaces - Import: `import { Article } from '@misoto22/folio'` - Page: https://ui.misoto22.com/components/article/ - Related: diagram, card ## When to reach for it A post, a changelog entry, a document. Not for interface copy: a paragraph inside a card is a paragraph, and this is a whole reading column with its own rhythm. ## Anatomy - **The column** (required) — Whatever as names — article, section or div — tagged data-folio-article, which is what every rule in article.css is scoped to. A 46rem measure and no inline margins of its own, so it sits wherever its parent puts it. - **Blocks** (required) — The DIRECT children, and where the rhythm lives: air above every block, more above a heading, none on the first. A block one level deeper is outside that rule and outside its spacing. - **Rendered HTML** — html, written with dangerouslySetInnerHTML. Present it and children are not rendered at all — the trust boundary is the pipeline that produced the string, because there is nothing here that will catch a script tag on the way through. - **Lead** — p.lead — the standfirst, at --fs-item in full --ink. Marked by the author or the pipeline, never inferred: the stylesheet does not promote whatever happened to come first. - **Wide blocks** — figure, table and .folio-wide are the three things allowed out of the measure, because a six-column table and an image with a subject in it are both unreadable at 46rem. ## Best practices ### Do - Centre it yourself. It sets a measure, not a layout — with no auto margins it sits against the start edge of a wide page until a parent centres it. - Keep every block a direct child: the rhythm is a child combinator, so a
wrapped around a run of paragraphs — even a display:contents one, which removes the box but not the node — costs all of them their spacing. - Have the pipeline wrap a wide table in .folio-table-scroll. A table is allowed out of the measure and has nothing of its own to scroll inside, so eight columns push the whole page sideways instead. - Sanitise before the string arrives, and mark the boundary where you do it: html is set as innerHTML, so a CMS field that reaches this prop unsanitised is stored XSS with a reading measure. ### Don’t - Do not pass html and children together — html wins and the children are dropped. It says so in development now, but html="" is still html, so a pipeline that rendered nothing takes the children down with it. A post that mixes prose with components is two Articles in order, not one holding both. - Do not count on a nested component’s utilities holding inside one: article.css is imported unlayered and beats Tailwind’s @layer utilities whatever the specificity, so any property these rules also set is overridden. A component that must keep one needs an inline style, or a tag the stylesheet does not reach. - Do not nest six heading levels: h5 and h6 are set as mono uppercase 11px eyebrows rather than as smaller headings, so a document loses its type hierarchy at exactly the depth that needed one. ## Accessibility - An
by default, so the piece is a landmark a reader can jump to. - Every heading carries scroll-margin, so an anchored link does not park the heading under a fixed masthead. - The styles are imported unlayered, so inside an article they beat a component’s layered utilities — which is what lets a Markdown paragraph give its margin up to the article’s rhythm. ## Article The long-form reading surface. Everything a Markdown pipeline can emit — headings, prose, lists, tables, quotations, code, figures, footnotes, MathML — set in this system's type, colour and rules. The styles live in `article.css` rather than in a class string, because the input is not JSX: there is no component to hang a class on when the markup arrived as a string, so the contract has to be the element names themselves. That file is published on its own as `@misoto22/folio/article.css`, so a site with its own Markdown pipeline can take the reading surface without taking the components — which is the case this exists for. Three sites hand-rolling a prose stylesheet is three prose stylesheets that drift. Composable either way: pass `html` for a rendered string, or children for real elements — one or the other, never both. `html` wins when both arrive, and says so in development: `html=""` is still `html`, which made losing a page of children a thing that could happen without a word. A post that mixes the two — prose with a `Diagram` dropped into the middle — renders its blocks in order and gives each one an `Article`. Inside one, these rules BEAT a component's utilities: `article.css` is imported unlayered while Tailwind's utilities sit in `@layer utilities`, and an unlayered rule wins over a layered one whatever either one's specificity is. That is the mechanism rather than an accident — it is what lets a `Markdown` paragraph, a `Text` carrying `m-0`, give its margin up to the article's rhythm. A component that has to hold a property inside an article needs an inline style, not a class. ### Props - `html` — `string`. Rendered HTML from a Markdown or MDX pipeline. Trusted markup only. This sets `dangerouslySetInnerHTML`, so it must come from content you control — a repository's own posts, a CMS you author — and never from a reader. Untrusted Markdown has to be sanitised before it reaches here; that is a pipeline decision, not a component one, and a sanitiser bolted on inside a styling primitive would be the wrong place to make it and the easiest place to get it wrong. - `as` — `'article' | 'div' | 'section'`. The element to render. `article` by default; pass `div` for a fragment. Also accepts: `HTMLAttributes`. ## Example — the reading column ```tsx import { Article } from '@misoto22/folio'

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.
``` ## Example — a markdown block ```tsx import { Article, Markdown } from '@misoto22/folio' import '@misoto22/folio/tokens.css'

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.

{REPLY}
``` ## Example — past the measure ```tsx import { Article } from '@misoto22/folio'

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.

{RELEASES.map((release) => ( ))}
Published releases
Version Released Components Tokens Bundle Tag
{release.version} {release.date} {release.components} {release.tokens} {release.size} {release.tag}
a figure, out past the measure
A figure takes the full width too, and keeps its caption with it.
```