Element structure
This page is about what the renderer does with your Markdown: which constructs it understands, how they nest, and where the result differs from what the source looks like. For the properties that control their appearance, see Style properties.
Block & inline elements
The renderer sorts every element into one of two kinds, and the distinction drives both styling and layout:
- Block elements occupy their own vertical space and carry their own font, color, and margins. A paragraph, a heading, a list, a quote, a code block.
- Inline elements live inside a line of text and modify a run of it. Bold, italic, links, inline code. They inherit size and line height from the block they sit in and override only what makes them distinct - see Style inheritance.
The document renders into native text views, with block structure drawn by spans rather than by separate views. Consecutive headings, paragraphs, lists, quotes, and code blocks share one text view, which is why a selection can run from the first heading to the last list item without interruption. A few blocks get a view of their own instead: tables, top-level code blocks set to scroll, and, with the LaTeX math plugin, display-math blocks. A selection stops at them, and you copy them with a long-press instead.
Block elements
| Element | Markdown | Notes |
|---|---|---|
| Heading | # H1 … ###### H6 | Six levels, each styled independently |
| Paragraph | Text separated by a blank line | |
| Blockquote | > quoted | Nests |
| Admonition | > [!NOTE] | Needs MarkdownOptions(admonitions = true) - see below |
| Unordered list | - item | Nests |
| Ordered list | 1. item | Nests |
| Task list | - [ ] / - [x] | Tappable - see onTaskListItemToggle |
| Table | GFM pipe table | Always enabled - see below |
| Fenced code block | ```kotlin | Long lines wrap or scroll; colored by language with CodeHighlightPlugin - see below |
| Block image |  first on its line | See below |
| Thematic break | --- | |
| Math block | $$...$$ | Needs LatexMathPlugin - see Math |
Inline elements
| Element | Markdown | Notes |
|---|---|---|
| Strong | **bold**, __bold__ | |
| Emphasis | *italic*, _italic_ | |
| Underline | _text_, __text__ | Needs MarkdownOptions(underline = true), and replaces the italic/bold meaning of those markers |
| Strikethrough | ~~struck~~, ~struck~ | A single ~ means subscript instead when MarkdownOptions(subscript = true) is set |
| Inline code | `code` | |
| Link | [text](url) | Inert until you handle onLinkClick |
| Autolink | <https://…>, or a bare URL | Bare URLs need permissiveAutolinks, which is on by default |
| Inline image |  after text | See below |
| Superscript | ^text^ | Needs MarkdownOptions(superscript = true) |
| Subscript | ~text~ | Needs MarkdownOptions(subscript = true) |
| Highlight | ==text== | Needs MarkdownOptions(highlight = true) |
| Inline math | $...$ | Needs LatexMathPlugin - see Math |
| Spoiler | ||text|| | Always parsed; concealed until tapped - see spoilerOverlay and spoiler |
Raw HTML is not rendered: inline HTML such as <b> stays literal text, and HTML blocks are dropped from the output.
The _ markers are context-dependent. By default _italic_ and __bold__ mean the same as *italic* and **bold**, but MarkdownOptions(underline = true) reinterprets both _text_ and __text__ as underline, so under that option they no longer produce emphasis or strong. The asterisk markers * and ** always mean emphasis and strong, whatever the option.
Nesting
Some Markdown blocks are container blocks: lists and blockquotes can hold other blocks inside them.
Nested lists
Indent a list item to nest it. Each level adds list.marginStart of indent, and unordered levels change their bullet shape so depth stays readable - a filled disc at the first level, a hollow circle at the second, and a square at the third and every level below:
- First level
- Second level
- Third level
Ordered and unordered lists can nest inside each other freely.
Lists with block content
A list item can hold more than one line. Indent the continuation to the item's text column and it stays part of that item:
1. First step
A second paragraph inside the same item.
2. Second step
Paragraphs, quotes, and code blocks all work as item content this way. Tables do not: a table inside a list item or a blockquote is not rendered. Keep tables at the top level of the document.
Nested blockquotes
Stack > markers to nest quotes. Each level draws its own accent bar, so the depth is visible:
> Outer quote
>
> > Inner quote
Admonitions
A blockquote whose first line is one of the five GitHub alert markers renders as a themed callout: the usual quote geometry plus a header row with a tinted icon and a bold title.
> [!WARNING]
> This action cannot be undone.
The five types are [!NOTE], [!TIP], [!IMPORTANT], [!WARNING], and [!CAUTION]. Colors come from the admonitions style block, which sits inside blockquote { admonitions { } }.
This is an opt-in parser extension. Without MarkdownOptions(admonitions = true) the marker stays literal text inside an ordinary quote.
Two behaviors worth knowing:
- An admonition nested inside a list item falls back to a plain blockquote, with no header. This is a known limitation.
- A body-less quote renders nothing at all, admonition or not - a header with no text beneath it is not worth the vertical space.
Copying a callout reproduces its > [!NOTE] marker, and TalkBack announces the header ahead of the body.
Tables
GFM pipe tables render as real views inside the text rather than as monospaced ASCII, and need no option - they are always parsed:
| Package | Platform |
| ------------------------- | :------: |
| enriched-markdown-android | Android |
- Each column sizes to its content, between 60dp and 300dp wide (plus cell padding). A cell longer than that wraps rather than pushing the column wider.
- A table wider than the view scrolls horizontally in place, without moving the rest of the document.
table { horizontalOverflow }lets the scrolling table extend that far past the text column on each side, for example to reach the screen edge. - Column alignment comes only from the separator row (
:--,:-:,--:).table { alignment }is something else: it places a table that is narrower than the view at the start, center, or end of the line. - A table is its own segment, so a selection cannot run across it, and a table nested inside a list item or blockquote is not rendered.
- Cells take inline styling - bold, italic, inline code, strikethrough, and tappable links.
Colors, borders, padding and the header row are all styled through the table block.
Code blocks
By default, a fenced code block wraps its long lines, and its code is part of the text selection like any other text.
Pass codeBlockLayout = CodeBlockLayout.Scrollable to keep lines intact instead:
- A header bar above the code shows the fence's language name (
```pyreads "Python") and a copy button that copies the code. - Lines never wrap. A block wider than the view scrolls horizontally in place, inside its box, without moving the rest of the document.
- This applies to code blocks at the top level of the document. A code block inside a list item or a quote still wraps its lines.
- Long-press a scrolling code block to copy its code, or to copy it as fenced Markdown. It is not part of the text selection.
- The block splits the text around it into separate selections, as a table does. Select all then Copy as Markdown copies only the text up to the nearest block, rebuilt from what is shown rather than the document's original Markdown, and leaves the code out. Long-press the block to copy the code itself.
In either layout the language label is parsed. To color code by it, add CodeHighlightPlugin - see Code highlighting.
Images: block vs. inline
The same  syntax renders two different ways, and which one you get depends on where the image sits on its line:
- Block image - nothing comes before the image on its line: it opens a paragraph, or follows a hard line break. It is laid out on its own, at
image.height, spanning the container width. This holds even when text follows it on the same line. - Inline image - text comes before it on the line. It is drawn in the text flow at
inlineImage.size, sized to sit on the line like a large glyph.
This differs from CommonMark, where every image is inline. Markdown has no block image syntax; the split is how this renderer lays images out.

Some text with an  in it.
So putting an image on its own line is the whole gesture - there is no separate syntax to learn. Remote images are downloaded and cached; see Image caching.
Line breaks
By default Markdown reflows text: a single newline inside a paragraph is treated as a space, and consecutive blank lines collapse into one paragraph separator. Two options change that.
Preserving single newlines
MarkdownOptions(hardSoftBreaks = true) turns every single newline into a real line break, so the lines you typed are the lines you get:
Roses are red
Violets are blue
Without the option that renders as one line. With it, two. This is the option to reach for when you render user-authored text - chat messages, notes - where people expect Enter to mean Enter.
Blank lines
MarkdownOptions(preserveBlankLines = true) keeps consecutive blank lines instead of collapsing them, so deliberate vertical whitespace in the source survives into the output:
A first paragraph.
A second one, three blank lines later.
Without the option those three blank lines collapse to one paragraph break; with it, the gap is kept.
Both options are off by default, and both are set per instance through options.
Spacing
Each block's marginTop and marginBottom add up: the gap between two blocks is the first one's marginBottom plus the next one's marginTop. The last block's marginBottom is trimmed, so the view ends at its content; pass includeLastBlockMargin = true to keep it.