Skip to content

Positioning & Layers ​

By default, RTXUI components arrange elements within the standard flexbox layout flow. For overlay blocks, modals, status bars, or layered widgets, you can use absolute coordinates and z-index ordering.


1. Positioning Modes (position) ​

RTXUI supports element positioning contexts:

  1. static (Default): The element renders in order within the normal flex layout flow.
  2. relative: The element remains in the normal flow but establishes a reference origin point for any descendant child elements set to position: absolute.
  3. absolute: The element is removed from the normal flow. It is positioned relative to its closest parent ancestor that has position: relative (or the root screen boundary if no ancestor is relative).
  4. fixed: The element is removed from the normal flow and positioned relative to the outermost terminal screen viewport.
  5. sticky: The element is positioned based on the user's scroll position. It behaves like relative until the viewport scrolls past a given offset (such as top: 0), at which point it "sticks" to that position, similar to fixed.

2. Offset Coordinates ​

For elements configured with position: absolute, position: fixed, or position: sticky, use these properties to set the spacing offsets from the container margins (measured in character cells):

  • left: Spacing from the container's left edge.
  • right: Spacing from the container's right edge.
  • top: Spacing from the container's top edge.
  • bottom: Spacing from the container's bottom edge.

A sticky element honours all four offsets. top/left pin it as it scrolls up or left out of view; bottom/right pin it while it is still below or to the right of the viewport, which is how a footer stays visible until the content it belongs to scrolls past. When both edges of one axis are set, the leading edge (top, left) wins.

html
<div class="modal-box">Centered Overlay</div>

<style>
  .modal-box {
    position: absolute;
    top: 5;
    left: 10;
    width: 40;
    height: 10;
    border: solid;
  }
</style>

3. Layer Ordering (z-index) ​

To manage depth when multiple absolute or fixed elements overlap, assign the drawing layers using z-index:

  • Elements with a higher z-index are drawn on top of elements with lower values.
  • Default z-index value is 0 (where overlap order falls back to template declaration order).
html
<div class="background-pane">Behind (z-index = 1)</div>
<div class="foreground-pane">In Front (z-index = 10)</div>

<style>
  .background-pane {
    position: absolute;
    z-index: 1;
  }
  .foreground-pane {
    position: absolute;
    z-index: 10;
  }
</style>

Interactive Demo ​

Below is the interactive tab view for layout layering:

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.
//
// position: relative, absolute and fixed, plus z-index stacking.
#include <algorithm>
#include <rtxui/rtxui.hpp>
#include <string>
#include <string_view>

using namespace rtxui;

class PositioningApp : public Component<PositioningApp> {
 public:
  int x = 8;
  int y = 4;

  void Move(std::string direction) {
    if (direction == "left") {
      x = std::max(0, x - 2);
    } else if (direction == "right") {
      x = std::min(40, x + 2);
    } else if (direction == "up") {
      y = std::max(0, y - 1);
    } else if (direction == "down") {
      y = std::min(5, y + 1);
    }
  }

  std::string_view view = R"html(
      <div class="card">
        <h1>Positioning</h1>
        <p>The stage is position: relative; the cards in it are absolute, stacked by z-index. The status bar is fixed to the screen.</p>

        <div class="buttons">
          <button onclick="Move(left)">◀</button>
          <button onclick="Move(right)">▶</button>
          <button onclick="Move(up)">▲</button>
          <button onclick="Move(down)">▼</button>
        </div>

        <div class="stage">
          <div class="back">z-index: 5</div>
          <div class="front">z-index: 10</div>
        </div>
      </div>

      <div class="status">position: fixed; bottom: 0   The front card is at left: {x}, top: {y}.</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%;
          height: 100%;
          background-color: rgb(13, 17, 23);
          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);
        }
        .buttons {
          display: flex;
          gap: 1;
        }
        button {
          border: tall;
          border-color: var(--border);
          background-color: rgb(13, 17, 23);
          padding: 0 1;
        }
        button:hover, button:focus {
          border-color: var(--accent);
          color: var(--accent);
        }
        .stage {
          position: relative;
          height: 12;
          margin-top: 1;
          border: round;
          border-color: var(--border);
        }
        .back, .front {
          position: absolute;
          width: 18;
          height: 5;
          border: tall;
          padding: 1;
        }
        .back {
          top: 2;
          left: 24;
          z-index: 5;
          text-align: right;
          border-color: rgb(188, 140, 255);
          background-color: rgb(52, 36, 79);
        }
        .front {
          top: {y};
          left: {x};
          z-index: 10;
          border-color: rgb(248, 81, 73);
          background-color: rgb(73, 32, 36);
        }
        .status {
          position: fixed;
          bottom: 0;
          left: 0;
          width: 100%;
          padding: 0 1;
          background-color: rgb(22, 45, 77);
          color: var(--accent);
        }
      </style>
    )html";

  PositioningApp() {
    Bind(x);
    Bind(y);
    Bind(Move);
  }
};

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

Sticky Positioning Demo ​

The interactive demo below showcases position: sticky inside a scrollable container. Notice how category headers stay pinned at the top until they are pushed out of the way by the next category.

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.
//
// position: sticky.
//
// Section headers pin to the top of the scroll container while their section is
// on screen.
#include <rtxui/rtxui.hpp>
#include <string>
#include <string_view>
#include <vector>

using namespace rtxui;

struct Month {
  std::string name;
  int days = 0;
  bool operator==(const Month& other) const = default;
};

// {month.name}, and {month.days}: the day numbers, a week per line.
void Fields(const Month& month, FieldSink& out) {
  out("name", month.name);
  std::string days;
  for (int day = 1; day <= month.days; ++day) {
    days += (day < 10 ? "  " : " ") + std::to_string(day);
    if (day % 7 == 0) {
      days += "\n";
    }
  }
  out("days", days);
}

class StickyDemo : public Component<StickyDemo> {
 public:
  std::vector<Month> months = {
      {"January", 31},   {"February", 28}, {"March", 31},    {"April", 30},
      {"May", 31},       {"June", 30},     {"July", 31},     {"August", 31},
      {"September", 30}, {"October", 31},  {"November", 30}, {"December", 31},
  };

  std::string_view view = R"html(
      <div class="card">
        <h1>Sticky</h1>
        <p>Scroll: each month's name stays at the top until the next one pushes it away.</p>

        <div class="calendar">
          <for each="{months}" as="month">
            <div class="month">
              <div class="name">{month.name}</div>
              <div class="days">{month.days}</div>
            </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: 40;
        }
        h1 {
          color: rgb(88, 166, 255);
        }
        p {
          color: rgb(139, 148, 158);
        }
        .calendar {
          height: 14;
          overflow-y: scroll;
          border: round;
          border-color: rgb(48, 54, 61);
        }
        .name {
          position: sticky;
          top: 0;
          background-color: rgb(22, 45, 77);
          color: rgb(88, 166, 255);
          font-weight: bold;
          padding: 0 1;
        }
        .days {
          white-space: pre;
          padding: 0 1 1 1;
        }
      </style>
    )html";

  StickyDemo() { Bind(months); }
};

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