Skip to content

HTML Element & Component Reference ​

This specification defines the complete set of built-in XML/HTML tags and components supported by the RTXUI template parser, including their classification, attribute interfaces, slot projections, event contracts, and default user-agent stylesheets.


1. Architectural Model ​

1.1 Unified Component Architecture ​

Every built-in tag—from <div> to <dialog>—is a component registered in the global component registry and built over rtxui::Element. Simpler tags like <div> or <span> use trivial templates (e.g. <slot></slot> with a default stylesheet), while richer widgets like <input> or <select> encapsulate internal HTML templates, reactive state bindings, slot projections, and scoped CSS rules. The difference is in complexity, not in kind.

1.2 Parser Contract ​

  • Well-Formed XML/HTML: All tags must be balanced or explicitly self-closed (e.g. <hr />, <br />, <input />). Unclosed tags generate parser errors with line and column diagnostics.
  • Case Sensitivity: Tag names and attribute names are parsed as lower-case identifiers.
  • Quoting: Attribute values must be enclosed in double ("...") or single ('...') quotes.

1.3 Binding Syntax ​

SyntaxTargetExampleSemantic Contract
{member}Text / Attribute<span id="{node_id}">{title}</span>Interpolates C++ member snapshot; updates DOM on change.
on<event>="Handler"Event Callback<button onclick="Submit">OK</button>Dispatches to registered C++ method or lambda. oncontextmenu takes the right click.

2. Global Attributes & Directives ​

The following attributes apply to all elements and components:

AttributeTypeDefaultDescription
idstring""Unique element identifier; targets #id CSS rules and for associations.
classstring""Space-delimited token list for .class CSS selector matching.
stylestring""Inline CSS declarations. Overrides stylesheet rules with higher specificity.
tabindexinteger-1Sequential keyboard focus order. Values >= 0 participate in Tab navigation; -1 excludes the element.
focusablebooleanfalseShorthand; focusable="true" is equivalent to tabindex="0".
disabledbooleanfalseDisables user interaction, removes focus, and applies the :disabled CSS pseudo-class.
ifexpression—Conditional rendering directive. If the expression evaluates to false, the element and its subtree are pruned from the DOM.
partstring""Names an element part for ::part() CSS selector targeting from ancestor components.
onanimationendhandler—Runs when one of the element's keyframe animations finishes. Not for an infinite one, nor one removed before its end.

3. Structural Containers & Layout ​

<div> ​

  • Classification: Block Component
  • Layout Model: display: block
  • Semantics: Standard block-level container for flow, flex, or grid layouts.

<span> ​

  • Classification: Inline Component
  • Layout Model: display: inline
  • Semantics: Inline-level formatting wrapper for styled text segments.

Semantic Elements (<header>, <footer>, <main>, <nav>, <aside>, <section>, <article>) ​

  • Classification: Block Components
  • Layout Model: display: block
  • Semantics: Structural containers functionally identical to <div>, providing semantic document organization.

<table>, <thead>, <tbody>, <tfoot>, <tr>, <th>, <td> ​

  • Classification: Table Components
  • Layout Model: Table formatting context with automatic column and row sizing.
  • Section Containers: <thead>, <tbody>, and <tfoot> group rows logically. The layout engine traverses them transparently when assembling grid rows and computing cell geometry.

<th> / <td> Attributes ​

AttributeTypeDefaultReactiveDescription
colspaninteger1One-wayNumber of columns the cell should span.
rowspaninteger1One-wayNumber of rows the cell should span.

Table Sizing Algorithm: ​

  • Columns: Evaluates the maximum preferred cell width across each column. Extra horizontal space from fixed container width or min-width is distributed proportionally.

  • Rows: Row height conforms to the tallest cell in the row. Shorter cells stretch vertically to maintain unbroken borders and backgrounds; vertical-align: middle | bottom on a cell moves its content down within it.

  • Cell borders: by default each cell draws its whole border, next to its neighbours'. border-spacing puts space between them. border-collapse: collapse on the table draws each line once and joins them at the crossings:

    css
    table { border: solid; border-collapse: collapse; }
    td, th { border: solid; padding: 0 1; }
    ┌──────────┬────────┐
    │ Service  │ Status │
    ├──────────┼────────┤
    │ Database │ Active │
    └──────────┴────────┘
xml
<table>
  <thead>
    <tr>
      <th>Service</th>
      <th>Status</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Database</td>
      <td>Active</td>
    </tr>
  </tbody>
</table>

<style>
  table {
    border: solid;
    border-color: #3b82f6;
  }
  th {
    font-weight: bold;
    border-bottom: solid;
    border-color: #334155;
  }
  td, th {
    padding-left: 1;
    padding-right: 1;
  }
</style>
Fullscreen Demo
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.
//
// Table elements: <table>, <thead>, <tr>, <th>, <td>, with colspan, rowspan and
// a sticky header row.
#include <rtxui/rtxui.hpp>
#include <string>
#include <string_view>
#include <vector>

using namespace rtxui;

struct Product {
  std::string name;
  std::string category;
  std::string price;
  bool active = true;
  bool operator==(const Product& other) const = default;
};

void Fields(const Product& product, FieldSink& out) {
  out("name", product.name);
  out("category", product.category);
  out("price", product.price);
  out("status", product.active ? "active" : "inactive");
}

class TableDemo : public Component<TableDemo> {
 public:
  std::vector<Product> products = {
      {"Gadget", "Appliances", "$10.00"},
      {"Widget", "Consumer Electronics", "$15.00"},
      {"Tool", "Hardware", "$5.99", false},
      {"Smartwatch", "Wearables", "$199.99"},
      {"Keyboard", "Accessories", "$45.00", false},
      {"Monitor", "Computers", "$249.99"},
      {"Desk lamp", "Furniture", "$35.00", false},
      {"Headphones", "Audio", "$79.99"},
      {"Camera", "Photography", "$450.00"},
      {"Tripod", "Photography", "$60.00"},
      {"Phone", "Consumer Electronics", "$699.99"},
  };

  std::string_view view = R"html(
      <div class="card">
        <h1>Tables</h1>
        <p>Columns size to their content. Scroll the list: the header row is sticky.</p>

        <div class="scroller">
          <table>
            <thead>
              <tr class="header">
                <th>Name</th>
                <th>Category</th>
                <th>Price</th>
                <th>Status</th>
              </tr>
            </thead>
            <tbody>
              <for each="{products}" as="p">
                <tr>
                  <td>{p.name}</td>
                  <td class="muted">{p.category}</td>
                  <td>{p.price}</td>
                  <td class="{p.status}">{p.status}</td>
                </tr>
              </for>
            </tbody>
          </table>
        </div>

