Skip to content

Box Model & Dimensions ​

RTXUI elements behave as standard CSS boxes, though spacing measurements represent terminal character cells instead of pixels.

Margin & Padding ​

Spacing properties accept positive integer values representing the number of character columns (for horizontal spacing) or character rows (for vertical spacing):

  • Shorthand Syntax:
    • padding: 1: Spacing of 1 cell on all sides (top, right, bottom, left).
    • padding: 1 2: Spacing of 1 vertical cell (top/bottom) and 2 horizontal cells (left/right).
  • Specific Properties:
    • margin-top, margin-bottom, margin-left, margin-right
    • padding-top, padding-bottom, padding-left, padding-right
css
.card {
  margin: 1 2; /* 1 row margin top/bottom, 2 columns left/right */
  padding: 1;   /* 1 cell padding internally on all sides */
}

Differences from CSS ​

Two box-model behaviours differ from a browser and are worth knowing before you port a stylesheet:

Vertical margins do not collapse. In CSS, a margin-bottom: 1 above a margin-top: 1 collapse into a single cell of space. Here they add up to two. Set the margin on one side only when you want a predictable gap, or use gap on a flex container:

css
.a { margin-bottom: 1; }
.b { margin-top: 1; }   /* two blank rows between them, not one */

Auto margins centre inside a parent, not at the component root.margin: 0 auto (and the margin-left/margin-right longhands) centre a fixed-width block within its containing block, and a single auto pushes the block to the opposite edge. A block that is the component's own root element has no containing block to centre within, so wrap it:

html
<div><div class="card">centred</div></div>
css
.card { width: 40; margin: 0 auto; }

For centring a whole screen, display: flex with justify-content: center on the root is usually clearer than auto margins.

Dimension Units ​

Sizing attributes width and height accept two types of length units:

  1. Integer Cell Counts: A raw number indicates character cell counts.
    css
    .sidebar { width: 20; } /* Exactly 20 columns wide */
  2. Percentage Boundaries: Appending a % dynamically resolves the dimension relative to the parent box.
    css
    .half-pane { width: 50%; } /* Fills half the parent width */

Both forms combine in calc() and are bounded by min(), max(), and clamp() — see values in a terminal. min-width/max-width and min-height/max-height constrain the resolved size.

box-sizing ​

width and height describe the border box (border + padding + content) by default — unlike web CSS, whose initial value is content-box. A box styled width: 10; padding-left: 2; border: solid; is exactly 10 cells wide; the content area shrinks to fit inside the padding and border.

css
.card { width: 10; padding: 1; border: solid; } /* 10 cells wide total */

Set box-sizing: content-box to make width/height (and their min-/max- variants, and flex-basis) describe the content area instead, with padding and border added on top — the familiar web-CSS behavior:

css
.card {
  box-sizing: content-box;
  width: 10;    /* 10 cells of content ... */
  padding: 1;   /* ... plus 1 cell of padding each side ... */
  border: solid; /* ... plus a 1-cell border each side: 14 cells total. */
}

Aspect Ratio ​

aspect-ratio: <width> / <height> derives a block element's automatic height from its used width. Ratios are measured in cells; since terminal cells are roughly twice as tall as they are wide, 2 / 1 produces a visually square box:

css
.tile { width: 30%; aspect-ratio: 2 / 1; }

An explicit height, or min-/max-height, takes precedence. Content taller than the ratio overflows — combine with overflow when clipping is wanted.

Live Demo - Borders ​

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.
//
// Every border style: solid, double, dashed, round, tall, vkey and more.
//
// Each tile names the style it draws, so this doubles as a lookup table.
#include <rtxui/rtxui.hpp>

using namespace rtxui;

class BorderBox : public Component<BorderBox> {
 public:
  struct Props {
    std::string title = "Border";
    std::string border_class = "solid";
  } props;

  std::string_view view = R"html(
      <div class="box-wrapper">
        <div class="label">{title}</div>
      </div>

      <style>
        self {
          display: block;
          flex-grow: 1;
        }
        .box-wrapper {
          border: {border_class};
          background-color:red;
          border-color: rgb(100, 200, 255);
          padding: 1;
          margin: 1;
          display: block;
        }
        .label {
          font-weight: bold;
          color: rgb(100, 200, 255);
          text-align: center;
        }
      </style>
    )html";

  BorderBox() {
    Bind(props.title);
    Bind(props.border_class);
  }
};

class BordersDemo : public Component<BordersDemo> {
 public:
  std::string_view view = R"html(
      <div class="content">
        <h1>RTXUI Border Styles Gallery</h1>
        <p>This demo showcases the 24 different border styles supported by RTXUI.</p>

