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 oneButton,AppShell, andBreadcrumbstake), used for hrefs that start with/. External links open in a new tab withrel="noreferrer"; a#hashlink 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(defaulttrue): raw HTML in the source is parsed and passed throughrehype-sanitize's default schema plusalign,width,height(a README's centered badge block survives; scripts, event handlers, and HTML comments never do).falserenders raw HTML as text.animate(defaulttrue): the top-level blocks cascade in withstagger-fadeInUp, the house entrance.className: the root wrapper. The root setstext-baseand 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:
| Markdown | Renders 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 block | CodeBlock with the fence's language (json, jsonc, ts, tsx, jsx, bash and its aliases, python, yaml, html, csv; anything else prints as text) |
| GFM table | The 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#
| Combination | Produces | Where used | Why |
|---|---|---|---|
Markdown inside a Card, linkAs = Next Link, above it a Breadcrumbs trail and a PageTitle | The docs browser page | apps/playground/src/app/docs/[...slug]/page.tsx | The 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 text | a stored note whose author might paste HTML | The 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.
- Does the shell, a parent layout, or a global provider already render this? Not applicable, Markdown is page content; no shell already renders it.
- Does this app already ship a local implementation of the same thing?
renderMarkdownLiteis the local answer for one formatted sentence, not a full doc. - 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.
- 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.
- Which canon §A rules bind this surface, and which does the composition break? §A4 hands a stripped H1 to
PageTitle, never renders it twice.