        <p>Cells span columns with colspan and rows with rowspan.</p>
        <table class="spans">
          <tr>
            <td rowspan="2" class="span">rowspan="2"</td>
            <td>A</td>
            <td>B</td>
          </tr>
          <tr>
            <td colspan="2" class="span">colspan="2"</td>
          </tr>
        </table>
      </div>

      <style>
        self {
          --border: rgb(48, 54, 61);
          --muted: rgb(139, 148, 158);
          --accent: rgb(88, 166, 255);

          display: flex;
          align-items: center;
          justify-content: center;
          width: 100%;
          color: rgb(230, 237, 243);
        }
        .card {
          border: tall;
          border-color: var(--border);
          background-color: rgb(22, 27, 34);
          padding: 1 3;
          width: 64;
        }
        h1 {
          color: var(--accent);
        }
        p, .muted {
          color: var(--muted);
        }
        .scroller {
          height: 9;
          overflow-y: scroll;
          border: round;
          border-color: var(--border);
        }
        .header {
          position: sticky;
          top: 0;
          background-color: rgb(22, 45, 77);
        }
        th {
          color: var(--accent);
          text-align: left;
        }
        th, td {
          padding: 0 1;
        }
        .active {
          color: rgb(63, 185, 80);
        }
        .inactive {
          color: rgb(248, 81, 73);
        }
        .spans td {
          border: round;
          border-color: var(--border);
        }
        .spans .span {
          border-color: var(--accent);
          color: var(--accent);
        }
      </style>
    )html";

  TableDemo() { Bind(products); }
};

int main() {
  auto app = Ref<TableDemo>::New();
  Screen screen(app);
  screen.Loop();
  return 0;
}

4. Form Controls & Interactive Widgets ​

<button> ​

  • Classification: Inline Component (rtxui::button)
  • Focusable: Yes (tabindex="0")

Attributes ​

AttributeTypeDefaultReactiveDescription
disabledbooleanfalseOne-waySuppresses activation events and sets :disabled pseudo-class.
onclickhandler——Callback invoked on mouse click or Enter/Space keyboard activation.

Slots ​

SlotTarget ElementDescription
(default)Inline containerButton text or child element content.

Default Stylesheet ​

css
self {
  display: inline-block;
  padding-left: 1;
  padding-right: 1;
  cursor: pointer;
  background-color: lighten(10%);
  opacity: 0.85;
  transition: background-color 0.1s linear, opacity 0.1s linear, color 0.1s linear;
}
self:hover {
  background-color: rgb(59, 130, 246);
  color: white;
  opacity: 0.95;
}
self:focus {
  background-color: rgb(37, 99, 235);
  color: white;
  opacity: 1.0;
}
self:active {
  background-color: rgb(29, 78, 216);
  color: white;
  opacity: 1.0;
}
self:disabled {
  cursor: default;
  opacity: 0.4;
}

<input> ​

  • Classification: Inline Component (rtxui::input)
  • Focusable: Yes (tabindex="0")

Attributes ​

AttributeTypeDefaultReactiveDescription
valuestring""Two-wayText content. Automatically synchronized during typing.
typestring"text"One-wayInput format. "text" renders characters; "password" masks characters with *.
placeholderstring""One-wayGhost text displayed when value is empty.
disabledbooleanfalseOne-wayDisables input and focus; applies :disabled pseudo-class.
readonlybooleanfalseOne-wayPrevents text modification while allowing focus and text selection.
select_on_focusbooleanfalseOne-wayAutomatically selects all text content when focused.
maxlengthinteger-1One-wayMaximum character length constraint (-1 indicates unconstrained).
onchangehandler——Callback invoked when value is committed. Accepts void() or void(std::string).

Styleable Parts (::part()) ​

  • placeholder: The span element rendering placeholder text.
  • cursor, cursor-focused: The active cursor indicator.
  • selection: Highlighted text selection regions.

Default Stylesheet ​

css
self {
  display: inline-flex;
  flex-direction: row;
  width: 20;
  padding-left: 1;
  padding-right: 1;
  overflow-x: scroll;
  scrollbar-width: none;
  white-space: nowrap;
  background-color: rgb(40, 40, 40);
  opacity: 0.8;
}
self:focus {
  opacity: 1.0;
}
self:disabled {
  opacity: 0.4;
  cursor: default;
}

<textarea> ​

  • Classification: Block Component (rtxui::textarea)
  • Focusable: Yes (tabindex="0")

Attributes ​

AttributeTypeDefaultReactiveDescription
valuestring""Two-wayMulti-line text content.
placeholderstring""One-wayGhost text rendered when content is empty.
disabledbooleanfalseOne-wayDisables editing and interaction.
readonlybooleanfalseOne-wayPrevents text edits while maintaining scroll and selection capabilities.
select_on_focusbooleanfalseOne-wayAutomatically selects all text content when focused.
maxlengthinteger-1One-wayMaximum character capacity limit.
linenumbersstring""One-wayDisplays line number gutter. Values: "" (off), "true" / "absolute" (absolute), "relative" (relative).
line_startinteger1One-wayThe displayed number of the first logical line.
line_endinteger-1One-wayThe last displayed number to show a gutter entry for; -1 means unbounded.
line_wrapstring""One-waySoft line wrapping at viewport boundary. Values: "", "ignore", "subline".
highlight_current_linebooleanfalseOne-wayEmphasizes the row hosting the active cursor.

<checkbox> ​

  • Classification: Inline-Flex Component (rtxui::checkbox)
  • Focusable: Yes (tabindex="0")

Attributes ​

AttributeTypeDefaultReactiveDescription
checkedbooleanfalseTwo-wayState toggle. Toggles between ☑ and ☐. Synchronizes :checked pseudo-class.
disabledbooleanfalseOne-waySuppresses toggle interaction and sets :disabled pseudo-class.
onchangehandler——Fired on state change. Accepts void() or void(bool).

Styleable Parts (::part()) ​

  • checkmark: Span element containing the checkmark glyph (☑ or ☐).

Default Stylesheet ​

css
self {
  display: inline-flex;
  flex-direction: row;
  white-space: nowrap;
  cursor: pointer;
  padding-left: 1;
  padding-right: 1;
  transition: background-color 0.1s linear;
}
self:hover {
  background-color: lighten(10%);
}
self:focus {
  background-color: lighten(25%);
}
self:active {
  background-color: lighten(35%);
}
self:disabled {
  cursor: default;
  opacity: 0.4;
}
.checkmark {
  margin-right: 1;
}

<radio> ​

  • Classification: Inline-Flex Component (rtxui::radio)
  • Focusable: Yes (tabindex="0")

Attributes ​

AttributeTypeDefaultReactiveDescription
namestring""One-wayRadio group identifier. Only one radio sharing a name can be checked simultaneously.
checkedbooleanfalseTwo-waySelection state. Toggles between ◉ and ○. Synchronizes :checked pseudo-class.
disabledbooleanfalseOne-waySuppresses activation and applies :disabled pseudo-class.
onchangehandler——Fired when the radio is selected.

