Agent docs

Markdown

A stored markdown document rendered with the kit's own parts.

A stored markdown document rendered with the kit's own parts: headings that anchor, inline code as a quiet chip, fenced code through CodeBlock, GFM tables through Table, and raw HTML let through only after sanitizing (markdown.tsx:23-62). The playground's docs browser renders every repo doc with it (apps/playground/src/app/docs/markdown-doc.tsx); an app reaches for it wherever a help page, a changelog, or a stored note is markdown on disk.

The canon behind it. docs/design-language/01-foundations.md §A5 (01-foundations.md:27, mono is for identifiers and code, never decoration, the rule behind the inline-code chip and fenced-block policy) and docs/design-language/09-page-composition.md §A4 (09-page-composition.md:25, composition order is fixed, why a stripped H1 hands off to PageTitle rather than rendering twice).

When to reach for it#

Any markdown the app stores or ships and wants to read in place, without a second styling system: a README, a contract doc, a generated report, release notes. It is not a rich-text editor and not a renderMarkdownLite replacement for one bold word in a toast; renderMarkdownLite stays the inline helper for a sentence, Markdown is the document renderer.

Contract#

  • content: string (required): the markdown source. GFM is on (tables, task lists, strikethrough, autolinks).
  • linkAs: the app's router link (NavLinkComponent, the same one Button, AppShell, and Breadcrumbs take), used for hrefs that start with /. External links open in a new tab with rel="noreferrer"; a #hash link scrolls smoothly and updates the address bar; a bare relative path (a repo citation, not a page) renders as a mono span showing the path itself. Every link reads in the primary ink and underlines under the pointer; a link whose text is inline code keeps the code chip, underlined at rest so it reads apart from static code, and the chip takes the primary soft tint on hover (owner, 2026-08-19).
  • html (default true): raw HTML in the source is parsed and passed through rehype-sanitize's default schema plus align, width, height (a README's centered badge block survives; scripts, event handlers, and HTML comments never do). false renders raw HTML as text.
  • animate (default true): the top-level blocks cascade in with stagger-fadeInUp, the house entrance.
  • className: the root wrapper. The root sets text-base and zeroes its first child's top margin, so a doc placed in a Card starts flush with the card's own padding. Prose runs the full width of whatever holds it: the measure is the container's call, not the component's (owner 2026-08-16, doc 55 D3; the docs card had a wider page than its text).

What each construct becomes:

MarkdownRenders as
# to ######A heading with a slug id, and a faint # on hover to the right that links to it
`code`A <code> in the neutral soft chip look, half the badge's paddings, no fixed height, break-all
fenced blockCodeBlock with the fence's language (json, jsonc, ts, tsx, jsx, bash and its aliases, python, yaml, html, csv; anything else prints as text)
GFM tableThe kit Table, compact density, in an overflow-x-auto frame
a file-and-line reference in plain prose (a path, a colon, a number)A muted mono span, so an unbackticked citation still reads as one
raw <p align="center">, <img>, <details>Sanitized and rendered
<!-- comment -->Dropped

Sanctioned combinations#

CombinationProducesWhere usedWhy
Markdown inside a Card, linkAs = Next Link, above it a Breadcrumbs trail and a PageTitleThe docs browser pageapps/playground/src/app/docs/[...slug]/page.tsxThe doc's H1 is stripped server-side and shown as the PageTitle, so the body starts at its first paragraph
Markdown with html={false}Source that shows its own HTML as texta stored note whose author might paste HTMLThe sanitizer is safe, but a note is not a page; showing the tags is the honest render

Banned combinations#

Do not render a doc's H1 twice: strip it and give it to PageTitle, or leave the doc bare, never both. Do not reach for Markdown to render one formatted sentence inside another component; that is renderMarkdownLite.

Before you adopt this#

Five questions to answer before reaching for Markdown.

  1. Does the shell, a parent layout, or a global provider already render this? Not applicable, Markdown is page content; no shell already renders it.
  2. Does this app already ship a local implementation of the same thing? renderMarkdownLite is the local answer for one formatted sentence, not a full doc.
  3. Does this app's kit pin reach the version this component or prop landed in? Not applicable, no version-landed Markdown prop is noted in this doc.
  4. Does the component derive its own accessible name and keyboard path, or must the call site supply them? Not applicable, headings and links derive their own anchors; no control needs a name.
  5. Which canon §A rules bind this surface, and which does the composition break? §A4 hands a stripped H1 to PageTitle, never renders it twice.
@versable-git/ui · reference, canon, and method, read in place