Element structure
This page is the catalog of every Markdown element react-native-enriched-markdown renders: what exists, whether it is a block or an inline element, and how elements nest. It is the map that ties the rest of the reference together:
- For the syntax of each element (how you write it), see Core concepts.
- For styling each element through
markdownStyle, see Style properties. - For which targets support each element (React Native and Web), see Feature support.
The syntax and style-property columns below are pointers for scanning - follow the links above for the full detail.
Block vs. inline elements
Every element falls into one of two categories, and the distinction drives how it lays out and how it inherits typography:
- Block elements are structural containers. They occupy whole lines, stack vertically, and each establishes its own typography context (font size, line height, spacing).
- Inline elements live within a block and style a run of text. They inherit the containing block's typography and layer additional styling on top - bold text inside a heading takes the heading's size and adds weight.
Supported elements
Block elements
| Element | Syntax | Style property | Description |
|---|---|---|---|
| Headings | # H1 to###### H6 | h1 - h6 | Six levels of headings |
| Paragraphs | Plain text | paragraph | Default text container |
| Blockquotes | > Quote | blockquote | Quoted content with an accent bar, unlimited nesting |
| Admonitions | > [!NOTE] | blockquote.admonitions | GitHub alert callouts with an icon and title header (requires flavor="github") |
| Code blocks | ``` code ``` | codeBlock | Multi-line code containers; with flavor="github" rendered as a block component with a language header and copy-code button |
| Unordered lists | - Item,* Item, or+ Item | list | Bullet lists with unlimited nesting |
| Ordered lists | 1. Item | list | Numbered lists with unlimited nesting |
| Task lists | - [x] Done,- [ ] Todo | taskList | Interactive checkboxes (requires flavor="github"). There is no separate task-list node - a task item is a list item carrying isTask and taskChecked |
| Thematic breaks | ---, ***,or ___ | thematicBreak | Horizontal rule separator |
| Images |  | image | Block-level images with spacing |
| Videos | <video src="url" /> | video | Native video player (requires flavor="github") |
| Tables | | col | col | | table | GFM tables with alignment support (requires flavor="github") |
| Math blocks | $$...$$ | math | Block-level LaTeX math (display equations) (requires flavor="github" and md4cFlags={{ latexMath: true }}, the latter on by default) |
Inline elements
Inline elements inherit the typography of their parent block and add their own styling on top. For how inheritance resolves and the base block style, see Style inheritance.
| Element | Syntax | Style property | Adds |
|---|---|---|---|
| Bold | **text** (or __text__) | strong | Bold weight, optional color |
| Italic | *text* (or _text_) | em | Italic style, optional color |
| Underline | _text_ | underline | Underline; custom underline color on iOS and web, Android always underlines in the text color (requires md4cFlags={{ underline: true }}) |
| Strikethrough | ~~text~~ | strikethrough | Strike line; custom line color on iOS and web, Android always strikes in the text color (requires flavor="github") |
| Bold + italic | ***text*** (or ___text___) | strong + em | Combined emphasis |
| Links | [text](url) | link | Optional font family, color, background color, underline. Bare URLs autolink too, in both flavors. Per-URL overrides go through linkVariants |
| Inline code | `code` | code | Monospace font, background, optional fontSize |
| Inline images |  | inlineImage | Inline images within the text flow (does not inherit block typography) |
| Inline math | $...$ | inlineMath | LaTeX math rendered within the text flow (requires md4cFlags={{ latexMath: true }}, on by default, plus the native math build) |
| Spoiler | ||text|| | spoiler | Text concealed behind an overlay; tap to reveal. Animated particles by default, or an opaque rectangle with spoilerOverlay="solid". It is always an inline span, never a block |
| Superscript | ^text^ | superscript | Raised text at a reduced font size (requires md4cFlags={{ superscript: true }}) |
| Subscript | ~text~ | subscript | Lowered text at a reduced font size (requires md4cFlags={{ subscript: true }}) |
| Highlight | ==text== | highlight | Highlighted text with a background color (requires md4cFlags={{ highlight: true }}) |
Some delimiters are ambiguous, and how they parse depends on your md4cFlags:
- Underscores (
__text__,_text_) mean bold/italic by default. Enablingmd4cFlags={{ underline: true }}takes_away from emphasis entirely:_text_becomes underline and__text__becomes doubly underlined rather than bold. The*forms are unaffected, which is why they are the safer ones to author with - and why the "(or__text__)" / "(or___text___)" alternatives in the table above do not hold with the flag on. - Single tildes (
~text~) mean nothing by default. Enablemd4cFlags={{ subscript: true }}to parse them as subscript. Double tildes (~~text~~) are strikethrough, but only underflavor="github"- undercommonmarkthey render literally. - Double pipes (
||text||) are always parsed as spoilers, soa || b || crendersbas a spoiler span rather than plain text.
Nesting
Several block elements support unlimited nesting depth with automatic indentation:
- Blockquotes - each level adds a new accent bar.
- Unordered lists - each level indents with
marginLeft. - Ordered lists - each level indents and maintains separate numbering.
Nested lists
Indent a list item to nest it under the one above. Unordered and ordered lists can be nested to any depth, and each level is indented automatically:
- First level
- Second level
- Third level
- Fourth level (unlimited depth!)
1. First item
1. Nested numbered
1. Deep nested
2. Another nested
2. Second item
Lists with block content
List items can contain block elements - fenced code blocks, nested lists, and multiple paragraphs:
1. Install via npm:
```
npm install
```
2. Verify the connection.
Code blocks indent to the item's content column, and the marker is drawn next to the first line even when a code block or nested list is the item's first child.
Nested blockquotes
Add a > for each level of quoting. Every level draws its own accent bar, and a blockquote can hold any other Markdown - paragraphs, lists, or deeper quotes:
> Level 1 quote
> > Level 2 nested
> > > Level 3 nested (unlimited depth!)
The two flavors render blockquotes differently. Under flavor="github" a blockquote is a recursive container block: it draws its own box (padding on all sides, borderRadius, backgroundColor, and the accent border) and splits its content into segments, so a code block, table, or math block inside becomes a real nested block and each nesting level is its own box. Long-pressing one opens the copy menu. Under flavor="commonmark" blockquotes stay inside the single text view and are drawn with spans.
Superscript and subscript
Superscript raises text and subscript lowers it, both at a reduced font size - useful for exponents, footnote markers, and chemical formulas:
E = mc^2^
H~2~O H~2~SO~4~
^14^C dating (isotope notation)
H~3~O^+^ (mixed superscript and subscript)
Superscript and subscript can be nested inside other inline elements such as bold, italic, and links, and inside each other in either direction (x^a~b~c^, x~a^b^c~). Nesting compounds both the font scale and the baseline shift, so two levels deep gets small fast.
Admonitions
With flavor="github", a blockquote whose first line is an alert marker renders as a themed callout with an icon and a title header instead of a plain quote:
> [!NOTE]
> Useful information the reader should know.
> [!TIP]
> A helpful hint.
> [!IMPORTANT]
> Something essential to completing the task.
> [!WARNING]
> Urgent information needing immediate attention.
> [!CAUTION]
> Risks or negative outcomes of an action.
Those five markers are the whole set, and they are case-insensitive - > [!note] works the same as > [!NOTE]. A marker that is not one of the five is not an admonition: the block renders as an ordinary blockquote with the marker left in as literal text.
An admonition is still a blockquote, so it holds any block content - more paragraphs, lists, code blocks, even a video:
> [!WARNING]
> Check these before upgrading:
>
> - the peer dependency range
> - your `enriched-markdown` config block
Admonitions reuse the blockquote's geometry and override only its colors, so they are styled through markdownStyle.blockquote.admonitions - one color pair per type, on top of the blockquote box itself.
Requires both flavor="github" and the md4cFlags.admonitions flag. The flag is on by default, but it is forced off under flavor="commonmark", where > [!NOTE] renders as a plain blockquote with the marker as text.
Images: block vs. inline
Images are automatically detected as block or inline based on context:
- Block images use block-level spacing and sit on their own line.
- Inline images align with the text baseline inside the surrounding run of text.
You don't need to specify which type - the renderer determines it from the image's position. Note that a single newline does not split a paragraph, so an image on its own source line directly below text is still inline; separate it with a blank line to make it a block image.
The rule differs between native and web. On iOS and Android the decision is made from what precedes the image on its line: an image is a block image when nothing has been emitted before it on that line. On web it must be the only child of its paragraph. So  trailing text renders as a block image on native and an inline image on web.
CommonMark has no block image.  is defined as an inline element wherever it appears; the Images row in the Supported elements table above is listed under block elements only to describe what this renderer does with a standalone one.
The split is a rendering decision, not a parsing one: the parser emits the same Image node in both cases, and the renderer marks the ones that are inline. Anything reading the AST - your own traversal, or another CommonMark implementation - sees an inline image either way.
Videos
Videos are embedded with an HTML <video> tag - the tag is the one piece of HTML the parser allowlists, and it is recognized only as a block-level element:
<video src="https://example.com/ocean.mp4" />
The paired form <video src="url"></video> works identically. Only src is read: width, height, controls, autoplay and every other attribute are ignored, so all appearance comes from markdownStyle.video. Playback controls come from the platform player - AVPlayerViewController on iOS, ExoPlayer on Android, and the browser's built-in <video> controls on web.
Quote the URL. An unquoted src ends at the first /, so <video src=https://example.com/a.mp4 /> parses as the URL https: and nothing loads.
On native, requires flavor="github" and the enableVideo build flag (on by default) - see Optional native features. Web has neither knob and always renders the player. No other HTML is rendered on any target: inline HTML is disabled and any other tag is ignored.
A video inside a blockquote or an admonition renders normally. A video inside a list item is different: it is promoted out of the list and rendered as a standalone block above the remaining items, without a bullet or number, which splits the list in two. The promotion happens in the parser, so web behaves the same way.
Line breaks
Newlines follow standard CommonMark semantics by default:
- Blank line - starts a new paragraph.
- Single newline (soft break) - renders as a space; consecutive lines flow together into one wrapped paragraph, matching how GitHub and other CommonMark renderers display Markdown.
- Hard break - end a line with two spaces or a backslash to force a line break within the paragraph.
<br>- not supported. Raw HTML is disabled, so an inline<br>stays in the output as literal text and one on its own line is dropped. Use a hard break, orhardSoftBreaksbelow, instead.
This line and
this line render as one continuous sentence.
A blank line starts a new paragraph.
Two trailing spaces
or a trailing backslash\
force a line break within the paragraph.
Preserving single newlines
When displaying content authored in EnrichedMarkdownTextInput, pressing Enter produces a single newline in the serialized markdown. By default, EnrichedMarkdownText collapses these to spaces (per CommonMark). To preserve them as visible line breaks, enable the hardSoftBreaks flag:
<EnrichedMarkdownText
markdown={markdownFromInput}
md4cFlags={{ hardSoftBreaks: true }}
/>
This forces the parser to treat every soft break as a hard break, so single newlines render as line breaks on all platforms.
Blank lines
By default, CommonMark collapses any run of consecutive blank lines between two blocks into a single paragraph break, so pressing Enter several times renders the same as pressing it once. To keep the extra spacing, enable the preserveBlankLines flag:
<EnrichedMarkdownText
markdown={markdownFromInput}
markdownStyle={{ paragraph: { marginTop: 0, marginBottom: 0 } }}
md4cFlags={{ preserveBlankLines: true }}
/>
Each blank line in the source renders as one empty line, so the output keeps the exact number of blank lines that were typed - four blank lines between two paragraphs render as four empty lines. This makes EnrichedMarkdownText reproduce content authored in EnrichedMarkdownTextInput line for line, which is useful for chat-style apps.
Paragraph margins stack on top of the blank-line spacing. Set paragraph.marginTop and paragraph.marginBottom to 0 in markdownStyle so that spacing is driven purely by blank lines; otherwise even a standard single-blank-line paragraph break will render with extra height.
To reproduce editor content line for line, combine preserveBlankLines with hardSoftBreaks. See the Editor-style text guide for the full recipe.
What happens to an unsupported element
Not every target renders every element, and the three renderers fail in three different ways. This matters most for spoilers on web and for math in a build compiled without the native math engine.
| Target | Unmapped node |
|---|---|
| iOS / macOS | Recurses into the node's children, so the text survives without its styling |
| Android | Falls back to a plain text renderer that emits only the node's own content and never visits children, so an unsupported container loses all of its text |
| Web | Drops the node and its whole subtree |
The clearest case is spoilers on web: the parser emits the node, the web renderer has no entry for it, so the concealed text is deleted rather than concealed - a ||secret|| b renders as a b. Do not put content behind a spoiler if the same Markdown is rendered on web.
Reading the AST
If you traverse the parsed tree yourself, these are the node type names the parser emits:
Document, Paragraph, Text, Link, Heading, LineBreak, Strong, Emphasis, Strikethrough, Underline, Code, Image, Blockquote, UnorderedList, OrderedList, ListItem, CodeBlock, ThematicBreak, Table, TableHead, TableBody, TableRow, TableHeaderCell, TableCell, LatexMathInline, LatexMathDisplay, Spoiler, Superscript, Subscript, Highlight, SoftBreak, BlankLine, Admonition, Video.
Two things are easy to miss: there is no TaskList node (a task item is a ListItem with isTask and taskChecked), and tables expand into five structural node types of their own. Match on the names rather than on any numeric ordinal - the ordinals are insertion order and new types are appended.