Styleable Parts (::part()) ​

  • radio-mark: Span element containing the radio glyph (◉ or ○).
xml
<radio name="theme" checked="{dark_mode}">Dark</radio>
<radio name="theme" checked="{light_mode}">Light</radio>
Fullscreen Demo
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.
//
// <radio> buttons sharing a `name`, with the selection bound to a C++ string.
#include <rtxui/rtxui.hpp>
#include <string>
#include <string_view>

using namespace rtxui;

class RadioDemo : public Component<RadioDemo> {
 public:
  bool select_a = false;
  bool select_b = false;
  bool select_c = false;

  std::string selected_tech() const {
    if (select_a) {
      return "PostgreSQL";
    }
    if (select_b) {
      return "MongoDB";
    }
    if (select_c) {
      return "Redis";
    }
    return "none";
  }

  std::string_view view = R"html(
      <div class="card">
        <h1>Radio</h1>
        <p>Radios sharing a name form one group: one is checked at a time.</p>

        <div class="group">
          <radio name="db" checked="{select_a}">PostgreSQL</radio>
          <radio name="db" checked="{select_b}">MongoDB</radio>
          <radio name="db" checked="{select_c}">Redis</radio>
        </div>

        <div class="readout">
          <span>selected</span>
          <span class="value">{selected_tech}</span>
        </div>
      </div>

      <style>
        self {
          --border: rgb(48, 54, 61);
          --muted: rgb(139, 148, 158);
          --accent: rgb(88, 166, 255);

          display: flex;
          align-items: center;
          justify-content: center;
          width: 100%;
          color: rgb(230, 237, 243);
        }
        .card {
          border: tall;
          border-color: var(--border);
          background-color: rgb(22, 27, 34);
          padding: 1 3;
          width: 50;
        }
        h1 {
          color: var(--accent);
        }
        p {
          color: var(--muted);
        }
        .group {
          display: flex;
          flex-direction: column;
        }
        radio:focus, radio:checked {
          color: var(--accent);
        }
        .readout {
          display: flex;
          justify-content: space-between;
          border-top: solid;
          border-color: var(--border);
          margin-top: 1;
          padding-top: 1;
          color: var(--muted);
        }
        .value {
          color: var(--accent);
          font-weight: bold;
        }
      </style>
    )html";

  RadioDemo() {
    Bind(select_a);
    Bind(select_b);
    Bind(select_c);
    Bind(selected_tech);
  }
};

int main() {
  auto app = Ref<RadioDemo>::New();
  Screen screen(app);
  screen.Loop();
  return 0;
}

<select> and <option> ​

  • Classification: Component (rtxui::select, rtxui::option)
  • Focusable: Yes (tabindex="0")

<select> Attributes ​

AttributeTypeDefaultReactiveDescription
valuestring""Two-wayActive selected option value.
disabledbooleanfalseOne-wayDisables dropdown expansion.
onchangehandler——Fired on option change. Accepts void() or void(std::string).

<option> Attributes ​

AttributeTypeDefaultReactiveDescription
valuestring""One-wayDistinct value payload passed to <select>.
disabledbooleanfalseOne-wayPrevents selection of this specific option.

Styleable Parts (::part()) ​

  • select-btn: Main clickable trigger container.
  • select-label: Active selected text label.
  • select-arrow: Dropdown expand/collapse arrow glyph.
  • dropdown-list: Floating options list container.

<slider> ​

  • Classification: Inline Component (rtxui::slider)
  • Focusable: Yes (tabindex="0")

Attributes ​

AttributeTypeDefaultReactiveDescription
valueinteger0Two-wayCurrent slider value. Clamped between min and max.
mininteger0One-wayLower bound of the range.
maxinteger100One-wayUpper bound of the range.
stepinteger1One-wayDiscrete increment step on keyboard navigation or dragging.
widthinteger20One-wayVisual cell track width.
directionstring"horizontal"One-wayTrack orientation ("horizontal" or "vertical").
disabledbooleanfalseOne-wayDisables slider manipulation.
onchangehandler——Fired on value updates. Accepts void() or void(int).

Styleable Parts (::part()) ​

  • slider-container: Wrapper element.
  • track-left: Filled track portion preceding the thumb.
  • thumb: Visual thumb handle glyph (●).
  • track-right: Unfilled track portion following the thumb.

<progress> ​

  • Classification: Inline Component (rtxui::progress)
  • Focusable: No

Attributes ​

AttributeTypeDefaultReactiveDescription
valuefloat0.0One-wayCurrent progress amount.
maxfloat100.0One-wayMaximum range value.
widthinteger20One-wayPhysical cell width of the bar.

Styleable Parts (::part()) ​

  • filled: Active progress section (rendered using █).
  • empty: Inactive remainder section.

<label> ​

  • Classification: Inline-Flex Component (rtxui::label)
  • Focusable: No (cursor: pointer)

Attributes ​

AttributeTypeDefaultReactiveDescription
forstring""One-wayID of target interactive element.

Event Delegation Semantics: ​

Clicking a <label> delegates focus and activation to the target:

  1. Explicit Target: Element whose id matches the for attribute.
  2. Implicit Target: The first focusable descendant nested within <label></label>.
Fullscreen Demo
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.
//
// <label> delegating clicks and focus to the control it names.
#include <rtxui/rtxui.hpp>
#include <string_view>

using namespace rtxui;

class LabelDemo : public Component<LabelDemo> {
 public:
  bool notify = false;
  bool sound = true;

  std::string_view view = R"html(
      <div class="card">
        <h1>Label</h1>
        <p>Clicking a label acts on the control it belongs to.</p>

        <h2>for="id" names the control</h2>
        <div class="row">
          <label for="notify">Click this label</label>
          <checkbox id="notify" checked="{notify}">Notifications</checkbox>
        </div>

        <h2>or the label wraps it</h2>
        <label class="wrapper">
          Click anywhere on this line
          <checkbox checked="{sound}">Sound</checkbox>
        </label>

        <div class="readout">
          <span>notifications <span class="value">{notify}</span></span>
          <span>sound <span class="value">{sound}</span></span>
        </div>
      </div>

