Components/Content
Markdown
Markdown renders a string of markdown as formatted text — headings, lists, tables, links and fenced code — styled to match the rest of the interface. It is how prose that arrives already written, most often a model’s answer, reaches the reader looking like part of the product, with anything unsafe stripped out on the way.
import { Markdown } from '@growth-protocol-ai/alpha';Supplier risk summary
Kestrel Components is single-source, with no qualified alternate. Two other suppliers carry exposure worth watching, neither of them urgent.
- Kestrel Components — single-source, no qualified alternate
- Nordwerk GmbH — dual-source, alternate at partial capacity
- Halcyon Materials — dual-source, no immediate concern
| Supplier | Region | Spend |
|---|---|---|
| Kestrel Components | APAC | $1.24m |
| Nordwerk GmbH | EMEA | $860k |
| Halcyon Materials | AMER | $412k |
The same total comes back from a query filtered to the region:
SELECT SUM(spend) AS apac_spend
FROM supplier_spend
WHERE region = 'APAC';
See the supplier risk model for how the exposure score is derived.
When to use
Reach for it when the text was authored somewhere other than your component tree — a model’s answer, a release note, a description somebody typed — and you want it to read like the product rather than like a raw string. Pass the source through as it arrives: an empty or half-finished value renders sensibly, so there is nothing to hold back until the whole answer has landed.
When not to use
Do not use it for words you are writing yourself in JSX, where markup you control is clearer than a parser between you and your own sentences. Where the reader is the one writing the markdown, the control is a text area and this renders the preview beside it — the two are easy to mix up because both hold the same string, so ask which side of it you are on: collecting, or showing. And a response that has not begun arriving wants a skeleton in its place, because this renders nothing at all rather than a placeholder.
variant3 values
Variant is how much typography the component brings, not how big it is. Regular and compact both keep the structure — list markers, code background, table borders, the quote rule — and differ only in how large and how spaced the text is. Plain drops the typography layer entirely, so the surface around it sets the type instead.
Kestrel Components
Single-source on a part with a long lead time, and no qualified alternate today.
- Spend: $1.24m
- Region: APAC
- Exposure: high
Kestrel Components
Single-source on a part with a long lead time, and no qualified alternate today.
- Spend: $1.24m
- Region: APAC
- Exposure: high
Kestrel Components
Single-source on a part with a long lead time, and no qualified alternate today.
- Spend: $1.24m
- Region: APAC
- Exposure: high
| variant | When to use |
|---|---|
regular | The default. Body text at the interface’s usual measure with headings scaled above it, for markdown that is the content of its surface rather than a note inside one. |
compact | Denser: smaller headings and body text, tighter list and table spacing. For an answer rendered inside a surface that is already busy — a card body, a chat bubble, a side panel. |
plain | No headings or body type of its own; only the structural rules survive. Reach for it inside a surface that already sets its own typography, and nowhere else. |
Kestrel Components
Single-source on a part with a long lead time. The alternate is **not yet qual
SELECT SUM(spend) AS apac_spend
FROM supplier_sp
| State | When to use |
|---|---|
Nothing to render | A null or empty source renders no element at all — not an empty wrapper — so a value that has not started arriving can be handed straight over without a guard around it. |
Mid-stream | A source that stops mid-document renders whatever has parsed so far. An unclosed fence is already a code block, so text arriving inside one does not jump from paragraph to code when the closing fence lands; an unpaired ** shows as two asterisks until its partner turns up. |
Raw HTML in the source is sanitized rather than rendered, and that is not configurable — it is what makes a model’s answer safe to put straight on the page. A renderer you pass through components sits inside that boundary, so markup you return there is yours to keep safe.
Links open in a new tab, because content the reader did not navigate to should not replace what they were reading. If you supply your own a renderer you take that with it, so put target and rel back unless you mean to drop them.
The heading levels in the source land on the page as real headings, so an answer dropped into a section takes over that section’s outline. Start the source one level below the heading it sits under, or a screen-reader user hears a hierarchy that skips.
Reach for plain only inside a surface that already sets its own typography. On its own it renders headings and body text at whatever the page happens to supply, which is rarely what anyone wanted.
Fenced code is highlighted only once the source contains a fence, and the grammar for it loads on demand — so expect a plain block on the first paint and colour a moment later. It is legible either way, which is why nothing waits for it.
The wrapper takes no width of its own, so the column around it sets the measure. A fenced block scrolls inside itself rather than stretching that column, but a wide table compresses its cells instead — give a table room rather than expecting it to scroll.
| Prop | Type | Default | Notes |
|---|---|---|---|
children | string | null | — | The markdown source, GitHub-flavoured — tables, strikethrough and bare autolinks parse alongside the CommonMark core. Renders nothing when it is null or empty. |
variant | "regular" | "compact" | "plain" | "regular" | How much typography the component brings. See the variant table above. |
components | Components | — | react-markdown’s per-element renderers, shallow-merged over the single default this component sets. An entry replaces that tag’s renderer rather than wrapping it. |