Skip to content

Typography & Text Styling ​

RTXUI supports rich text styling including colors, alignment, decorations, bold/strong emphasis, and opacity. These are applied via CSS properties in <style> blocks, just like in the web.


Colors ​

RTXUI supports multiple color formats for color and background-color properties:

FormatExample
Named keywordsred, navy, silver, fuchsia, …
Hex (short)#f0f, #0ff
Hex (full)#ff3366, #151520
Hex with alpha#00ffcc88, #ff000080
rgb()rgb(255, 0, 128)
rgba()rgba(255, 0, 0, 0.5)

The 16 standard CSS color keywords are supported (black, white, red, green, blue, yellow, aqua, fuchsia, gray, silver, maroon, olive, navy, purple, teal, lime).

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.
//
// Foreground and background colors.
//
// Colors accept named, #rgb / #rrggbb / #rrggbbaa, rgb() and rgba() forms.
// Each swatch is drawn by a <for> loop, its color set with an inline style.
#include <rtxui/rtxui.hpp>
#include <string>
#include <string_view>
#include <vector>

using namespace rtxui;

struct Swatch {
  std::string color;
  bool light = false;  // Whether it needs dark text.
  bool operator==(const Swatch& other) const = default;
};

void Fields(const Swatch& swatch, FieldSink& out) {
  out("color", swatch.color);
  out("ink", swatch.light ? "black" : "white");
}

class ColorDemo : public Component<ColorDemo> {
 public:
  std::vector<Swatch> named = {
      {"black"},  {"gray"},       {"silver", true}, {"white", true},
      {"maroon"}, {"red"},        {"purple"},       {"fuchsia"},
      {"green"},  {"lime", true}, {"olive"},        {"yellow", true},
      {"navy"},   {"blue"},       {"teal"},         {"aqua", true},
  };
  std::vector<Swatch> hex = {
      {"#f0f"},
      {"#0ff", true},
      {"#ff3366"},
      {"#33cc66", true},
      {"#bae1ff", true},
      {"#e8c4ff", true},
      {"#00ffcc88", true},
      {"#151520"},
  };
  std::vector<Swatch> rgb = {
      {"rgb(255, 0, 128)"},
      {"rgb(0, 255, 128)", true},
      {"rgb(128, 0, 255)"},
      {"rgb(255, 128, 0)", true},
  };
  std::vector<Swatch> rgba = {
      {"rgba(255, 0, 0, 0.5)"},
      {"rgba(0, 0, 255, 0.3)"},
      {"rgba(0, 255, 0, 0.7)", true},
      {"rgba(255, 255, 255, 0.2)"},
  };

  std::string_view view = R"html(
      <div class="card">
        <h1>Colors</h1>
        <p>Every CSS color form; alpha blends with what is underneath.</p>

        <h2>Named</h2>
        <div class="swatches">
          <for each="{named}" as="s">
            <div class="swatch" style="background-color: {s.color}; color: {s.ink};">{s.color}</div>
          </for>
        </div>

        <h2>Hex</h2>
        <div class="swatches">
          <for each="{hex}" as="s">
            <div class="swatch" style="background-color: {s.color}; color: {s.ink};">{s.color}</div>
          </for>
        </div>

        <h2>rgb() and rgba()</h2>
        <div class="swatches wide">
          <for each="{rgb}" as="s">
            <div class="swatch" style="background-color: {s.color}; color: {s.ink};">{s.color}</div>
          </for>
          <for each="{rgba}" as="s">
            <div class="swatch" style="background-color: {s.color}; color: {s.ink};">{s.color}</div>
          </for>
        </div>
      </div>

      <style>
        self {
          display: flex;
          align-items: center;
          justify-content: center;
          width: 100%;
          height: 70%;
          background-color: rgba(13, 17, 23, 0.0);
          color: rgb(230, 237, 243);
          overflow-y: auto;
        }
        .card {
          border: tall;
          border-color: rgb(48, 54, 61);
          background-color: rgb(22, 27, 34);
          padding: 1 3;
          width: 84;
        }
        h1 {
          color: rgb(88, 166, 255);
        }
        p, h2 {
          color: rgb(139, 148, 158);
        }
        h2 {
          margin: 2 0 1 0;
          font-weight: normal;
        }
        .swatches {
          display: grid;
          grid-template-columns: repeat(4, 1fr);
          gap: 1 2;
        }
        .wide {
          grid-template-columns: repeat(2, 1fr);
        }
        .swatch {
          padding: 1;
          border: tall;
          border-color: rgb(0,0,0,0.1);
          text-align: center;
        }
        .swatch:hover {
          border-color: white;
        }

      </style>
    )html";

  ColorDemo() {
    Bind(named);
    Bind(hex);
    Bind(rgb);
    Bind(rgba);
  }
};

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

Weight and Style ​

Terminals render four text intensities and an italic face; RTXUI maps the standard CSS properties onto them:

css
font-weight: bold;      /* bold — also: bolder, or numbers >= 600 */
font-weight: lighter;   /* dim  — also: numbers <= 300 */
font-weight: normal;
font-style: italic;     /* also: oblique */