      <style>
        self {
          --border: rgb(48, 54, 61);
          --muted: rgb(139, 148, 158);
          --accent: rgb(88, 166, 255);

          display: flex;
          align-items: center;
          justify-content: center;
          width: 100%;
          color: rgb(230, 237, 243);
        }
        .card {
          border: tall;
          border-color: var(--border);
          background-color: rgb(22, 27, 34);
          padding: 1 3;
          width: 54;
        }
        h1 {
          color: var(--accent);
        }
        p {
          color: var(--muted);
        }
        h2 {
          margin: 1 0 0 0;
          font-weight: normal;
          color: var(--muted);
        }
        .row {
          display: flex;
          gap: 3;
        }
        label {
          color: var(--accent);
        }
        label:hover {
          text-decoration: underline;
        }
        .wrapper {
          display: flex;
          justify-content: space-between;
        }
        .readout {
          display: flex;
          justify-content: space-between;
          border-top: solid;
          border-color: var(--border);
          margin-top: 1;
          padding-top: 1;
          color: var(--muted);
        }
        .value {
          color: var(--accent);
          font-weight: bold;
        }
      </style>
    )html";

  LabelDemo() {
    Bind(notify);
    Bind(sound);
  }
};

int main() {
  auto app = Ref<LabelDemo>::New();
  Screen screen(app);
  screen.Loop();
  return 0;
}

5. Disclosure, Navigation & Overlays ​

<details> and <summary> ​

  • Classification: Block Component (rtxui::details, rtxui::summary)

Attributes ​

AttributeTypeDefaultReactiveDescription
openbooleanfalseTwo-wayExpansion state. Toggled by clicking <summary> or pressing Enter/Space.

Slots ​

SlotTarget ElementDescription
summary<summary>Clickable disclosure header row. If omitted, defaults to "Details".
(default)Block containerCollapsible body content displayed when open="true".
Fullscreen Demo
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 <details>/<summary> disclosure widget.
//
// Try it: click a summary row, or focus it and press Enter.
#include <rtxui/rtxui.hpp>
#include <string_view>

using namespace rtxui;

class DetailsDemo : public Component<DetailsDemo> {
 public:
  bool logs_open = false;
  bool settings_open = true;
  bool debug = false;

  std::string_view view = R"html(
      <div class="card">
        <h1>Details</h1>
        <p>Click a summary, or focus it and press Enter.</p>

        <details open="{logs_open}">
          <summary>System logs</summary>
          <div class="body">
            <div>09:41:02  Application initialized.</div>
            <div>09:41:05  Theme loaded.</div>
            <div>09:42:15  User input detected.</div>
          </div>
        </details>

        <details open="{settings_open}">
          <summary>Settings</summary>
          <div class="body">
            <checkbox checked="{debug}">Debug mode</checkbox>
          </div>
        </details>

        <details>
          <div class="body">Without a &lt;summary&gt;, the header reads "Details".</div>
        </details>
      </div>

      <style>
        self {
          --border: rgb(48, 54, 61);
          --muted: rgb(139, 148, 158);
          --accent: rgb(88, 166, 255);

          display: flex;
          align-items: center;
          justify-content: center;
          width: 100%;
          color: rgb(230, 237, 243);
        }
        .card {
          border: tall;
          border-color: var(--border);
          background-color: rgb(22, 27, 34);
          padding: 1 3;
          width: 56;
        }
        h1 {
          color: var(--accent);
        }
        p {
          color: var(--muted);
        }
        summary:focus {
          color: var(--accent);
        }
        .body {
          border-left: solid;
          border-color: var(--border);
          padding-left: 2;
          margin-left: 1;
          color: var(--muted);
        }
      </style>
    )html";

  DetailsDemo() {
    Bind(logs_open);
    Bind(settings_open);
    Bind(debug);
  }
};

int main() {
  auto app = Ref<DetailsDemo>::New();
  Screen screen(app);
  screen.Loop();
  return 0;
}

<fieldset> and <legend> ​

  • Classification: Block Component (rtxui::fieldset, rtxui::legend)

Attributes ​

AttributeTypeDefaultReactiveDescription
disabledbooleanfalseOne-wayDisables the fieldset and all enclosed form controls.

Slots ​

SlotTarget ElementDescription
legend<legend>Header caption nested into the top border frame.
(default)Block containerEnclosed form controls and content.
Fullscreen Demo
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.
//
// <fieldset> and <legend> grouping.
//
// The legend is nested into the top border of the group it captions.
#include <rtxui/rtxui.hpp>
#include <string_view>

using namespace rtxui;

class FieldsetDemo : public Component<FieldsetDemo> {
 public:
  std::string_view view = R"html(
      <div class="card">
        <h1>Fieldset</h1>
        <p>The legend sits in the top border of the group it captions.</p>

        <fieldset>
          <legend>Author</legend>
          <div><span class="key">Name</span>Arthur Sonzogni</div>
          <div><span class="key">Role</span>Maintainer</div>
        </fieldset>

        <fieldset>
          <legend>Project</legend>
          <div><span class="key">Name</span>RTXUI</div>
          <div><span class="key">Language</span>C++23</div>
        </fieldset>
      </div>

      <style>
        self {
          --border: rgb(48, 54, 61);
          --muted: rgb(139, 148, 158);
          --accent: rgb(88, 166, 255);

          display: flex;
          align-items: center;
          justify-content: center;
          width: 100%;
          color: rgb(230, 237, 243);
        }
        .card {
          border: tall;
          border-color: var(--border);
          background-color: rgb(22, 27, 34);
          padding: 1 3;
          width: 50;
        }
        h1 {
          color: var(--accent);
        }
        p {
          color: var(--muted);
        }
        fieldset {
          margin-bottom: 0;
        }
        fieldset::part(legend-line) {
          color: var(--accent);
          background-color: rgb(22, 27, 34);
        }
        fieldset::part(fieldset-body) {
          border-color: var(--border);
          padding: 1 2;
        }
        .key {
          display: inline-block;
          width: 10;
          color: var(--muted);
        }
      </style>
    )html";
};

int main() {
  auto app = Ref<FieldsetDemo>::New();
  Screen screen(app);
  screen.Loop();
  return 0;
}

<tabs> and <tab-pane> ​

  • Classification: Component (rtxui::tabs, rtxui::tab_pane)

<tabs> Attributes ​

AttributeTypeDefaultReactiveDescription
valuestring""Two-wayActive pane identifier matching <tab-pane name="...">.
onchangehandler——Fired when active tab changes.

<tab-pane> Attributes ​

AttributeTypeDefaultReactiveDescription
namestring""One-wayUnique tab identifier.
labelstring""One-wayHeader text displayed on the tab button.
Fullscreen Demo
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 built-in <tabs>/<tab-pane> components.
//
// The active pane is selected by the `value` attribute bound to a C++ string.
#include <rtxui/rtxui.hpp>
#include <string>
#include <string_view>

using namespace rtxui;

class TabsDemo : public Component<TabsDemo> {
 public:
  std::string current_tab = "home";

  std::string_view view = R"html(
      <div class="card">
        <h1>Tabs</h1>
        <p>The value attribute names the active pane. Click a tab, or focus it and use the arrows.</p>

