Skip to content

Markdown rendering

How Display.dev renders Markdown — syntax and reading experience.

On this page

Display.dev renders Markdown to themed HTML on publish. The viewer experience aligns with the Markdown Experience Guidelines (MDXG).

What renders out of the box

The base is CommonMark. Standard syntax works as you'd expect — headings, paragraphs, lists (ordered, unordered, nested), blockquotes, links, images, inline and fenced code, horizontal rules. On top of that:

  • Tables — pipe-delimited, GFM-style.
  • Strikethrough~~text~~.
  • Auto-linking — bare URLs and email addresses become clickable links. Wrap a URL in [text](url) only when you want different link text.

Themes

Pick a theme on publish with --theme. Available:

  • github (default)
  • modest
  • pandoc
  • air
  • solarized

Every theme adapts to the viewer's dark-mode preference. The --theme flag has no effect when publishing HTML.

bash
dsp publish report.md --theme pandoc

Heading anchors

Every heading gets an id derived from its text. A # permalink appears on hover — click it to copy a link to that section:

https://<org>.dsp.so/<shortId>-<slug>#section-title

The id strips punctuation and preserves Unicode letters. Repeated heading text gets a numeric suffix (setup, setup-1). Multi-page documents extend the URL with a page slug.

Multi-page documents

An artifact with two or more # H1 headings automatically renders as a multi-page document. A navigation panel on the left titled "On this doc" lists every page, the active page expands to show its outline, and a prev/next footer at the bottom of the page connects them. One # H1 (or none) renders as a single scrolling page.

Display.dev splits on # H1 only, not ## H2. The MDXG spec allows either; we deviate because a published artifact is the page — an inline # H1 typically signals a major section, while H2s typically subdivide a single document. Content before the first # H1 becomes an Introduction page.

URLs

In single-page documents the URL fragment is the heading id (#section-title). In multi-page documents it identifies the page:

FragmentBehaviour
(none)First page
#<page-slug>Open that page
#<page-slug>--<heading-id>Open that page and scroll to the heading

Old #heading-id bookmarks continue to work — they resolve to the page containing the heading.

Keyboard

  • Arrow Up / Arrow Down move focus through the page list.
  • Enter opens the focused page.
  • Page Up / Page Down move to the previous / next page from anywhere outside form fields.
  • Esc returns focus to the article body.

The top-bar Copy as Markdown button becomes per-page in multi-page documents — see Source export.

Table of contents

Single-page documents with two or more H2 sections get a collapsible table of contents on the left that highlights the current section as you scroll. Don't write your own — Display.dev builds it from H2 and H3 headings.

Multi-page documents replace the TOC with a per-page outline inside the side nav: when a page is active, its H2 and H3 headings expand inline under its row.

Cmd+F (or Ctrl+F) opens an in-document find input that cycles through matches with a counter. In multi-page documents it walks hidden pages too and shows the page name next to each match, so matches outside the active page aren't skipped.

Cmd+K (or Ctrl+K) opens a palette of ranked pages with snippets. The palette is only available in multi-page documents.

Footnotes

[^label] references become superscript links to a footnote section at the bottom of the document. Footnotes can be referenced multiple times; each reference gets a back-link in the definition.

markdown
The migration[^why] took a week.
 
[^why]: Originally scheduled for one day. Estimates remain hard.

A reference with no matching definition renders as literal [^label] text. A definition with no reference is dropped silently.

In multi-page documents, footnote definitions are redistributed per page — each page that references a footnote gets its own copy at the bottom, with back-links pointing to the reference on that page.

Smart punctuation

These character sequences get transformed at publish time:

  • --- → em dash
  • -- → en dash
  • ... → ellipsis
  • (c), (C) → ©
  • (r), (R) → ®
  • (tm), (TM) → ™
  • +- → ±
  • Straight quotes around words → curly quotes

Fenced code blocks bypass the transform — code samples render exactly as typed.

Code blocks

Fenced blocks (```language) render in a monospace block with the theme's background colour.

Math (KaTeX)

Wrap math in $…$ for inline, $$…$$ for a centred display block. The LaTeX is rendered to static HTML at publish time — readers don't load a math runtime in the browser.

markdown
The loss is $\mathcal{L} = -\sum_i y_i \log \hat{y}_i$.
 
$$
\frac{\partial \mathcal{L}}{\partial z_j} = \hat{y}_j - y_j
$$

Supported: the KaTeX function set. MathML, asciimath, and other math syntaxes are not.

Invalid LaTeX shows as an inline error instead of breaking the page. Prose like $5 fee or It costs $100. is left alone — math only parses when the opening $ has whitespace or punctuation before it and the closing $ has whitespace or punctuation after it. Links inside math (\href) are disabled.

Diagrams (Mermaid)

Fence a block with mermaid and the diagram renders in the viewer's browser. Flowcharts, sequence diagrams, state diagrams, ER diagrams, Gantt charts, and the rest of the Mermaid syntax are supported.

markdown
```mermaid
flowchart LR
  A[Markdown] --> B[Display.dev] --> C[Themed HTML]
```

The diagram follows the page theme — toggle dark mode and the colours update without a reload. Display.dev hosts the Mermaid library itself — no third-party CDN, no external fonts — and only loads it when a page contains a mermaid block.

Invalid syntax shows an error in place of the diagram instead of breaking the page. Clickable links inside diagrams are disabled.

Raw HTML

Raw HTML inside Markdown is escaped, not executed. <script> blocks, event-handler attributes (onerror, onclick, …), and raw <img> tags all render as visible source. This is intentional — artifacts are served to viewers other than the publisher. (Markdown image syntax — ![alt](url) — works normally.)

To ship interactive content, publish HTML directly instead of Markdown.

What's not supported

A few syntaxes and features from other Markdown viewers aren't supported here:

  • Task lists- [ ] item renders as literal text, not a checkbox.
  • Definition lists — the term / : definition two-line pattern renders as plain text.
  • GitHub-style alerts> [!NOTE] renders as a regular blockquote, without callout styling.
  • Embedded media<video>, <audio>, and <iframe> are escaped along with all raw HTML. Publish as HTML to embed media.
  • Code-block syntax highlighting — fenced blocks render as plain monospace. Pre-highlight as HTML and publish that file directly if you need it.
  • Per-block code copy buttons — the in-viewer Copy button copies the whole document or the active page, not individual code blocks.

Source export

Display.dev keeps the original Markdown alongside the rendered HTML. Two ways to get it back:

  • dsp export <shortId> — returns the original Markdown.
  • The Copy button in the viewer's top bar — copies the source to the clipboard. On single-page documents it's labelled Copy as Markdown. On multi-page documents it becomes Copy this page (or Copy intro on the Introduction page), with a secondary Copy whole doc link for the full source.

If the artifact was published as HTML, dsp export <shortId> --format markdown converts the HTML back to Markdown.

Was this page helpful?