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 three 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 */
  3. Viewport Units: vh and vw are hundredths of the terminal's height and width, whatever the parent is, and follow it when it is resized.
    css
    .panel { height: 50vh; } /* Half the terminal's rows */

There are no px, em or rem: a terminal has only cells. A length in another unit is reported as a diagnostic and ignored.

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,
// then the ways to combine them: a color in the shorthand, a style per side.
//
// Each tile names the style it draws, so this doubles as a lookup table.
#include <rtxui/rtxui.hpp>
#include <string>
#include <string_view>
#include <vector>

using namespace rtxui;

class BordersDemo : public Component<BordersDemo> {
 public:
  std::vector<std::string> styles = {
      "solid",
      "round",
      "double",
      "heavy",
      "dashed",
      "dotted",
      "dotted-fine",
      "ascii",
      "heavy-dashed",
      "double-horizontal",
      "double-vertical",
      "tall",
      "wide",
      "thick",
      "inner",
      "outer",
      "panel",
      "hkey",
      "vkey",
      "outset",
      "inset",
      "block",
      "shadow",
      "blank",
      "shade-light",
      "shade-medium",
      "shade-dark",
      "none",
  };
  std::vector<std::string> combinations = {
      "border: round red",
      "border: double green",
      "border: solid; border-top: heavy",
      "border: solid; border-bottom: double yellow",
      "border-left: heavy magenta",
      "border-top: solid; border-bottom: solid",
      "border: dashed; border-left-style: heavy; border-right-style: heavy",
      "border: inset cyan",
  };

  std::string_view view = R"html(
      <div class="card">
        <h1>Borders</h1>
        <p>Every value of the border property, each drawn on itself.</p>

        <div class="grid">
          <for each="{styles}" as="s">
            <div class="tile" style="border: {s};">{s}</div>
          </for>
        </div>

        <h2>Combined</h2>
        <div class="grid combinations">
          <for each="{combinations}" as="c">
            <div class="tile" style="{c}">{c}</div>
          </for>
        </div>
      </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: 92;
          max-height: 100%;
          overflow-y: scroll;
        }
        h1, h2 {
          color: rgb(88, 166, 255);
        }
        h2 {
          margin-top: 2;
        }
        p {
          color: rgb(139, 148, 158);
        }
        .grid {
          display: grid;
          grid-template-columns: repeat(4, 1fr);
          gap: 2 2;
        }
        .combinations {
          grid-template-columns: repeat(2, 1fr);
        }
        .tile {
          border-color: rgb(88, 166, 255);
          text-align: center;
          padding: 1;
        }
        .tile:hover {
          border-color: yellow;
          background-color:black;
        }
      </style>
    )html";

  BordersDemo() {
    Bind(styles);
    Bind(combinations);
  }
};

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>
#include <string>
#include <string_view>
#include <vector>

using namespace rtxui;

class BorderScrollDemo : public Component<BorderScrollDemo> {
 public:
  std::vector<std::string> styles = {
      "solid",
      "round",
      "double",
      "heavy",
      "dashed",
      "dotted",
      "dotted-fine",
      "ascii",
      "heavy-dashed",
      "double-horizontal",
      "double-vertical",
      "tall",
      "wide",
      "thick",
      "inner",
      "outer",
      "panel",
      "hkey",
      "vkey",
      "outset",
      "inset",
      "block",
      "shadow",
      "blank",
      "shade-light",
      "shade-medium",
      "shade-dark",
      "none",
  };
  std::vector<std::string> lines = {
      "Each line is wider than the box, so it also scrolls sideways.",
      "The scrollbars meet in the corner, inside the border.",
      "Try round, double, tall, shadow, or a shade.",
      "Untick a checkbox to drop that scrollbar.",
      "Line 5",
      "Line 6",
      "Line 7",
      "Line 8",
      "Line 9",
      "Line 10",
      "Line 11",
      "The last line.",
  };
  std::string border_style = "round";
  bool vertical = true;
  bool horizontal = true;

  std::string overflow_y() const { return vertical ? "scroll" : "hidden"; }
  std::string overflow_x() const { return horizontal ? "scroll" : "hidden"; }

  std::string_view view = R"html(
      <div class="card">
        <h1>Borders and scrollbars</h1>
        <p>A scrollbar sits inside the border. Pick a style and scroll the box: Tab to it, then the arrows, or the wheel.</p>

        <div class="controls">
          <select value="{border_style}">
            <for each="{styles}" as="style">
              <option value="{style}">{style}</option>
            </for>
          </select>
          <checkbox checked="{vertical}">Vertical</checkbox>
          <checkbox checked="{horizontal}">Horizontal</checkbox>
        </div>

        <div class="box" tabindex="0">
          <for each="{lines}" as="line">
            <div>{line}</div>
          </for>
        </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: 60;
        }
        h1 {
          color: var(--accent);
        }
        p {
          color: var(--muted);
        }
        .controls {
          display: flex;
          gap: 3;
          margin-bottom: 1;
        }
        select {
          width: 22;
        }
        select::part(dropdown-list) {
          max-height: 10;
          overflow-y: scroll;
        }
        select:focus, checkbox:focus {
          color: var(--accent);
        }
        .box {
          height: 10;
          border: {border_style};
          border-color: var(--border);
          overflow-y: {overflow_y};
          overflow-x: {overflow_x};
        }
        .box:focus {
          border-color: var(--accent);
        }
        .box div {
          white-space: nowrap;
          padding: 0 1;
        }
      </style>
    )html";

  BorderScrollDemo() {
    Bind(styles);
    Bind(lines);
    Bind(border_style);
    Bind(vertical);
    Bind(horizontal);
    Bind(overflow_y);
    Bind(overflow_x);
  }
};

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