        <tabs value="{current_tab}">
          <tab-pane label="Dashboard" name="home">
            <div class="pane">Active processes: 14</div>
          </tab-pane>
          <tab-pane label="Settings" name="settings">
            <div class="pane">Preferences and theme.</div>
          </tab-pane>
          <tab-pane label="About" name="about">
            <div class="pane">RTXUI, a terminal UI library for C++.</div>
          </tab-pane>
        </tabs>

        <div class="readout">
          <span>value</span>
          <span class="value">{current_tab}</span>
        </div>
      </div>

      <style>
        self {
          --border: rgb(48, 54, 61);
          --muted: rgb(139, 148, 158);
          --accent: rgb(88, 166, 255);

          display: flex;
          align-items: center;
          justify-content: center;
          width: 100%;
          color: rgb(230, 237, 243);
        }
        .card {
          border: tall;
          border-color: var(--border);
          background-color: rgb(22, 27, 34);
          padding: 1 3;
          width: 56;
        }
        h1 {
          color: var(--accent);
        }
        p {
          color: var(--muted);
        }
        .pane {
          border: round;
          border-color: var(--border);
          padding: 1 2;
        }
        .readout {
          display: flex;
          justify-content: space-between;
          border-top: solid;
          border-color: var(--border);
          margin-top: 1;
          padding-top: 1;
          color: var(--muted);
        }
        .value {
          color: var(--accent);
          font-weight: bold;
        }
      </style>
    )html";

  TabsDemo() { Bind(current_tab); }
};

int main() {
  auto app = Ref<TabsDemo>::New();
  Screen screen(app);
  screen.Loop();
  return 0;
}

<dialog> ​

  • Classification: Modal Overlay (rtxui::dialog)
  • Layer: Top-layer modal (z-index: 100)

Attributes ​

AttributeTypeDefaultReactiveDescription
openbooleanfalseTwo-wayVisibility toggle. When true, presents centered overlay with translucent backdrop.
titlestring"Dialog"One-wayTitle text displayed in the header frame.

Keyboard Invariants: ​

Pressing the Escape key automatically dismisses an open dialog and updates the bound open property to false.

Fullscreen Demo
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 built-in <dialog> element.
//
// `open` is bound to a bool; the dialog renders centered over the rest of the
// UI with a dimmed backdrop and closes on Escape.
//
// Try it: open the dialog, then dismiss it with either button or Escape.
#include <rtxui/rtxui.hpp>
#include <string>
#include <string_view>

using namespace rtxui;

class DialogDemo : public Component<DialogDemo> {
 public:
  bool is_modal_open = false;
  std::string last_action = "Nothing yet.";

  void ShowDialog() { is_modal_open = true; }

  void Proceed() {
    last_action = "Proceeded.";
    is_modal_open = false;
  }

  void Cancel() {
    last_action = "Cancelled.";
    is_modal_open = false;
  }

  std::string_view view = R"html(
      <div class="card">
        <h1>Dialog</h1>
        <p>The dialog renders above everything else, on a dimmed backdrop.</p>

        <button onclick="ShowDialog">Open dialog</button>

        <div class="status">
          <span class="label">Last action</span>
          <span class="value">{last_action}</span>
        </div>

        <dialog open="{is_modal_open}" title="Confirm destruction">
          <p class="dialog-msg">This action is permanent and cannot be undone.</p>
          <div class="actions">
            <button class="danger" onclick="Proceed">Proceed</button>
            <button onclick="Cancel">Cancel</button>
          </div>
        </dialog>
      </div>

      <style>
        self {
          --bg: rgb(13, 17, 23);
          --border: rgb(48, 54, 61);
          --text: rgb(230, 237, 243);
          --muted: rgb(139, 148, 158);
          --accent: rgb(88, 166, 255);
          --danger: rgb(248, 81, 73);

          display: flex;
          align-items: center;
          justify-content: center;
          width: 100%;
          height: 100%;
          background-color: var(--bg);
          color: var(--text);
        }
        .card {
          border: tall;
          border-color: var(--border);
          background-color: rgb(22, 27, 34);
          padding: 1 3;
          width: 54;
        }
        h1 {
          color: var(--accent);
          font-weight: bold;
        }
        p {
          color: var(--muted);
          margin-bottom: 1;
        }

        /* Every colour is stated outright: a button that inherits the
           terminal's default foreground can end up invisible against its own
           background on a light-themed terminal. */
        button {
          border: tall;
          border-color: var(--border);
          background-color: var(--bg);
          color: var(--text);
          padding: 0 2;
          text-align: center;
          transition: background-color 0.15s ease, border-color 0.15s ease,
                      color 0.15s ease;
        }
        button:hover, button:focus {
          border-color: var(--accent);
          color: var(--accent);
        }
        button:active {
          background-color: var(--accent);
          color: var(--bg);
        }

        .status {
          display: flex;
          justify-content: space-between;
          width: 100%;
          border-top: solid;
          border-color: var(--border);
          margin-top: 1;
          padding-top: 1;
        }
        .label {
          color: var(--muted);
        }
        .value {
          color: var(--accent);
          font-weight: bold;
        }

        .dialog-msg {
          color: var(--danger);
          margin-bottom: 1;
        }
        .actions {
          display: flex;
          gap: 2;
          justify-content: flex-end;
        }
        .actions button {
          /* Without this the two buttons shrink to fit and their labels wrap
             mid-word inside the dialog's max-width. */
          flex-shrink: 0;
        }
        .actions button.danger {
          border-color: var(--danger);
          color: var(--danger);
        }
        .actions button.danger:hover, .actions button.danger:focus {
          background-color: var(--danger);
          color: var(--bg);
        }
      </style>
    )html";

  DialogDemo() {
    Bind(is_modal_open);
    Bind(last_action);
    Bind(ShowDialog);
    Bind(Proceed);
    Bind(Cancel);
  }
};

int main() {
  auto app = Ref<DialogDemo>::New();
  Screen screen(app);
  screen.SetInline(false);
  screen.Loop();
  return 0;
}

<toast> ​

  • Classification: Notification Overlay (rtxui::toast)
  • Layer: Top-layer popup (z-index: 200)

A short notification in a corner of the screen, over everything else. It closes itself after duration milliseconds, or when clicked.

Attributes ​

AttributeTypeDefaultReactiveDescription
openbooleanfalseTwo-wayShows the toast. It returns to false when the toast closes, by its timer or a click. Each opening starts the timer again.
durationinteger3000One-wayMilliseconds before it closes itself; 0 keeps it until clicked or closed by the application.
placementstring"bottom-right"One-wayCorner: bottom-right, bottom-left, top-right or top-left. Anything else is reported as a diagnostic.

Parts ​

toast (the box). It has a tall border by default; restyle it like any element, e.g. .error::part(toast) { border-color: red; } or toast::part(toast) { border: round; }. While it closes, the box is also part closing.

Animation ​