The semantic inline tags apply these for you: <b>/<strong> for bold, <i>/<em> for italic, <u> for underline, <s>/<del> for strikethrough.


Case Transformation ​

text-transform changes the rendered case without touching the underlying text. It is inherited.

css
text-transform: uppercase;   /* MENU */
text-transform: lowercase;   /* menu */
text-transform: capitalize;  /* Menu Item */

ASCII letters are transformed; other characters pass through unchanged.


Spacing ​

letter-spacing inserts blank cells between characters, and line-height sets how many rows each line occupies. Both take whole numbers of cells and are inherited:

css
h1 {
  letter-spacing: 1;  /* H E A D I N G */
}
p {
  line-height: 2;  /* One blank row between lines. */
}

The cells added by letter-spacing are non-breaking: a spaced word never wraps in the middle, while the gaps between words stay breakable. line-height is a minimum — a taller inline element still grows its line.


Text Alignment ​

Use text-align to control horizontal text placement within a block element:

css
text-align: left;    /* Default */
text-align: center;
text-align: right;
text-align: justify;

justify widens the spaces of soft-wrapped lines so each fills the content width; the last line, and lines ended by an explicit newline, keep their natural width. The alignment is inherited by child elements.

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.
//
// text-align: left, center and right.
#include <rtxui/rtxui.hpp>
#include <string_view>

using namespace rtxui;

class TextAlignDemo : public Component<TextAlignDemo> {
 public:
  std::string_view view = R"html(
      <div class="card">
        <h1>Text alignment</h1>
        <p>text-align places each line within its box.</p>

        <div class="box left">
          <span class="name">left</span>
          Lines start at the left edge, which is the default.
        </div>
        <div class="box center">
          <span class="name">center</span>
          Lines are centered between both edges.
        </div>
        <div class="box right">
          <span class="name">right</span>
          Lines end at the right edge.
        </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: 46;
        }
        h1 {
          color: var(--accent);
        }
        p {
          color: var(--muted);
        }
        .box {
          border: round;
          border-color: var(--border);
          padding: 0 1;
          margin-top: 1;
        }
        .name {
          display: block;
          color: var(--accent);
        }
        .left {
          text-align: left;
        }
        .center {
          text-align: center;
        }
        .right {
          text-align: right;
        }
      </style>
    )html";
};

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

Text Decoration ​

Use text-decoration to add visual emphasis to text. Multiple decorations can be combined by separating them with spaces.

ValueEffect
underlineSingle underline
double-underlineDouble underline
line-through / strikethroughHorizontal line through text
blinkBlinking text (terminal support varies)
noneRemove decorations

Combine multiple decorations:

css
text-decoration: double-underline strikethrough;
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.
//
// Text decoration: bold, dim, italic, underline and strikethrough.
#include <rtxui/rtxui.hpp>
#include <string_view>

using namespace rtxui;

class TextDecorationDemo : public Component<TextDecorationDemo> {
 public:
  std::string_view view = R"html(
      <div class="card">
        <h1>Text decoration</h1>
        <p>Terminal text attributes, set from CSS.</p>

        <div class="samples">
          <span class="name">font-weight: bold</span>
          <span class="bold">The quick brown fox</span>
          <span class="name">font-weight: lighter</span>
          <span class="lighter">The quick brown fox</span>
          <span class="name">font-style: italic</span>
          <span class="italic">The quick brown fox</span>
          <span class="name">underline</span>
          <span class="underline">The quick brown fox</span>
          <span class="name">double-underline</span>
          <span class="double-underline">The quick brown fox</span>
          <span class="name">overline</span>
          <span class="overline">The quick brown fox</span>
          <span class="name">line-through</span>
          <span class="line-through">The quick brown fox</span>
          <span class="name">blink</span>
          <span class="blink">The quick brown fox</span>
          <span class="name">combined</span>
          <span class="combined">The quick brown fox</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);
        }
        .samples {
          display: grid;
          grid-template-columns: 24 auto;
        }
        .name {
          color: var(--muted);
        }
        .bold {
          font-weight: bold;
        }
        .lighter {
          font-weight: lighter;
        }
        .italic {
          font-style: italic;
        }
        .underline {
          text-decoration: underline;
        }
        .double-underline {
          text-decoration: double-underline;
        }
        .overline {
          text-decoration: overline;
        }
        .line-through {
          text-decoration: line-through;
        }
        .blink {
          text-decoration: blink;
        }
        .combined {
          font-weight: bold;
          font-style: italic;
          text-decoration: underline line-through;
          color: var(--accent);
        }
      </style>
    )html";
};

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

Whitespace and Wrapping ​

white-space controls how newlines and wrapping behave inside an element:

ValueNewlinesWrapping
normal (default)Rendered as spacesWraps at spaces
nowrapRendered as spacesNever wraps
preHard line breaksNever wraps
pre-wrapHard line breaksWraps at spaces
pre-lineHard line breaks; space runs collapse to oneWraps at spaces

