Skip to content
Component installs need the registry setup— the Base UI shadcn project, the @vegastack namespace and the Cloudflare Access service token.
VegaStack Design

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
stable
Since
0.1.0
Accessibility pattern
semantic HTML document

Last updated

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-view

The 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"]}>
  {"![Product preview](https://media.example.com/preview.png)"}
</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.

GitHub-flavored markdown

remark-gfm adds tables, ~~strikethrough~~, and - [ ] task lists. Task-list checkboxes are rendered read-only (display only).

Code blocks

Inline code renders as a muted font-mono chip; fenced code blocks render inside a bordered, scrollable <pre>.

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.

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.

API Reference

PropTypeDefaultDescription
allowedImageOriginsreadonly 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.
childrenstringThe markdown string to render. Provided as children (preferred) or via the content prop — when both are present, children wins.
contentstringThe 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

AttributeValues
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 dangerouslySetInnerHTML and escapes raw HTML, so <script>, <img onerror>, and inline event handlers in untrusted input render as plain text rather than executing. rehype-raw is 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 use referrerPolicy="no-referrer".
  • Body text inherits the page text color (text-foreground); links use text-info-text with an underline, keeping a visible, color-independent affordance.
KeyAction
TabMove focus through rendered links in document order.
EnterFollow the focused link.
ContractStates tested
Behaviourdefault, active, checked, disabled, empty, open
Accessibilitysemantic-html
Visualdefault, hover, disabled, empty

Do / Don't

Do
Use MarkdownView for trusted-or-untrusted markdown — it escapes raw HTML, so user-authored comments and descriptions render safely without extra sanitization.
Don't
Add rehype-raw to render embedded HTML — it re-enables the XSS vectors this component exists to avoid. Build a dedicated, separately-sanitized renderer if raw HTML is truly required.

On this page