A toast slides in horizontally from its side of the screen, and slides back out when it closes: a terminal has more columns than rows, so a sideways move looks smoother than a vertical one. It stays on screen until its closing animation ends. Replace either animation with your own @keyframes, declared in your component:

css
@keyframes fade-in {
  from {
    opacity: 0;
  }
}
@keyframes fade-out {
  to {
    opacity: 0;
  }
}
toast::part(toast) {
  animation: fade-in 200ms;
}
toast::part(closing) {
  animation: fade-out 200ms forwards;
}

Write the closing rule after the toast one: the closing box is both parts. toast::part(toast) { animation: none; } turns both animations off, and a toast with no closing animation, or an infinite one, hides at once.

Several toasts open at once in the same corner overlap. To stack them, give each a different placement, or render them from a <for> inside a fixed container of your own and set toast::part(toast) { position: static; }.

Fullscreen Demo
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.
//
// Toast notifications.
//
// A <toast> shows over everything else in a corner of the screen, and closes
// itself after `duration` milliseconds, or when clicked. Its `open` attribute
// is two-way bound: the application raises its flag to show it, and the flag
// falls back to false when the toast closes.
//
// A toast slides in from its side of the screen and back out when it closes.
// The error toast replaces both animations with its own: it shakes as it
// arrives, and fades out.
//
// Try it: press Save or Fail, then wait, or click a toast to dismiss it.
#include <rtxui/rtxui.hpp>
#include <string_view>

using namespace rtxui;

class ToastDemo : public Component<ToastDemo> {
 public:
  bool saved = false;
  bool failed = false;
  int saves = 0;

  void Save() {
    saves++;
    saved = true;
  }
  void Fail() { failed = true; }

  ToastDemo() {
    Bind(saved);
    Bind(failed);
    Bind(saves);
    Bind(Save);
    Bind(Fail);
  }

  std::string_view view = R"html(
      <div class="card">
        <h1>Toasts</h1>
        <p>A toast shows a message for a while, then closes itself. Saves so far: {saves}.</p>
        <div class="row">
          <button onclick="Save">Save</button>
          <button onclick="Fail">Fail</button>
        </div>
      </div>

      <toast open="{saved}" duration="2000">✔ Saved</toast>
      <toast class="error" open="{failed}" duration="4000" placement="top-right">
        ✘ Could not reach the server
      </toast>

      <style>
        self {
          display: flex;
          align-items: center;
          justify-content: center;
          width: 100%;
          height: 100%;
          background-color: rgb(13, 17, 23);
          color: rgb(230, 237, 243);
        }
        .card {
          border: tall;
          border-color: rgb(48, 54, 61);
          background-color: rgb(22, 27, 34);
          padding: 1 3;
          width: 44;
        }
        h1 {
          color: rgb(88, 166, 255);
        }
        p {
          color: rgb(139, 148, 158);
        }
        .row {
          display: flex;
          gap: 2;
        }
        button {
          border: tall;
          border-color: rgb(48, 54, 61);
          background-color: rgb(13, 17, 23);
          padding: 0 1;
        }
        button:hover, button:focus {
          border-color: rgb(88, 166, 255);
          color: rgb(88, 166, 255);
        }
        /* Slides in, then swings sideways a few times, less each time. */
        @keyframes shake-in {
          from {
            translate: calc(100% + 2);
            animation-timing-function: ease-out-cubic;
          }
          30% { translate: 0; }
          42% { translate: -6; }
          54% { translate: 2; }
          66% { translate: -4; }
          78% { translate: 1; }
          90% { translate: -2; }
        }
        @keyframes fade-out {
          to { opacity: 0; }
        }
        .error::part(toast) {
          border-color: rgb(248, 81, 73);
          color: rgb(255, 161, 152);
          animation: shake-in 1s ease-in-out;
        }
        /* After the rule above: a closing toast is both parts. */
        .error::part(closing) {
          animation: fade-out 300ms forwards;
        }
      </style>
    )html";
};

int main() {
  auto app = Ref<ToastDemo>::New();
  Screen screen(app);
  screen.SetInline(false);
  screen.Loop();
  return 0;
}

<tree-item> ​

  • Classification: Interactive Tree Node (rtxui::tree_item)
  • Focus: Its row is focusable.

One node of a tree: a row showing label, and the <tree-item>s nested inside it, shown indented below while it is open. A node with no <tree-item> (or other element) inside is a leaf.

html
<tree-item label="src" open="{src_open}">
  <tree-item label="main.cpp" onclick="Open(main.cpp)"/>
  <tree-item label="ui">
    <tree-item label="app.cpp" onclick="Open(ui/app.cpp)"/>
  </tree-item>
</tree-item>

Attributes ​

AttributeTypeDefaultReactiveDescription
labelstring""One-wayText of the row.
openbooleanfalseTwo-wayWhether the children show. Ignored on a leaf.
onclickcallback——Runs when the row is clicked or activated with Enter. A branch without one toggles instead.

Interaction ​

  • Clicking the arrow toggles a branch. Clicking the row, or pressing Enter on it, runs its onclick; a branch without one toggles.
  • Right opens and Left closes the focused branch; with nothing to open or close, the arrows move focus as usual.

Parts ​

row, arrow (▸, ▾, or blank for a leaf), label, children.

Fullscreen Demo
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.
//
// A tree view.
//
// Each <tree-item> is a focusable row with a `label`, and the <tree-item>s
// nested inside it are its children, shown indented while it is `open`.
// Clicking a branch, or pressing Enter on it, toggles it; Right opens and Left
// closes the focused branch. A leaf's own `onclick` runs when it is picked.
//
// Try it: Tab into the tree, then use the arrows and Enter.
#include <rtxui/rtxui.hpp>
#include <string>
#include <string_view>
#include <utility>

using namespace rtxui;

class TreeDemo : public Component<TreeDemo> {
 public:
  bool src_open = true;
  std::string picked = "nothing";

  void Pick(std::string file) { picked = std::move(file); }

  TreeDemo() {
    Bind(src_open);
    Bind(picked);
    Bind(Pick);
  }

  std::string_view view = R"html(
      <div class="card">
        <h1>Project</h1>
        <div class="tree">
          <tree-item label="src" open="{src_open}">
            <tree-item label="main.cpp" onclick="Pick(src/main.cpp)"/>
            <tree-item label="ui">
              <tree-item label="app.cpp" onclick="Pick(src/ui/app.cpp)"/>
              <tree-item label="app.hpp" onclick="Pick(src/ui/app.hpp)"/>
            </tree-item>
          </tree-item>
          <tree-item label="tests">
            <tree-item label="app_test.cpp" onclick="Pick(tests/app_test.cpp)"/>
          </tree-item>
          <tree-item label="README.md" onclick="Pick(README.md)"/>
        </div>
        <p>Picked: {picked}</p>
      </div>