The <pre> element sets white-space: pre for you; <textarea> uses pre-wrap. For text that must not wrap, pair nowrap with text-overflow: ellipsis to truncate with ... instead of overflowing. Three periods rather than …, because they are one cell each and every terminal has them; in a container too narrow for the text plus the marker, as many periods are shown as fit. Truncation lands on a character boundary, so a double-width glyph is dropped whole rather than halved:

css
.crumb {
  white-space: nowrap;
  text-overflow: ellipsis;
}

To show a few lines of wrapped text rather than one, line-clamp keeps the first N lines of a block and ends the last with the same ... when it cut anything:

css
.summary {
  line-clamp: 2;
}

text-indent shifts the first line of a block. A negative indent with as much padding hangs it, which suits a list marker written in the text:

css
.note {
  padding-left: 2;
  text-indent: -2;
}

Words longer than the line break at the container edge by default (overflow-wrap: anywhere — note this differs from CSS, whose default lets long words overflow). Set overflow-wrap: normal to keep such words intact and let them overflow instead:

css
.url {
  overflow-wrap: normal;
}

Opacity ​

The opacity property controls the transparency of an element and all its children. Values range from 0.0 (fully transparent) to 1.0 (fully opaque).

css
opacity: 0.5;

Opacity is multiplicative: a child with opacity: 0.5 inside a parent with opacity: 0.5 will render at 25% opacity. Opacity can also be animated using CSS transitions.

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.
//
// Opacity and alpha blending, including how nested opacity compounds.
#include <rtxui/rtxui.hpp>
#include <string_view>

using namespace rtxui;

class OpacityDemo : public Component<OpacityDemo> {
 public:
  int opacity_percent = 70;

  float opacity_float() const { return opacity_percent / 100.0f; }
  float nested_opacity() const { return opacity_float() * 0.5f; }

  std::string_view view = R"html(
      <div class="card">
        <h1>Opacity</h1>
        <p>An element and everything in it blend with what is behind. Nested opacity multiplies.</p>

        <div class="control">
          <slider value="{opacity_percent}" min="0" max="100" step="10" width="40" />
          <span class="value">{opacity_float}</span>
        </div>

        <div class="outer">
          opacity: {opacity_float}
          <div class="inner">opacity: 0.5 inside, so {nested_opacity} in all</div>
        </div>

        <div class="fade" tabindex="0">Hover or focus me: opacity eases to 1</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);
        }
        .control {
          display: flex;
          justify-content: space-between;
        }
        slider:focus {
          color: var(--accent);
        }
        .value {
          color: var(--accent);
          font-weight: bold;
        }
        .outer {
          border: tall;
          border-color: var(--accent);
          background-color: rgb(22, 45, 77);
          padding: 0 2;
          margin-top: 1;
          opacity: {opacity_float};
        }
        .inner {
          border: tall;
          border-color: rgb(188, 140, 255);
          background-color: rgb(52, 36, 79);
          padding: 0 2;
          opacity: 0.5;
        }
        .fade {
          border: tall;
          border-color: var(--border);
          background-color: rgb(13, 17, 23);
          text-align: center;
          margin-top: 1;
          opacity: 0.4;
          transition: opacity 0.3s ease-in-out;
        }
        .fade:hover, .fade:focus {
          opacity: 1;
        }
      </style>
    )html";

  OpacityDemo() {
    Bind(opacity_percent);
    Bind(opacity_float);
    Bind(nested_opacity);
  }
};

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

Selecting and Copying Text ​

Dragging the mouse over text selects it, as in a browser: the text from the character the drag started on to the one it is on, in document order, shown in reverse video. A double click selects a word, a triple click the whole block (a paragraph, say), and a drag after either extends by words or blocks. Shift and a click extends the selection from where the last click was to there. Releasing the button copies the selection to the system clipboard (OSC 52, so it works over SSH). The next click clears it. Ctrl+A selects and copies the whole page, unless what has the focus takes it, as an input does for its own text.

The selection is of the document, not of the screen. Dragging from one column of a layout to the one beside it takes the end of the first and the start of the second, not the rows of both. Scrolling moves the selection with its text, and what is scrolled out of view is copied too. Dragging past the edge of the scroll container the selection started in scrolls it, faster the further the mouse is, and the selection follows; at its end, the scroll container around it takes over, once the mouse is past that one's edge too; for a container that reaches the edge of the screen, the screen's edge row or column does it. A paragraph's wrapped lines are copied as one line; separate blocks, and lines a <br> or a newline in <pre> ends, on lines of their own. Text is copied as written, before text-transform changes its case.

Only text is taken: borders, scrollbars and empty space are left out, and a gap in a row of selected text, a border between table cells say, is copied as a space. A drag that starts on an element with a click handler, on a link, in an input or on a scrollbar does what it does there instead.

A ::selection rule changes the highlight's colors; see ::selection.

user-select chooses what can be selected. It is inherited:

css
.toolbar { user-select: none; } /* never selected; a drag from it selects nothing */
code     { user-select: all; }  /* a click selects and copies it whole */