        <div class="row">
          <BorderBox title="ascii" border_class="ascii"></BorderBox>
          <BorderBox title="blank" border_class="blank"></BorderBox>
          <BorderBox title="dashed" border_class="dashed"></BorderBox>
          <BorderBox title="double" border_class="double"></BorderBox>
        </div>

        <div class="row">
          <BorderBox title="hkey" border_class="hkey"></BorderBox>
          <BorderBox title="heavy" border_class="heavy"></BorderBox>
          <BorderBox title="inner" border_class="inner"></BorderBox>
          <BorderBox title="none" border_class="none"></BorderBox>
        </div>

        <div class="row">
          <BorderBox title="outer" border_class="outer"></BorderBox>
          <BorderBox title="panel" border_class="panel"></BorderBox>
          <BorderBox title="round" border_class="round"></BorderBox>
          <BorderBox title="solid" border_class="solid"></BorderBox>
        </div>

        <div class="row">
          <BorderBox title="tall" border_class="tall"></BorderBox>
          <BorderBox title="thick" border_class="thick"></BorderBox>
          <BorderBox title="vkey" border_class="vkey"></BorderBox>
          <BorderBox title="wide" border_class="wide"></BorderBox>
        </div>

        <div class="row">
          <BorderBox title="dotted" border_class="dotted"></BorderBox>
          <BorderBox title="double-horiz" border_class="double-horizontal"></BorderBox>
          <BorderBox title="double-vert" border_class="double-vertical"></BorderBox>
          <BorderBox title="shadow (3d)" border_class="shadow"></BorderBox>
        </div>

        <div class="row">
          <BorderBox title="shade-light" border_class="shade-light"></BorderBox>
          <BorderBox title="shade-med" border_class="shade-medium"></BorderBox>
          <BorderBox title="shade-dark" border_class="shade-dark"></BorderBox>
          <BorderBox title="squiggle" border_class="squiggle"></BorderBox>
        </div>
      </div>

      <style>
        self {
          display: block;
          padding: 1;
          background-color: var(--bg);
          color: white;
          width: 100%;
          height: 100%;
          overflow-y: scroll;
        }
        .content {
          display: block;
          max-width: 80;
          margin: 0 auto;
        }
        h1 {
          font-weight: bold;
          margin-bottom: 1;
          color: rgb(100, 200, 255);
        }
        p {
          margin-bottom: 2;
          color: rgb(170, 200, 255);
        }
        .row {
          display: flex;
          width: 100%;
          gap: 2;
          margin-bottom: 2;
        }
      </style>
    )html";

  BordersDemo() { Import<BorderBox>(); }
};

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

Live Demo - Borders & Scrollbars Interaction ​

To see how all the different border styles behave interactively and how they coordinate with scrollbars when content overflows, check out the following interactive demo.

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.
//
// Borders and scrollbars sharing an edge.
//
// A scrollbar is laid out inside the border box, so it has to coexist with the
// border. Switch border styles to see each combination.
#include <rtxui/rtxui.hpp>

using namespace rtxui;

class BorderScrollDemo : public Component<BorderScrollDemo> {
 public:
  std::string border_style = "solid";
  bool show_v_scroll = true;
  bool show_h_scroll = true;

  std::string overflow_y_val = "scroll";
  std::string overflow_x_val = "scroll";

  void SelectBorder(std::string name) {
    if (name == "double-horiz") {
      border_style = "double-horizontal";
    } else if (name == "double-vert") {
      border_style = "double-vertical";
    } else {
      border_style = name;
    }
  }

  void ToggleVScroll() { show_v_scroll = !show_v_scroll; }
  void ToggleHScroll() { show_h_scroll = !show_h_scroll; }

