The tamboui-markdown module renders CommonMark + GFM (tables, task lists,
strikethrough) to a TamboUI Buffer.
dependencies {
implementation("dev.tamboui:tamboui-markdown:LATEST")
// Toolkit DSL integration (optional)
implementation("dev.tamboui:tamboui-toolkit-markdown:LATEST")
}
Requires Java 11.
Toolkit DSL
The tamboui-toolkit-markdown module provides MarkdownElement, a
StyledElement that wraps MarkdownView so markdown content participates
in the toolkit’s styling and layout system.
import static dev.tamboui.toolkit.Toolkit.*;
import static dev.tamboui.toolkit.markdown.MarkdownElement.markdown;
panel("README",
markdown(readme).overflow(Overflow.WRAP_WORD).fill()
).rounded();
CSS rules attached to the MarkdownElement selector flow through to the
underlying widget, so the same heading-1-color, link-color,
blockquote-prefix and so on documented below apply.
Quick start
import dev.tamboui.markdown.MarkdownView;
import dev.tamboui.widgets.block.Block;
import dev.tamboui.widgets.block.Borders;
import dev.tamboui.widgets.block.Title;
MarkdownView view = MarkdownView.builder()
.source("# Hello\n\nThis is **markdown**.")
.block(Block.builder()
.borders(Borders.ALL)
.title(Title.from(" README "))
.build())
.scroll(0)
.build();
view.render(area, buffer);
Supported elements
| Element | Notes |
|---|---|
Headings (1-6) |
Bold styling, level-scaled colors. H1 underlined with |
Paragraphs |
Width-aware word wrap. Soft line breaks become a single space; hard line breaks (two trailing spaces) start a new line. |
Bullet, ordered and task lists |
Bullet |
Block quotes |
Single `│ ` bar with the configured blockquote style (default: dim). |
Fenced and indented code blocks |
Wrapped in a rounded |
GFM tables |
Delegated to |
Thematic breaks |
A row of |
Inline emphasis, strong, strikethrough, code |
Composed via |
Links |
Underlined and colored. The link target is also attached to each span as an OSC-8 hyperlink, so terminals like iTerm2 and Kitty render them clickable. |
Images |
Rendered as a styled |
HTML blocks and inline HTML |
Rendered as escaped text in a dim style. The HTML source stays visible but is clearly not interpreted. |
Streaming
MarkdownView is safe to re-bind on every frame, including when the source
is being typed token-by-token by an LLM. Before every parse the source goes
through PartialMarkdownSanitizer, which trims trailing unmatched inline
markers. Well-formed markdown is returned byte-identical, so the same code
path serves both static and streaming consumers.
The sanitizer rules:
-
A trailing run of
,_,~, or single/double`whose left boundary is start-of-string, whitespace, or an opening bracket is treated as an unmatched opener and dropped. A trailing run after non-whitespace is a closer (e.g. theat the end ofbold*) and is left alone. -
A trailing run of three or more backticks is left alone — that is a code fence delimiter, not inline code.
-
A dangling link
[label](with no closing)is rewritten to plain[label]. -
A trailing partial ATX header line (
##with no content) is dropped. -
An open code fence is left alone — CommonMark already treats EOF as fence close.
Customizing styles
Styles resolve in this order: explicit MarkdownStyles value > CSS resolver > built-in default.
Programmatic overrides
MarkdownStyles controls the styles applied to each element. Override any
subset and keep the rest at their defaults:
import dev.tamboui.markdown.MarkdownStyles;
import dev.tamboui.style.Color;
import dev.tamboui.style.Style;
MarkdownStyles styles = MarkdownStyles.builder()
.heading(1, Style.EMPTY.bold().fg(Color.MAGENTA))
.link(Style.EMPTY.fg(Color.GREEN).underlined())
.build();
MarkdownView view = MarkdownView.builder()
.source(source)
.styles(styles)
.build();
CSS resolver
Pass a StylePropertyResolver (the same single-resolver pattern Block and
the other widgets use) to fill any slot the user did not set
programmatically. Each markdown sub-element exposes three flat properties:
-
{element}-color— foreground color (named, hex, rgb, or indexed) -
{element}-background— background color -
{element}-text-style— space-separated modifiers (bold,italic,underlined,dim,reversed,crossed-out/strikethrough,hidden,slow-blink,rapid-blink)
Plus a few string properties (mirroring the Checkbox widget convention):
-
blockquote-prefix— a glyph drawn at the start of each quoted line; a single space is always appended after it. -
task-checked-symbol/task-unchecked-symbol— glyphs drawn for GFM task-list items; defaults are[x]and[ ], again followed by a single space. -
text-overflow— the standardtext-overflowproperty, same asParagraph. Supported values:wrap-word(default),wrap-character/wrap,clip,ellipsis,ellipsis-start,ellipsis-middle. Applies to prose: paragraphs, headings, list items, blockquote contents. Code-block lines always clip (their structure is meaningful and ellipsis would mislead) and table cells are sized by theTablewidget.
Element names: heading-1 … heading-6, strong, emphasis,
strikethrough, inline-code, code-block, link, blockquote,
list-marker, html, horizontal-rule, task-checked, task-unchecked.
MarkdownView view = MarkdownView.builder()
.source(source)
.styleResolver(myCssEngine.resolver())
.build();
A TCSS rule for the markdown widget looks like:
MarkdownView {
text-overflow: ellipsis;
heading-1-color: magenta;
heading-1-text-style: bold;
link-color: green;
link-text-style: underlined;
blockquote-color: yellow;
blockquote-background: black;
blockquote-prefix: ">";
task-checked-color: green;
task-checked-symbol: "✓";
task-unchecked-color: gray;
task-unchecked-symbol: "·";
}