      <style>
        self {
          display: flex;
          align-items: center;
          justify-content: center;
          width: 100%;
          color: rgb(230, 237, 243);
        }
        .card {
          border: tall;
          border-color: rgb(48, 54, 61);
          background-color: rgb(22, 27, 34);
          padding: 1 3;
          width: 40;
        }
        h1 {
          color: rgb(88, 166, 255);
          font-weight: bold;
        }
        .tree {
          margin: 1 0;
        }
        tree-item::part(arrow) {
          color: rgb(139, 148, 158);
        }
      </style>
    )html";
};

int main() {
  auto app = Ref<TreeDemo>::New();
  Screen screen(app);
  screen.Loop();
  return 0;
}

<tooltip> ​

  • Classification: Hover Overlay (rtxui::tooltip)
  • Layer: Top-layer popup (z-index: 1000)

Attributes ​

AttributeTypeDefaultReactiveDescription
contentstring""One-wayText rendered inside the floating popup.
placementstring"top"One-wayPositioning anchor: "top", "bottom", "left", "right", "top-start", "top-end", "bottom-start", "bottom-end".
Fullscreen Demo
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 <tooltip> component, shown on hover in each of four directions.
#include <rtxui/rtxui.hpp>
#include <string>
#include <string_view>
#include <vector>

using namespace rtxui;

class TooltipDemo : public Component<TooltipDemo> {
 public:
  std::vector<std::string> placements = {
      "top-start",  "top",  "top-end",  "bottom-start", "bottom", "bottom-end",
      "left-start", "left", "left-end", "right-start",  "right",  "right-end",
  };
  std::string custom_text = "Edit the field above to change this.";

  std::string_view view = R"html(
      <div class="card">
        <h1>Tooltips</h1>
        <p>Hover a button to show its tooltip, on the side its placement names.</p>

        <div class="grid">
          <for each="{placements}" as="placement">
            <tooltip content="{placement}" placement="{placement}">
              <button>{placement}</button>
            </tooltip>
          </for>
        </div>

        <p>The content attribute can be bound, like any other:</p>
        <input value="{custom_text}" />
        <tooltip content="{custom_text}" placement="bottom">
          <button>Hover me</button>
        </tooltip>
      </div>

      <style>
        self {
          --border: rgb(48, 54, 61);
          --muted: rgb(139, 148, 158);
          --accent: rgb(88, 166, 255);

          display: flex;
          align-items: center;
          justify-content: center;
          width: 100%;
          color: rgb(230, 237, 243);
        }
        .card {
          border: tall;
          border-color: var(--border);
          background-color: rgb(22, 27, 34);
          padding: 1 3;
          width: 66;
        }
        h1 {
          color: var(--accent);
        }
        p {
          color: var(--muted);
        }
        .grid {
          display: grid;
          grid-template-columns: repeat(3, 1fr);
          gap: 1 4;
          margin: 1 0;
        }
        button {
          text-align: center;
          background-color: rgb(13, 17, 23);
        }
        .grid button {
          width: 100%;
        }
        button:hover {
          background-color: rgb(22, 45, 77);
          color: var(--accent);
        }
        input {
          width: 100%;
          border: tall;
          border-color: var(--border);
          background-color: rgb(13, 17, 23);
          padding: 0 1;
          margin-bottom: 1;
        }
        input:focus {
          border-color: var(--accent);
        }
      </style>
    )html";

  TooltipDemo() {
    Bind(placements);
    Bind(custom_text);
  }
};

int main() {
  auto app = Ref<TooltipDemo>::New();
  Screen screen(app);
  screen.Loop();
  return 0;
}

6. Typography & Formatting Elements ​

TagClassificationDefault StyleSemantic Invariant
<h1>, <h2>, <h3>, <h4>, <h5>, <h6>Blockdisplay: block; font-weight: bold;Hierarchical headings with stepped margins and sizes.
<p>Blockdisplay: block; margin: 1 0;Paragraph block with vertical separation.
<a>Inlinecolor: rgb(59, 130, 246); text-decoration: underline;Hyperlink. href="#id" scrolls the element with that id into view, when clicked or when focused and activated with Enter. With an href, it is in the Tab order, as in a browser. An href that is a URL with a scheme (https://…, mailto:…, file://…) makes the text a terminal hyperlink (OSC 8): kitty, WezTerm, iTerm2, foot, GNOME Terminal and Windows Terminal open it the way each does for links (usually Ctrl- or Cmd-click, plus Shift in some while the app reads the mouse), and other terminals show the text as usual (example).
<b>, <strong>Inlinefont-weight: bold;Emphasized text with bold weight attribute.
<i>, <em>Inlinefont-style: italic;Slanted text with italic terminal attribute.
<u>Inlinetext-decoration: underline;Underlined text.
<s>, <strike>, <del>Inlinetext-decoration: line-through;Strikethrough text.
<code>Inlinebackground-color: rgb(40, 40, 40);Monospaced inline code snippet.
<kbd>Inlinedisplay: inline; font-weight: bold;Keyboard shortcut token.
<pre>Blockwhite-space: pre;Preformatted text preserving line breaks and spaces.
<blockquote>Blockdisplay: block; padding-left: 2; border-left: solid;Indented quotation block with left accent line.
<hr />Blockheight: 1; border-top: solid;Full-width horizontal divider line.
<br />Inline—Forces hard line break in inline flow algorithms.

<markdown> ​

  • Classification: Block Component (rtxui::markdown)

Attributes ​

AttributeTypeDefaultReactiveDescription
contentstring""Two-wayRaw Markdown text to parse and render.
stylesheetstring""One-wayCustom CSS applied to the rendered DOM tree.
Fullscreen Demo
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 <hr> separator, and how border styles apply to it.
#include <rtxui/rtxui.hpp>
#include <string_view>

using namespace rtxui;

class HrDemo : public Component<HrDemo> {
 public:
  std::string_view view = R"html(
      <div class="card">
        <h1>Horizontal rule</h1>
        <p>&lt;hr&gt; draws a line across its container to separate sections.</p>

        <h2>Introduction</h2>
        <div>The first section, above the rule.</div>

        <hr />

        <h2>Details</h2>
        <div>The second section, below it.</div>
      </div>

      <style>
        self {
          --border: rgb(48, 54, 61);
          --muted: rgb(139, 148, 158);
          --accent: rgb(88, 166, 255);

          display: flex;
          align-items: center;
          justify-content: center;
          width: 100%;
          color: rgb(230, 237, 243);
        }
        .card {
          border: tall;
          border-color: var(--border);
          background-color: rgb(22, 27, 34);
          padding: 1 3;
          width: 54;
        }
        h1 {
          color: var(--accent);
        }
        p {
          color: var(--muted);
        }
        h2 {
          margin-bottom: 0;
        }
      </style>
    )html";
};

