Skip to main content

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​

ElementMarkdownNotes
Heading# H1 … ###### H6Six levels, each styled independently
ParagraphText separated by a blank line
Blockquote> quotedNests
Admonition> [!NOTE]Needs MarkdownOptions(admonitions = true) - see below
Unordered list- itemNests
Ordered list1. itemNests
Task list- [ ] / - [x]Tappable - see onTaskListItemToggle
TableGFM pipe tableAlways enabled - see below
Fenced code block```kotlinLong lines wrap or scroll; colored by language with CodeHighlightPlugin - see below
Block image![alt](url) first on its lineSee below
Thematic break---
Math block$$...$$Needs LatexMathPlugin - see Math

Inline elements​

ElementMarkdownNotes
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 URLBare URLs need permissiveAutolinks, which is on by default
Inline image![alt](url) after textSee 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.

important

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 (```py reads "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 ![alt](url) 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.
note

This differs from CommonMark, where every image is inline. Markdown has no block image syntax; the split is how this renderer lays images out.

![A block image](https://example.com/hero.png)

Some text with an ![inline image](https://example.com/icon.png) 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.