This page documents the Markdown renderable in Rich, covering how Markdown text is parsed, how the element hierarchy is structured, and how rendering state is managed through MarkdownContext and StyleStack. For general information about the rendering pipeline that executes __rich_console__, see Rendering Pipeline. For the Syntax class used inside code blocks, see Syntax Highlighting.
Markdown (rich/markdown.py512-694) is a Rich renderable that takes a Markdown string, parses it with markdown-it-py, and renders each structural element to the terminal using styled Text, Syntax, Rule, and Table objects.
Usage:
From the command line:
python -m rich.markdown README.md
The Markdown.__init__ method (rich/markdown.py548-566) instantiates a MarkdownIt parser with the strikethrough and table extensions enabled:
The result, self.parsed, is a flat list of Token objects. The _flatten_tokens method (rich/markdown.py568-576) recursively yields from token.children for most tokens, while treating fence (fenced code blocks) and img tokens as atomic — they are yielded directly rather than expanded.
Token nesting conventions used in __rich_console__:
token.nesting value | Meaning |
|---|---|
1 | Opening tag (e.g. paragraph_open) |
-1 | Closing tag (e.g. paragraph_close) |
0 | Self-closing tag (e.g. text, fence, image) |
Sources: rich/markdown.py568-600
MarkdownElement class hierarchy diagram:
Sources: rich/markdown.py25-462
The Markdown.elements class variable (rich/markdown.py527-544) maps markdown-it token type strings to MarkdownElement subclasses:
| Token type | MarkdownElement class |
|---|---|
paragraph_open | Paragraph |
heading_open | Heading |
fence | CodeBlock |
code_block | CodeBlock |
blockquote_open | BlockQuote |
hr | HorizontalRule |
bullet_list_open | ListElement |
ordered_list_open | ListElement |
list_item_open | ListItem |
image | ImageItem |
table_open | TableElement |
tbody_open | TableBodyElement |
thead_open | TableHeaderElement |
tr_open | TableRowElement |
td_open | TableDataElement |
th_open | TableDataElement |
The Markdown.inlines set (rich/markdown.py546) lists inline style tag names that are handled via style stack pushes rather than element instantiation: {"em", "strong", "code", "s"}.
Sources: rich/markdown.py527-546
MarkdownContext (rich/markdown.py464-509) is the central state object passed to every element callback. It holds:
console — the active Consoleoptions — the active ConsoleOptionsstyle_stack: StyleStack — tracks the cumulative style as the parser walks the token treestack: Stack[MarkdownElement] — tracks the current element nesting_syntax: Syntax | None — optionally set when inline_code_lexer is provided, used to syntax-highlight inline code and fence contentKey methods on MarkdownContext:
| Method | Effect |
|---|---|
current_style (property) | Returns style_stack.current, the product of all pushed styles |
enter_style(style_name) | Looks up style from console, pushes onto style_stack, returns new current style |
leave_style() | Pops from style_stack, returns popped style |
on_text(text, node_type) | Routes text to stack.top.on_text(); if node_type is fence or code_inline and _syntax is set, highlights it first |
StyleStack (rich/style.py) is a simple stack abstraction. Each push composites the new style on top of the running style. current always reflects the fully combined style.
Stack (rich/_stack.py) is a generic stack used for element nesting. stack.top gives the innermost element without popping.
Sources: rich/markdown.py464-509
Markdown rendering data flow:
Sources: rich/markdown.py578-695
The loop in __rich_console__ (rich/markdown.py578-695) processes each flattened token:
text, hardbreak, softbreak) are dispatched to context.on_text(), which calls stack.top.on_text().link_open, link_close) either push/pop a link style (if hyperlinks=True) or manage a Link element manually.em, strong, code, s) trigger context.enter_style() / context.leave_style() to push named styles like "markdown.em", "markdown.strong", etc.self.elements, instantiated with element_class.create(self, token), and pushed onto the context stack.context.stack.top.on_child_close(context, element) is called. If on_child_close returns True, the element is rendered via console.render(element, context.options). If it returns False, the parent takes ownership of the child (used by BlockQuote, ListElement, TableElement, etc.).element.new_line controls whether a blank line Segment is emitted between consecutive rendered elements.Sources: rich/markdown.py596-695
Paragraph (rich/markdown.py107-124) renders its accumulated Text object with the justify value from Markdown.justify (defaulting to "left"). Style name: "markdown.paragraph".
Heading (rich/markdown.py133-164) uses the token tag field (h1–h6) to pick a style name ("markdown.h1" through "markdown.h6") and a justification from LEVEL_ALIGN:
| Heading level | Default justification |
|---|---|
h1 | center |
h2–h6 | left |
CodeBlock (rich/markdown.py167-189) extracts the language identifier from token.info (e.g. ```python → "python"). It renders using a Syntax object with word_wrap=True and padding=1. If no language is specified, the lexer name defaults to "text". The Pygments theme is taken from Markdown.code_theme (default: "monokai").
BlockQuote (rich/markdown.py192-215) overrides on_child_close to collect child elements rather than render them inline. In __rich_console__, it calls console.render_lines() on the collected Renderables at max_width - 4, then prefixes each line with a "▌ " segment styled with "markdown.block_quote".
HorizontalRule (rich/markdown.py218-228) yields a Rule object styled with "markdown.hr" and characters="-", then an empty Text. It sets new_line = False.
ListElement (rich/markdown.py339-368) handles both bullet (bullet_list_open) and ordered (ordered_list_open) lists. It collects ListItem children via on_child_close.
ListItem (rich/markdown.py371-410) supports nested renderables. Two render methods exist:
render_bullet — prefixes each line with " • " styled as "markdown.item.bullet"render_number — prefixes each line with a right-justified number styled as "markdown.item.number", padded to accommodate the width of last_numberBoth methods use console.render_lines() to pre-render item content at max_width - N (3 for bullet, number width for ordered), then re-assemble with prefix segments per line.
Table rendering uses a tree of helper elements:
TableElement.__rich_console__ (rich/markdown.py247-269) assembles a Table with box=box.SIMPLE, pad_edge=False, style="markdown.table.border". Header columns are added from TableHeaderElement.row.cells, with content styled by "markdown.table.header". Body rows are added from TableBodyElement.rows.
TableDataElement (rich/markdown.py308-336) reads CSS text-align values from token.attrs["style"] to determine column justification (left, center, right, or default).
ImageItem (rich/markdown.py424-461) renders a placeholder: a 🌆 emoji followed by the alt-text (or a filename derived from the URL). If hyperlinks=True, the image destination URL is attached as a hyperlink style.
Sources: rich/markdown.py107-461
Markdown.__init__ (rich/markdown.py548-566):
| Parameter | Type | Default | Description |
|---|---|---|---|
markup | str | required | The Markdown source string |
code_theme | str | "monokai" | Pygments theme for fenced code blocks |
justify | JustifyMethod | None | None | Paragraph justification ("left", "center", "right", "full") |
style | str | Style | "none" | Base style applied to all rendered output |
hyperlinks | bool | True | If True, links render as terminal hyperlinks; if False, URLs are printed inline as text (url) |
inline_code_lexer | str | None | None | Pygments lexer name for inline code highlighting |
inline_code_theme | str | None | None | Pygments theme for inline code; falls back to code_theme |
Sources: rich/markdown.py512-566
The following named styles are looked up from the console's theme system during rendering. They can be overridden via a custom Theme. For details on the theme system, see Themes and Customization.
| Style name | Used by |
|---|---|
markdown.paragraph | Paragraph |
markdown.h1 through markdown.h6 | Heading |
markdown.code_block | CodeBlock |
markdown.block_quote | BlockQuote |
markdown.hr | HorizontalRule |
markdown.item | ListItem |
markdown.item.bullet | ListItem.render_bullet |
markdown.item.number | ListItem.render_number |
markdown.table.border | TableElement |
markdown.table.header | TableElement |
markdown.link | __rich_console__ (non-hyperlink mode) |
markdown.link_url | __rich_console__ (both modes) |
markdown.em | Inline em tag |
markdown.strong | Inline strong tag |
markdown.code | Inline code tag |
markdown.s | Inline strikethrough (s) tag |
Sources: rich/markdown.py94-643
When inline_code_lexer is set, MarkdownContext.__init__ (rich/markdown.py480-482) creates a Syntax instance:
MarkdownContext.on_text (rich/markdown.py489-498) detects node_type in {"fence", "code_inline"} and calls self._syntax.highlight(text) to produce a styled Text object, which is then assembled with the current style and dispatched to stack.top.on_text().
Sources: rich/markdown.py464-498
rich/markdown.py can be run as a module (rich/markdown.py698-793):
python -m rich.markdown PATH
Supported flags:
| Flag | Description |
|---|---|
-c / --force-color | Force color output for non-terminals |
-t / --code-theme | Pygments theme for code blocks (default: monokai) |
-i / --inline-code-lexer | Lexer for inline code highlighting |
-y / --hyperlinks | Enable hyperlinks |
-w / --width | Output width (auto-detects by default) |
-j / --justify | Enable full-text justification |
-p / --page | Pipe output through a pager |
Sources: rich/markdown.py698-793
Refresh this wiki
This wiki was recently refreshed. Please wait 2 days to refresh again.