# 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.
Published releases
Version
Released
Components
Tokens
Bundle
Tag
{RELEASES.map((release) => (
{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.
```