Syntax Reference
This page documents every markdown syntax construct and the exact Domphy element shape @domphy/markdown produces for each one. Use it to understand what body contains and how to write patches or styles that target specific elements.
Headings
ATX headings (# through ######) become h1 through h6 with an id attribute derived from the heading text:
# Level 1
## Level 2
### Level 3// body
[
{ h1: ["Level 1"], id: "level-1" },
{ h2: ["Level 2"], id: "level-2" },
{ h3: ["Level 3"], id: "level-3" },
]The id is the slug produced by defaultSlugify (or your anchorSlugify option). Duplicate heading text gets a numeric suffix: first occurrence keeps the base slug, subsequent ones get -1, -2, and so on.
# Intro
# Intro[
{ h1: ["Intro"], id: "intro" },
{ h1: ["Intro"], id: "intro-1" },
]Paragraphs
Blocks of text become p elements. The children array holds plain-text strings and any inline elements:
A paragraph with **bold**, _italic_, and `code`.[{
p: [
"A paragraph with ",
{ strong: ["bold"] },
", ",
{ em: ["italic"] },
", and ",
{ code: "code" },
".",
],
}]Inline emphasis
| Markdown | Domphy element |
|---|---|
**bold** or __bold__ | { strong: [...children] } |
_italic_ or *italic* | { em: [...children] } |
~~struck~~ | { s: [...children] } |
`inline code` | { code: "text" } |
Emphasis elements can be nested: **_bold italic_** becomes { strong: [{ em: ["bold italic"] }] }.
Links
[Domphy](https://domphy.dev "Homepage"){ a: ["Domphy"], href: "https://domphy.dev", title: "Homepage" }All attributes that markdown-it emits are copied as element properties. Auto-linked bare URLs (enabled by default via linkify: true) produce the same shape.
Images
{ img: null, src: "/img/diagram.png", alt: "A diagram", title: "Figure 1" }The img property is null because images are void elements. The alt text is extracted from the image's inline token children (markup stripped to plain text).
Unordered lists
- one
- two
- three{
ul: [
{ li: ["one"], _key: 0 },
{ li: ["two"], _key: 1 },
{ li: ["three"], _key: 2 },
],
}Each li element carries a _key number equal to its zero-based position among siblings. This gives Domphy stable keys for list diffing. Items whose content is a tight paragraph (no blank line between items) expose the text directly as li children; items separated by blank lines get a p child inside li.
Ordered lists
1. first
2. second
3. third{
ol: [
{ li: ["first"], _key: 0 },
{ li: ["second"], _key: 1 },
{ li: ["third"], _key: 2 },
],
}Nested lists
- parent
- child one
- child two{
ul: [
{
li: [
"parent",
{
ul: [
{ li: ["child one"], _key: 0 },
{ li: ["child two"], _key: 1 },
],
},
],
_key: 0,
},
],
}The nested list appears as the last child inside its parent li. Ordered and unordered lists can be mixed at any depth.
Blockquotes
> Quoted text.
> Second line.{
blockquote: [
{ p: ["Quoted text. Second line."] },
],
}Blockquotes nest: >> deeply quoted produces { blockquote: [{ blockquote: [{ p: [...] }] }] }.
Fenced code blocks
```ts
const x: number = 42
```{
pre: [{
code: "const x: number = 42\n",
dataLanguage: "ts",
class: "language-ts",
}],
}codeholds the raw source text, HTML-escaped when no highlighter is supplied.dataLanguageis Domphy's camelCase form of thedata-languageattribute, set to the fence's language identifier.classis set to"language-{lang}"so CSS-based highlighters can target it.
When a highlighter is provided, code contains the highlighted output (a string of inner HTML or a DomphyElement) instead of the escaped raw text.
Indented code blocks
Four-space-indented code blocks produce the same pre > code shape but carry no language metadata:
const x = 1;
const y = 2;{
pre: [{ code: "const x = 1;\nconst y = 2;\n" }],
}dataLanguage and class are absent because indented code blocks carry no language annotation.
GFM tables
| Name | Age |
| ----- | --- |
| Alice | 30 |
| Bob | 25 |{
table: [
{
thead: [{
tr: [
{ th: ["Name"] },
{ th: ["Age"] },
],
}],
},
{
tbody: [
{ tr: [{ td: ["Alice"] }, { td: ["30"] }] },
{ tr: [{ td: ["Bob"] }, { td: ["25"] }] },
],
},
],
}The full table > thead/tbody > tr > th/td structure is preserved.
Column alignment
Alignment markers (:-, :-:, -:) add a style object to each cell in that column:
| Left | Center | Right |
|:-----|:------:|------:|
| L | C | R |{ th: ["Left"], style: { textAlign: "left" } }
{ th: ["Center"], style: { textAlign: "center" } }
{ th: ["Right"], style: { textAlign: "right" } }Cells without an alignment marker have no style property.
Horizontal rule
---{ hr: null }hr is null because <hr> is a void element.
Line breaks
A regular newline inside a paragraph becomes a soft break — rendered as a single space:
line one
line two{ p: ["line one", " ", "line two"] }Two trailing spaces followed by a newline produce a hard break — a void br element:
line one
line two{ p: ["line one", { br: null }, "line two"] }Raw HTML
Block HTML
A block of raw HTML becomes a div element whose content is the raw HTML string. When Domphy renders it, the string is emitted verbatim as inner HTML:
<figure>
<img src="/chart.png" alt="Chart">
<figcaption>Monthly visits</figcaption>
</figure>{
div: "<figure>\n <img src=\"/chart.png\" alt=\"Chart\">\n <figcaption>Monthly visits</figcaption>\n</figure>",
}Inline HTML
Raw HTML inline is a per-fragment operation: each open or close tag fragment is individually wrapped in a { span: fragment }. The walker does not reconstruct the nested element tree from raw inline HTML:
Text with <strong>bold</strong> inline.{
p: [
"Text with ",
{ span: "<strong>" },
"bold",
{ span: "</strong>" },
" inline.",
],
}For content that needs inline styling, prefer standard markdown emphasis syntax. Reserve raw HTML for block-level elements that have no markdown equivalent (e.g. <figure>, <details>, <video>).