# 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 <div> 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 <article> 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<HTMLElement>`.

## Example — the reading column

```tsx
import { Article } from '@misoto22/folio'

<Article>
  <h2 id="a-heading">A heading, in the editorial serif</h2>
  <p className="lead">
    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.
  </p>
  <p>
    Body copy sits on the reading measure and nothing wider, with{' '}
    <a href="#a-heading">a link</a>, some <code>inline code</code> and a{' '}
    <strong>strong</strong> word in it.
  </p>
  <blockquote>
    Depth is a hairline and a change of ground, never a blur.
    <cite>The White Reset, law 2</cite>
  </blockquote>
  <ul>
    <li>The list marker is a hairline dash, not a filled disc.</li>
    <li>A filled circle in this system means a status dot.</li>
  </ul>
</Article>
```

## Example — a markdown block

```tsx
import { Article, Markdown } from '@misoto22/folio'
import '@misoto22/folio/tokens.css'

<Article>
  <h2>Notes from the review</h2>
  <p>
    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.
  </p>
  <Markdown headingLevelStart={2} idPrefix="reply">
    {REPLY}
  </Markdown>
</Article>
```

## Example — past the measure

```tsx
import { Article } from '@misoto22/folio'

<Article>
  <h2>What each release changed</h2>
  <p>
    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.
  </p>
  <div className="folio-table-scroll">
    <table>
      <caption>Published releases</caption>
      <thead>
        <tr>
          <th scope="col">Version</th>
          <th scope="col">Released</th>
          <th scope="col">Components</th>
          <th scope="col">Tokens</th>
          <th scope="col">Bundle</th>
          <th scope="col">Tag</th>
        </tr>
      </thead>
      <tbody>
        {RELEASES.map((release) => (
          <tr key={release.version}>
            <th scope="row">{release.version}</th>
            <td>{release.date}</td>
            <td>{release.components}</td>
            <td>{release.tokens}</td>
            <td>{release.size}</td>
            <td>{release.tag}</td>
          </tr>
        ))}
      </tbody>
    </table>
  </div>
  <figure>
    <div className="grid h-28 place-items-center rounded-(--radius) bg-(--stone) mono-meta text-(--ink-3-aa)">
      a figure, out past the measure
    </div>
    <figcaption>A figure takes the full width too, and keeps its caption with it.</figcaption>
  </figure>
</Article>
```
