Markdown View
Render a markdown string to safe, token-styled HTML — headings, lists, code, blockquotes, links, and GFM tables — XSS-safe with no raw HTML.
- Status
- Since
0.1.0- Accessibility pattern
- semantic HTML document
Last updated
Deployment summary
Render a markdown string to safe, token-styled HTML. The agent wrote this
changelog (opens in new tab) using inline code and the blocks below.
What changed
- Migrated tokens to the warm-neutral ramp
- Rationed colour to a neutral
primaryandinfo - Flattened surfaces to a single border
function deploy(target: string) {
return `Deploying to ${target}…`;
}
Links use the
infoblue, headings settle at weight 500 — never bold.
Install
Add Markdown View from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.
pnpm dlx shadcn@latest add @vegastack/markdown-viewThe same command installs the registry items it composes: @vegastack/checkbox, @vegastack/code-block.
It also adds the sanctioned engines to your package.json: react-markdown (markdown renderer engine), remark-gfm (GFM parser plugin).
Usage
import { MarkdownView } from "@/components/ui/markdown-view";
<MarkdownView>{`# Hello\n\nThis is **markdown**.`}</MarkdownView>;Pass the markdown as children (a string) or via the content prop when the
source comes from a data field:
<MarkdownView content={task.description} />MarkdownView is built on react-markdown
with remark-gfm (tables, strikethrough, task lists, autolinks). It builds a
React tree directly — never dangerouslySetInnerHTML — so any raw HTML in the
source is escaped to inert text. It is server-safe (no 'use client') and
renders nothing for empty/whitespace input.
Relative image paths render normally. Absolute and protocol-relative remote images are blocked by default so untrusted Markdown cannot create tracking requests. When a trusted media host is required, allow its exact origin; allowed remote images send no referrer:
<MarkdownView allowedImageOrigins={["https://media.example.com"]}>
{""}
</MarkdownView>Examples
Default
Headings, paragraphs, inline code, links, lists, and blockquotes — styled by the
shared prose recipe from @vegastack/design, worn as one class on the root (no
@tailwindcss/typography dependency). TextEdit
wears the same string, so rendered markdown and edited rich text are the same
typography rather than two that agree by review.
Deployment summary
Render a markdown string to safe, token-styled HTML. The agent wrote this
changelog (opens in new tab) using inline code and the blocks below.
What changed
- Migrated tokens to the warm-neutral ramp
- Rationed colour to a neutral
primaryandinfo - Flattened surfaces to a single border
function deploy(target: string) {
return `Deploying to ${target}…`;
}
Links use the
infoblue, headings settle at weight 500 — never bold.
GitHub-flavored markdown
remark-gfm adds tables, ~~strikethrough~~, and - [ ] task lists. Task-list
checkboxes are rendered read-only (display only).
GitHub-flavored markdown
A table, strikethrough, and a task list:
| Feature | Status |
|---|---|
| Tables | Done |
| Task lists | Done |
- Render tables
- Render task lists
- Add raw HTML (never)
Code blocks
Inline code renders as a muted font-mono chip; fenced code blocks render inside
a bordered, scrollable <pre>.
Code blocks
Inline const x = 1 and a fenced block:
function greet(name: string) {
return `Hello, ${name}`;
}
Links
External links (those starting with http(s)://) open in a new tab with
target="_blank" and rel="noreferrer noopener"; relative/internal links stay
in the same tab. The link color (text-info-text) and underline are identical in
both cases.
Links adapt to their destination:
- External link (opens in new tab) opens in a new tab.
- Internal link stays in the same tab.
Full element set
A consolidated sample exercising every styled element — all six heading levels, ordered and unordered lists, a horizontal rule, an image, and inline emphasis — so the complete prose surface renders in one view.
Heading 1
Heading 2
Heading 3
Heading 4
Heading 5
Heading 6
A paragraph with bold, italic, strikethrough, and inline code.
- First ordered step
- Second ordered step
- Third ordered step
- Unordered item
- Another item
A blockquote settles at the muted foreground.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
allowedImageOrigins | readonly string[] | [] | Exact HTTP(S) origins allowed to load Markdown images. Relative/same-site paths are always allowed. Absolute and protocol-relative remote images are blocked by default to prevent untrusted Markdown from creating tracking requests. Allowed remote images use a no-referrer policy. |
children | string | — | The markdown string to render. Provided as children (preferred) or via the
content prop — when both are present, children wins. |
content | string | — | The markdown string to render. Alternative to children; useful when the
source comes from a data field rather than JSX text. |
Data attributes and CSS variables on MarkdownView
| Attribute | Values |
|---|---|
data-slot | "markdown-view" |
Accessibility
- Markdown maps to semantic HTML —
#→<h1>,-→<ul><li>,>→<blockquote>,```→<pre><code>— so the document structure is exposed to assistive technology and heading navigation works. - XSS-safe by construction: react-markdown never uses
dangerouslySetInnerHTMLand escapes raw HTML, so<script>,<img onerror>, and inline event handlers in untrusted input render as plain text rather than executing.rehype-rawis intentionally not added. - Links open in a new tab with
rel="noreferrer noopener"(no reverse tabnabbing); unsafe URL protocols (e.g.javascript:) are dropped. - Remote images are blocked unless their exact HTTP(S) origin appears in
allowedImageOrigins; allowed remote images usereferrerPolicy="no-referrer". - Body text inherits the page text color (
text-foreground); links usetext-info-textwith an underline, keeping a visible, color-independent affordance.
| Key | Action |
|---|---|
| Tab | Move focus through rendered links in document order. |
| Enter | Follow the focused link. |
| Contract | States tested |
|---|---|
| Behaviour | default, active, checked, disabled, empty, open |
| Accessibility | semantic-html |
| Visual | default, hover, disabled, empty |