MarkdownRenderer
ImplementedXSS-safe Markdown-subset → HTML renderer (h1-h3, bold/italic, inline+block code, ul/ol, blockquote, safe links; no images/tables). Raw HTML inside content is escaped FIRST. Allowlist for links: http(s), mailto, /, #, ./, ../ (javascript: stripped). html mode renders pre-sanitized HTML verbatim.
patterns/molecules/markdownrenderer.htmlXSS-safe Markdown → HTML renderer with an explicit trusted-HTML mode. Raw HTML inside markdown is escaped FIRST (allowlist by construction: only tags the generator emits can exist in the output); link protocols are allowlisted (javascript: degrades to plain text). The html prop renders caller-sanitized HTML verbatim.
MarkdownRenderer
molecules
Markdown mode (default). The container carries the scoped .ep-mdr-content token styles — headings/lists/blockquote/code follow the DS type scale in both light and dark themes (the old `prose prose-sm` classes were ghosts). The empty state renders role="status" aria-live="polite".
Markdown mode (escaped)
Headline act
Paragraph with bold, italic and inline code.
- Doors at 8:00 PM
- All ages welcome
- First support act
- Late-night set
Doors open ninety minutes before showtime.
const ticket = await checkout(cartId);
Safe link and a raw <script>alert(1)</script> that renders as inert text.
Trusted-HTML mode (caller-sanitized)
Persisted rich blob
Sanitized upstream with DOMPurify and rendered verbatim.
Empty state (custom label, FR locale available)
This component supports light and dark mode.
Usage
content is required. Markdown mode escapes raw HTML before parsing — user-supplied markdown can never inject tags. For persisted rich HTML (e.g. RichTextEditor output), sanitize first and pass html: the sanitized blob renders verbatim and wins over content.
import { MarkdownRenderer } from '@epasslive/design-system'
// Markdown mode — raw HTML in user content is escaped (XSS-safe)
<MarkdownRenderer content={"# Headline act\n\n- Doors at 8:00 PM"} />
// Trusted-HTML mode — caller-sanitized blob rendered verbatim
// (sanitize first, e.g. storefront/src/lib/sanitize-html.ts)
<MarkdownRenderer content="" html={sanitizeHtml(richBlob)} />
// Empty state — label defaults to the i18n catalog, override or set locale
<MarkdownRenderer content="" locale="fr" />
<MarkdownRenderer content="" emptyLabel="No description available." />Security
- Escape-first: every inline fragment is HTML-escaped before any transform runs, so the output can only contain tags the generator itself emits (h1–h3, p, ul/ol/li, blockquote, pre/code, strong, em, a).
- Link allowlist: only
http(s):,mailto:,/,#,./,../become anchors; anything else (e.g.javascript:) degrades to plain text. Every generated link carriesrel="noopener noreferrer". - html prop trust boundary: the value is rendered verbatim via
dangerouslySetInnerHTML— the caller MUST sanitize. The canonical sanitizer isstorefront/src/lib/sanitize-html.ts(DOMPurify allowlist). RichTextEditor is the input side of this contract; MarkdownRenderer is the read side.
Props
content*stringMarkdown source. Raw HTML inside is escaped first (XSS-safe). Subset: h1–h3, bold, italic, inline + fenced code, ul/ol, blockquote, safe links — images and tables are NOT supported (literal passthrough).
htmlstringPre-sanitized HTML rendered verbatim via dangerouslySetInnerHTML. TRUST BOUNDARY: the caller MUST sanitize (e.g. DOMPurify — see storefront/src/lib/sanitize-html.ts). html wins over content when both are non-empty.
emptyLabelstringi18n catalog: markdownRenderer.emptyShown when content is empty. Defaults to the DS i18n catalog value for the active locale ("Nothing to show yet" / "Rien à afficher pour le moment"); an explicit value always wins.
locale"en" | "fr""en"Locale used to resolve the built-in empty-state label from i18n/{en,fr}.json (markdownRenderer.empty).
classNamestringAdditional CSS classes on the container div (both states).
idstringStable id for the container (defaults to an auto-generated markdown-* id from React.useId).
Props
MarkdownRenderer
contentstring—Markdown source — raw HTML inside is escaped (XSS-safe).
emptyLabelstringi18n catalog (`markdownRenderer.empty`)Shown when content is empty — defaults to the DS i18n catalog value for the active locale ('Nothing to show yet' / 'Rien à afficher pour le moment').
htmlstring—Pre-sanitized HTML rendered verbatim via dangerouslySetInnerHTML, bypassing markdown parsing (§126 Wave 3 #4). TRUST BOUNDARY: caller must sanitize; html wins over content when both non-empty.
locale'en' | 'fr''en'Resolves the built-in empty-state label from the DS i18n catalog; an explicit emptyLabel always wins.
classNamestring—Additional CSS classes on the container.
idstring—Stable container id.
<MarkdownRenderer />Accessibility
ARIA
1 ruleType
1 ruleUsage
Import
import { MarkdownRenderer } from "@epasslive/design-system"Example 1
<MarkdownRenderer content={"## Doors open\n- 19:00 doors\n- 20:00 show"} />Example 2
<MarkdownRenderer content="" html={sanitizeHtml(description)} emptyLabel="Nothing to show yet." />Anti-patterns
— Do not pass unsanitized HTML into html — sanitize at the trust boundary first
— Do not wrap it in hand-rolled prose utilities — scoped token styling is built in