  std::string v_scroll_class() const { return show_v_scroll ? "active" : ""; }
  std::string h_scroll_class() const { return show_h_scroll ? "active" : ""; }

#define BORDER_CLASS_FN(name, actual)              \
  std::string name##_class() const {               \
    return border_style == actual ? "active" : ""; \
  }

  BORDER_CLASS_FN(solid, "solid")
  BORDER_CLASS_FN(round, "round")
  BORDER_CLASS_FN(double_style, "double")
  BORDER_CLASS_FN(ascii, "ascii")
  BORDER_CLASS_FN(dashed, "dashed")
  BORDER_CLASS_FN(heavy, "heavy")
  BORDER_CLASS_FN(dotted, "dotted")
  BORDER_CLASS_FN(double_horizontal, "double-horizontal")
  BORDER_CLASS_FN(double_vertical, "double-vertical")
  BORDER_CLASS_FN(shadow, "shadow")
  BORDER_CLASS_FN(shade_light, "shade-light")
  BORDER_CLASS_FN(shade_medium, "shade-medium")
  BORDER_CLASS_FN(shade_dark, "shade-dark")
  BORDER_CLASS_FN(squiggle, "squiggle")
  BORDER_CLASS_FN(tall, "tall")
  BORDER_CLASS_FN(panel, "panel")
  BORDER_CLASS_FN(thick, "thick")
  BORDER_CLASS_FN(outer, "outer")
  BORDER_CLASS_FN(inner, "inner")
  BORDER_CLASS_FN(wide, "wide")
  BORDER_CLASS_FN(vkey, "vkey")
  BORDER_CLASS_FN(hkey, "hkey")
  BORDER_CLASS_FN(blank, "blank")
  BORDER_CLASS_FN(none, "none")

  std::string_view view = R"html(
      <div class="main-container">
        <div class="sidebar">
          <h2>Controls</h2>
          
          <div class="toggles">
            <button onclick="ToggleVScroll" class="{v_scroll_class}">V-Scroll</button>
            <button onclick="ToggleHScroll" class="{h_scroll_class}">H-Scroll</button>
          </div>
          
          <div class="section-title">Select Border Style:</div>
          <div class="buttons-grid">
            <div class="row">
              <button onclick="SelectBorder(solid)" class="{solid_class}">solid</button>
              <button onclick="SelectBorder(round)" class="{round_class}">round</button>
              <button onclick="SelectBorder(double)" class="{double_style_class}">double</button>
            </div>
            <div class="row">
              <button onclick="SelectBorder(ascii)" class="{ascii_class}">ascii</button>
              <button onclick="SelectBorder(dashed)" class="{dashed_class}">dashed</button>
              <button onclick="SelectBorder(heavy)" class="{heavy_class}">heavy</button>
            </div>
            <div class="row">
              <button onclick="SelectBorder(dotted)" class="{dotted_class}">dotted</button>
              <button onclick="SelectBorder(double-horiz)" class="{double_horizontal_class}">double-h</button>
              <button onclick="SelectBorder(double-vert)" class="{double_vertical_class}">double-v</button>
            </div>
            <div class="row">
              <button onclick="SelectBorder(shadow)" class="{shadow_class}">shadow</button>
              <button onclick="SelectBorder(shade-light)" class="{shade_light_class}">light</button>
              <button onclick="SelectBorder(shade-medium)" class="{shade_medium_class}">medium</button>
            </div>
            <div class="row">
              <button onclick="SelectBorder(shade-dark)" class="{shade_dark_class}">dark</button>
              <button onclick="SelectBorder(squiggle)" class="{squiggle_class}">squigg</button>
              <button onclick="SelectBorder(tall)" class="{tall_class}">tall</button>
            </div>
            <div class="row">
              <button onclick="SelectBorder(panel)" class="{panel_class}">panel</button>
              <button onclick="SelectBorder(thick)" class="{thick_class}">thick</button>
              <button onclick="SelectBorder(outer)" class="{outer_class}">outer</button>
            </div>
            <div class="row">
              <button onclick="SelectBorder(inner)" class="{inner_class}">inner</button>
              <button onclick="SelectBorder(wide)" class="{wide_class}">wide</button>
              <button onclick="SelectBorder(vkey)" class="{vkey_class}">vkey</button>
            </div>
            <div class="row">
              <button onclick="SelectBorder(hkey)" class="{hkey_class}">hkey</button>
              <button onclick="SelectBorder(blank)" class="{blank_class}">blank</button>
              <button onclick="SelectBorder(none)" class="{none_class}">none</button>
            </div>
          </div>
          
          <div class="status-box">
            <div>Active Border: <span class="highlight">{border_style}</span></div>
          </div>
        </div>

        <div class="demo-area">
          <h2>Interactive Area</h2>
          <p class="instruction">Focus the test area (Tab) and scroll/drag. Observe how borders and scrollbars mix correctly without overlapping!</p>
          
          <div class="scroll-container-wrapper">
            <div id="scrollable-content" tabindex="0">
              <div class="scroll-inner">
                <div class="line header-line">RTXUI Border & Scrollbar Test Container</div>
                <div class="line">Line 01: Focus this box (Tab) and use Arrow Keys to scroll.</div>
                <div class="line">Line 02: Click on any border style on the left to see live changes.</div>
                <div class="line">Line 03: The scrollbars sit inside the border edges cleanly.</div>
                <div class="line">Line 04: This is a very long line of content to trigger the horizontal scrollbar.</div>
                <div class="line">Line 05: Notice how the corners (top/bottom/left/right) are preserved!</div>
                <div class="line">Line 06: Both scrollbars will meet at the corner but not overlap the border.</div>
                <div class="line">Line 07: You can scroll vertically and horizontally simultaneously.</div>
                <div class="line">Line 08: Try different border styles like 'round', 'double', 'shade-dark', or 'tall'!</div>
                <div class="line">Line 09: Hello world from the RTXUI library!</div>
                <div class="line">Line 10: End of the test content area. Enjoy testing!</div>
              </div>
            </div>
          </div>
        </div>
      </div>

      <style>
        self {
          --surface: rgb(22, 27, 34);
          --border: rgb(48, 54, 61);
          --muted: rgb(139, 148, 158);

          display: block;
          padding: 1;
          background-color: rgb(13, 17, 23);
          color: white;
          width: 100%;
          height: 100%;
        }
        .main-container {
          display: flex;
          width: 100%;
          height: 100%;
          gap: 2;
        }
        .sidebar {
          display: block;
          width: 48;
          flex-shrink: 0;
          border: solid;
          border-color: var(--border);
          padding: 1;
        }
        h2 {
          font-weight: bold;
          color: rgb(56, 189, 248);
          margin-bottom: 1;
        }
        .demo-area {
          display: block;
          flex-grow: 1;
          border: solid;
          border-color: var(--border);
          padding: 1;
        }
        .instruction {
          color: var(--muted);
          margin-bottom: 2;
        }
        .scroll-container-wrapper {
          display: flex;
          align-items: center;
          justify-content: center;
          width: 100%;
          height: 18;
        }
        #scrollable-content {
          display: block;
          width: 44;
          height: 13;
          border: {border_style};
          border-color: rgb(244, 63, 94);
          background-color: var(--surface);
          overflow-y: {overflow_y_val};
          overflow-x: {overflow_x_val};
        }
        #scrollable-content:focus {
          border-color: rgb(56, 189, 248);
        }
        .scroll-inner {
          display: block;
          width: 75;
        }
        .line {
          display: block;
          padding: 0 1;
          margin-bottom: 1;
          color: rgb(230, 237, 243);
        }
        .header-line {
          font-weight: bold;
          color: rgb(251, 146, 60);
        }
        .toggles {
          display: flex;
          gap: 1;
          margin-bottom: 2;
        }
        .toggles button {
          width: 20;
        }
        .section-title {
          display: block;
          font-weight: bold;
          margin-bottom: 1;
          color: var(--muted);
        }
        .buttons-grid {
          display: flex;
          flex-direction: column;
          gap: 1;
          margin-bottom: 1;
          width: 44;
        }
        .row {
          display: flex;
          width: 100%;
          gap: 1;
        }
        button {
          width: 13;
          background-color: var(--surface);
          color: rgb(203, 213, 225);
          border: solid;
          border-color: var(--border);
          padding: 0 1;
          cursor: pointer;
        }
        button:hover {
          background-color: rgb(88, 166, 255);
          border-color: rgb(121, 192, 255);
          color: white;
        }
        button.active {
          background-color: rgb(244, 63, 94);
          border-color: rgb(251, 113, 133);
          color: white;
          font-weight: bold;
        }
        .status-box {
          display: block;
          margin-top: 1;
          border: dashed;
          border-color: rgb(100, 116, 139);
          padding: 1;
          color: var(--muted);
        }
        .highlight {
          color: rgb(244, 63, 94);
          font-weight: bold;
        }
      </style>
    )html";

  BorderScrollDemo() {
    Bind(border_style);
    Bind(show_v_scroll);
    Bind(show_h_scroll);
    Bind(overflow_y_val);
    Bind(overflow_x_val);

    Bind(solid_class);
    Bind(round_class);
    Bind(double_style_class);
    Bind(ascii_class);
    Bind(dashed_class);
    Bind(heavy_class);
    Bind(dotted_class);
    Bind(double_horizontal_class);
    Bind(double_vertical_class);
    Bind(shadow_class);
    Bind(shade_light_class);
    Bind(shade_medium_class);
    Bind(shade_dark_class);
    Bind(squiggle_class);
    Bind(tall_class);
    Bind(panel_class);
    Bind(thick_class);
    Bind(outer_class);
    Bind(inner_class);
    Bind(wide_class);
    Bind(vkey_class);
    Bind(hkey_class);
    Bind(blank_class);
    Bind(none_class);

    Bind(ToggleVScroll);
    Bind(ToggleHScroll);
    Bind(v_scroll_class);
    Bind(h_scroll_class);

    Bind(SelectBorder);
  }

  bool Digest() override {
    bool changed = Component::Digest();
    std::string new_overflow_y = show_v_scroll ? "scroll" : "visible";
    std::string new_overflow_x = show_h_scroll ? "scroll" : "visible";
    if (new_overflow_y != overflow_y_val || new_overflow_x != overflow_x_val) {
      overflow_y_val = new_overflow_y;
      overflow_x_val = new_overflow_x;
      changed = true;
    }
    return changed;
  }
};

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