int main() {
  auto app = Ref<HrDemo>::New();
  Screen screen(app);
  screen.Loop();
  return 0;
}

7. Lists (<ul>, <ol>, <li>) ​

Unordered Lists (<ul>) ​

Unordered lists render alternating glyph markers based on structural nesting depth:

  • Level 1: Disc (• )
  • Level 2: Circle (○ )
  • Level 3+: Square (■ )

Ordered Lists (<ol>) ​

Ordered lists format numbered decimal prefixes (1. , 2. ):

  • start (integer, default 1): Initial counter value.
  • reversed (boolean, default false): Counts down rather than up.

List Items (<li>) ​

  • value (integer): Explicit sequence override within ordered lists.
Fullscreen Demo
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.
//
// <ul>, <ol> and <li>, including nesting, `start`/`reversed`, per-item `value`,
// and the list-style-type property.
#include <rtxui/rtxui.hpp>
#include <string>
#include <string_view>

using namespace rtxui;

class ListsDemo : public Component<ListsDemo> {
 public:
  std::string selected_style = "disc";

  std::string_view view = R"html(
      <div class="card">
        <h1>Lists</h1>
        <p>&lt;ul&gt; nests with a new bullet per level; &lt;ol&gt; counts, from start, or down when reversed.</p>

        <div class="style">
          <span>list-style-type</span>
          <select value="{selected_style}">
            <option value="disc">disc</option>
            <option value="circle">circle</option>
            <option value="square">square</option>
            <option value="decimal">decimal</option>
            <option value="none">none</option>
          </select>
        </div>

        <div class="columns">
          <ul class="styled">
            <li>Apples</li>
            <li>Pears</li>
            <li>Plums</li>
          </ul>
          <ul>
            <li>Fruit
              <ul>
                <li>Citrus
                  <ul>
                    <li>Lemon</li>
                  </ul>
                </li>
                <li>Berries</li>
              </ul>
            </li>
            <li>Vegetables</li>
          </ul>
          <ol start="3">
            <li>Clone</li>
            <li>Build</li>
            <li value="10">Ship</li>
          </ol>
          <ol reversed="true">
            <li>Three</li>
            <li>Two</li>
            <li>One</li>
          </ol>
        </div>
      </div>

      <style>
        self {
          --border: rgb(48, 54, 61);
          --muted: rgb(139, 148, 158);
          --accent: rgb(88, 166, 255);

          display: flex;
          align-items: center;
          justify-content: center;
          width: 100%;
          color: rgb(230, 237, 243);
        }
        .card {
          border: tall;
          border-color: var(--border);
          background-color: rgb(22, 27, 34);
          padding: 1 3;
          width: 70;
        }
        h1 {
          color: var(--accent);
        }
        p {
          color: var(--muted);
        }
        .style {
          display: flex;
          gap: 2;
          margin-bottom: 1;
          color: var(--muted);
        }
        select {
          width: 14;
          color: rgb(230, 237, 243);
        }
        select:focus {
          color: var(--accent);
        }
        .columns {
          display: flex;
          gap: 3;
        }
        .styled {
          list-style-type: {selected_style};
        }
      </style>
    )html";

  ListsDemo() { Bind(selected_style); }
};

int main() {
  auto app = Ref<ListsDemo>::New();
  Screen screen(app);
  screen.Loop();
  return 0;
}

8. Template Control Flow ​

<if>, <elif>, <else> ​

Conditional evaluation nodes. Direct children of <if> branches are mounted or unmounted dynamically:

xml
<if condition="{has_error}">
  <span class="error">{error_message}</span>
</if>
<elif condition="{is_loading}">
  <span>Loading...</span>
</elif>
<else>
  <span>Ready.</span>
</else>

<for> ​

Repeats template structures across bound collections. Syntax: <for each="{collection}" as="item">. The loop index is available as {$index} (zero-based).

  • each: Bound collection expression.
  • as: Variable alias for collection element.
  • key: (Optional) Unique identifier field on struct item. Preserves element state and cursor position across sort and filter operations.
  • virtual: (Optional) Inside a scroll container, renders only the items around what it shows. See Long lists.
  • item-height: (Optional, with virtual) Rows each item takes; 1 by default.

<slot> and <template> (Content Projection) ​

Enables reusable components to project child content passed by their caller:

  • Default Slot: <slot></slot> inside a component definition renders all unassigned child elements.
  • Named Slots: <slot name="header"></slot> defines a named projection target.
  • Content Injection: Callers project an element into a named slot with slot="header" on it, or several nodes at once with <template slot="header">. Content naming the same slot fills it in document order.
  • Fallback Content: Markup placed inside <slot>Fallback</slot> is rendered if the caller provides no projecting children.
xml
<!-- Component Definition (e.g. Card) -->
<div class="card">
  <div class="card-header"><slot name="header">Default Header</slot></div>
  <div class="card-body"><slot></slot></div>
</div>

<!-- Caller Usage -->
<Card>
  <span slot="header">Custom Title</span>
  <p>Body projected into default slot.</p>
</Card>
Fullscreen Demo
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.
//
// Content projection with <slot>.
//
// A reusable Card component places its caller's markup into named slots, which
// is how every built-in tag is implemented too.
#include <rtxui/rtxui.hpp>
#include <string_view>

using namespace rtxui;

// A card with a header, a body and a footer, each filled by the caller.
class Card : public Component<Card> {
 public:
  std::string_view view = R"html(
    <div class="header">
      <slot name="header">Untitled</slot>
    </div>
    <slot></slot>
    <div class="footer">
      <slot name="footer"></slot>
    </div>

    <style>
      self {
        display: block;
        width: 50;
        border: tall;
        border-color: rgb(48, 54, 61);
        background-color: rgb(22, 27, 34);
        padding: 1 3;
      }
      .header {
        color: rgb(88, 166, 255);
        font-weight: bold;
        margin-bottom: 1;
      }
      .footer {
        border-top: solid;
        border-color: rgb(48, 54, 61);
        margin-top: 1;
        color: rgb(139, 148, 158);
      }
    </style>
  )html";
};

class SlotsDemo : public Component<SlotsDemo> {
 public:
  std::string_view view = R"html(
    <Card>
      <span slot="header">Slots</span>

      <div>Card places what it is given into its slots: this text in the default slot, the title and this footer in named ones.</div>

      <template slot="footer">Card's own markup draws the frame and the rule.</template>
    </Card>

    <style>
      self {
        display: flex;
        align-items: center;
        justify-content: center;
        width: 100%;
        color: rgb(230, 237, 243);
      }
    </style>
  )html";

  SlotsDemo() { Import<Card>(); }
};

int main() {
  auto app = Ref<SlotsDemo>::New();
  Screen screen(app);
  screen.Loop();
  return 0;
}