The <markdown> component
The <markdown> component: a live editor and rendered preview, side by side.
cpp
// Copyright 2026 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.
//
// The <markdown> component: a live editor and rendered preview, side by side.
#include <string>
#include "rtxui/rtxui.hpp"
using namespace rtxui;
namespace {
constexpr std::string_view kDefaultMarkdown = R"md(
# Markdown Playground
Edit the **Markdown** on the left. This preview re-renders on every
keystroke, using the same `<markdown>` component every RTXUI app can embed.
## Features
- **Bold** and _italic_ text
- [Links](https://github.com/ArthurSonzogni/RTXUI)
- Inline `code` and fenced code blocks:
```cpp
#include <rtxui/rtxui.hpp>
int main() {
return 0;
}
```
- Ordered and unordered lists
- Blockquotes
> Style the generated tags in the Stylesheet editor below.
| Metric | Value |
| --- | --- |
| DOM Digest | 0.53 ms |
| Layout/Paint | 0.52 ms |
)md";
constexpr std::string_view kDefaultStylesheet = R"css(
h1 {
color: #3b82f6;
border-bottom: solid;
border-color: #3b82f6;
margin-bottom: 1;
}
h2 {
color: #60a5fa;
margin-top: 1;
border-bottom: solid;
border-color: #334155;
}
strong { color: #facc15; }
em { color: #a78bfa; }
code { color: #94a3b8; }
pre {
background-color: #1e293b;
border: tall;
border-color: #334155;
display: block;
}
blockquote {
border-left: heavy;
border-color: #4b5563;
padding-left: 2;
font-style: italic;
color: #9ca3af;
}
ul, ol { margin-left: 2; color: #d1d5db; }
a { color: #3b82f6; text-decoration: underline; }
table {
border: solid;
border-color: #334155;
margin-top: 1;
margin-bottom: 1;
}
th {
font-weight: bold;
color: #60a5fa;
border-bottom: solid;
border-color: #334155;
padding-left: 1;
padding-right: 1;
}
td { padding-left: 1; padding-right: 1; }
)css";
} // namespace
// Left editor + right preview, mirroring example/playground.cpp: two
// `<textarea>`s hold the raw Markdown source and its stylesheet, and the
// `<markdown>` component on the right re-renders both reactively -- no
// manual reparse/HotReload plumbing needed, since `content`/`stylesheet` are
// ordinary bound props.
class MarkdownPlayground : public Component<MarkdownPlayground> {
public:
std::string markdown_source = std::string(kDefaultMarkdown);
std::string stylesheet = std::string(kDefaultStylesheet);
std::string status = "OK";
std::string status_class() const { return status == "OK" ? "ok" : "error"; }
MarkdownPlayground() {
Bind(markdown_source);
Bind(stylesheet);
Bind(status);
BindComputed(status_class);
}
// Resets `status` to "OK" right before the reactive re-render triggered by
// an edit -- if the new stylesheet is malformed, SetCssErrorHandler's
// callback (wired up in main()) overwrites it again with the real error
// during that same render, same recovery pattern as playground.cpp.
bool Digest() override {
if (markdown_source != last_markdown_ || stylesheet != last_stylesheet_) {
last_markdown_ = markdown_source;
last_stylesheet_ = stylesheet;
status = "OK";
}
return Component<MarkdownPlayground>::Digest();
}
std::string_view view = R"html(
<div class="app">
<div class="pane editor-pane">
<div class="pane-title">Markdown
<span class="hint">— live</span>
</div>
<textarea
class="editor"
value="{markdown_source}"
linenumbers="absolute"
highlight_current_line="true"
/>
<div class="pane-title">Stylesheet
<span class="hint">— CSS, live</span>
</div>
<textarea
class="editor"
value="{stylesheet}"
linenumbers="absolute"
highlight_current_line="true"
/>
<div class="status {status_class}">{status}</div>
</div>
<div class="pane preview-pane">
<div class="pane-title">Preview</div>
<div class="preview-frame">
<markdown content="{markdown_source}" stylesheet="{stylesheet}" />
</div>
</div>
</div>
<style>
self {
--border: rgb(48, 54, 61);
--muted: rgb(139, 148, 158);
display: block;
width: 100%;
height: 100%;
background-color: rgb(15, 23, 42);
color: white;
}
.app {
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
}
.pane {
display: flex;
flex-direction: column;
width: 50%;
height: 100%;
padding: 1;
}
.editor-pane {
border-right: solid;
border-color: var(--border);
}
.pane-title {
font-weight: bold;
color: rgb(129, 140, 248);
margin-bottom: 1;
}
.hint {
font-weight: normal;
color: var(--muted);
}
.editor {
flex-grow: 1;
width: 100%;
padding-left: 1;
padding-right: 1;
overflow-y: scroll;
margin-bottom: 1;
}
/* Themes the gutter/current-line, which live inside the textarea's
own template and so aren't reachable by an ordinary .editor .gutter
selector -- see docs/guide/css/basics.md. */
.editor::part(gutter) {
color: var(--border);
}
.editor::part(active) {
color: rgb(129, 140, 248);
}
.editor::part(current-line) {
background-color: rgb(49, 55, 79);
}
.editor::part(selection) {
background-color: rgb(67, 56, 202);
}
.editor::part(cursor) {
color: rgb(230, 237, 243);
}
.status {
color: var(--muted);
}
.status.error {
color: rgb(248, 81, 73);
}
.preview-frame {
flex-grow: 1;
width: 100%;
overflow-y: scroll;
}
</style>
)html";
private:
std::string last_markdown_ = std::string(kDefaultMarkdown);
std::string last_stylesheet_ = std::string(kDefaultStylesheet);
};
int main() {
auto app = Ref<MarkdownPlayground>::New();
// Without this, a CSS error in the live-edited stylesheet (e.g.
// mid-keystroke, before the user finishes typing a rule) would print
// straight to stderr and corrupt the running frame -- Screen owns the
// terminal in raw mode, so route it into the status line instead.
SetCssErrorHandler([app](const CssError& error) {
app->status = "CSS error, line " + std::to_string(error.line + 1) + ": " +
error.message;
});
// The <markdown> component wraps the stylesheet and rendered Markdown body
// into one document and re-parses it as XML on every keystroke (see
// markdown.cpp's Digest()); a malformed stylesheet (e.g. a literal <tag>
// inside a CSS comment, same bug class as playground.cpp) can break that
// parse. Route it into the status line, same recovery pattern as the CSS
// handler above.
SetXmlErrorHandler([app](const XmlError& error) {
app->status = "HTML error: " + error.message;
});
Screen screen(app);
screen.Loop();
return 0;
}- Source file:
example/markdown.cpp - Standalone terminal: ⛶ Open Fullscreen
- Guide: Relevant Documentation
- ← Back to Examples Index

