Skip to content

Keyboard Focus & Navigation ​

RTXUI applications are fully keyboard-operable out of the box. This page describes what users can do with the keyboard, and how to control which elements participate.

Built-in keys ​

KeyBehavior
Tab / Shift-TabMove focus through focusable elements in order.
Arrow keys / h j k lSpatial navigation: focus the nearest focusable element in that direction; inside a scrollable, scroll.
Enter / SpaceActivate the focused element (equivalent to a click).
PageUp / PageDownScroll the focused scrollable by a viewport.
Home / EndJump the focused scrollable to its start or end.
EscapeClose the open <dialog>; otherwise exit the application.

Component OnEvent overrides see events before these built-in behaviors, so custom key handling always wins.

Focus Control ​

To make any layout container (like a custom card or wrapper <div>) interactive and focusable, use these properties:

tabindex ​

Determines where the element fits in the sequential keyboard Tab navigation loop:

  • tabindex="0": The element is sequentially focusable (standard behavior).
  • tabindex="-1": The element is focusable programmatically or via mouse clicks, but skipped when hitting Tab/Shift-Tab.
  • tabindex="1" (or higher): Establishes custom sequential traversal order (lower positive values focus first).
html
<!-- Tab navigates to this div first, then the next -->
<div tabindex="1">First focusable zone</div>
<div tabindex="2">Second focusable zone</div>

focusable Shorthand ​

Set focusable="true" as a clean shorthand for setting tabindex="0":

html
<div focusable="true" class="button">
  Interactive Box
</div>

Focusable items automatically capture keyboard events (ArrowLeft, Enter, etc.) and focus CSS pseudoclasses like :focus.

Live 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.
//
// Controlling focus order with tabindex.
#include <rtxui/rtxui.hpp>

using namespace rtxui;

class TabIndexDemo : public Component<TabIndexDemo> {
 public:
  std::string_view view = R"html(
    <div class="container">
      <h2>Keyboard TabIndex Navigation</h2>
      <p class="description">
        Use <b>Tab</b> and <b>Shift-Tab</b> to move the focus.
        Notice that positive tabindexes are traversed first (ascending order),
        followed by tabindex 0, and tabindex -1 is skipped from tabbing.
      </p>

      <div class="list">
        <div class="item positive-two" tabindex="2">
          <span class="badge badge-pos">tabindex="2"</span>
          <span class="label">First item in HTML, but second in Tab order</span>
        </div>

        <div class="item zero-one" tabindex="0">
          <span class="badge badge-zero">tabindex="0"</span>
          <span class="label">Zero tabindex (navigated third)</span>
        </div>

        <div class="item positive-one" tabindex="1">
          <span class="badge badge-pos">tabindex="1"</span>
          <span class="label">First element in Tab order (tabindex=1)</span>
        </div>

        <div class="item minus-one" tabindex="-1">
          <span class="badge badge-neg">tabindex="-1"</span>
          <span class="label">Skipped during tab navigation (click-only)</span>
        </div>

        <div class="item implicit-zero" focusable="true">
          <span class="badge badge-implicit">focusable="true"</span>
          <span class="label">Implicitly tabindex 0 (navigated fourth)</span>
        </div>
      </div>
    </div>

    <style>
      self {
        --text: rgb(230, 237, 243);
        --accent: rgb(88, 166, 255);

        display: block;
        padding: 1 2;
        background-color: rgb(13, 17, 23); /* Deep dark slate background */
        color: var(--text);
      }
      h2 {
        color: var(--accent); /* Bright blue */
        margin-bottom: 0;
      }
      .description {
        color: rgb(139, 148, 158); /* Muted gray text */
        margin-bottom: 1;
      }
      .list {
        display: block;
        height: 8;
        overflow-y: scroll;
        scroll-behavior: smooth;
      }
      .item {
        display: block;
        border: tall;
        border-color: rgb(22, 27, 34); /* Dark slate border */
        background-color: rgb(30, 41, 59, 0.4);
        padding: 0 1;
        margin-bottom: 0;
        width: 60;
      }
      .item:hover {
        background-color: rgb(30, 41, 59, 0.8);
        border-color: rgb(48, 54, 61);
      }
      .item:focus {
        border-color: var(--accent); /* Focus highlight */
        background-color: rgb(30, 58, 138, 0.5); /* Deep slate blue on focus */
      }
      .badge {
        display: inline;
        padding: 0 1;
        text-align: center;
        width: 18;
      }
      .badge-pos {
        background-color: rgb(63, 185, 80); /* Emerald Green */
        color: rgb(255, 255, 255);
      }
      .badge-zero {
        background-color: rgb(245, 158, 11); /* Amber Orange */
        color: rgb(255, 255, 255);
      }
      .badge-neg {
        background-color: rgb(248, 81, 73); /* Rose Red */
        color: rgb(255, 255, 255);
      }
      .badge-implicit {
        background-color: rgb(99, 102, 241); /* Indigo */
        color: rgb(255, 255, 255);
      }
      .label {
        display: inline;
        margin-left: 2;
        color: var(--text);
      }
      .item:focus .label {
        color: rgb(255, 255, 255);
        font-weight: bold;
      }
    </style>
  )html";
};

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

Spatial Navigation ​

In addition to sequential tab navigation (using Tab/Shift-Tab), RTXUI supports Spatial Navigation. By pressing the Arrow keys (or hjkl keys), RTXUI performs a geometric search in 2D screen space to find the closest focusable element in that direction and moves focus there. This provides a natural, game-like, or TV-like focus navigation experience for complex layouts.

Live Demo - Spatial Navigation ​

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.
//
// Arrow-key spatial navigation.
//
// Focus moves to the nearest focusable element in the direction pressed,
// computed from the laid-out geometry.
//
// Try it: move around the grid with the arrow keys.
#include <string>
#include <vector>

#include "rtxui/rtxui.hpp"

using namespace rtxui;

class SpatialNavDemo : public Component<SpatialNavDemo> {
 public:
  std::string last_action = "None";
  int click_count = 0;

  void OnButtonClick(std::string label) {
    last_action = "Clicked: " + label;
    click_count++;
  }

  SpatialNavDemo() {
    Import("OnButtonClick",
           [this](std::string label) { OnButtonClick(label); });
    Bind(last_action);
    Bind(click_count);
  }

  std::string_view view = R"html(
    <style>
      self {

        display: flex;
        flex-direction: column;
        padding: 1;
        gap: 1;
        background-color: rgb(13, 17, 23);
        color: #f1f5f9;
      }
      h1 {
        color: #38bdf8;
        font-weight: bold;
        margin-bottom: 0;
      }
      .info {
        color: #94a3b8;
        margin-bottom: 1;
      }
      .section-title {
        color: #7dd3fc;
        margin-top: 1;
        text-decoration: underline;
      }
      .row {
        display: flex;
        gap: 2;
        align-items: center;
      }
      .card {
        border: tall;
        border-color: #1e293b;
        padding: 1;
        width: 15;
        height: 5;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        background-color: #1e293b;
        transition: all 0.1s linear;
      }
      .card:hover {
        background-color: #334155;
      }
      .card:focus {
        background-color: #0c4a6e;
        border-color: #38bdf8;
        color: #fff;
      }
      .card:active {
        background-color: #075985;
      }
      
      button {
        border-color: #334155;
      }
      button:focus {
        border-color: #38bdf8;
        background-color: #0c4a6e;
      }

      .nested-container {
        display: flex;
        flex-direction: column;
        gap: 1;
        border: dashed;
        border-color: #334155;
        padding: 1;
        background-color: rgba(30, 41, 59, 0.5);
      }

      .status {
        border: solid;
        border-color: #0ea5e9;
        padding: 1;
        background-color: #0c4a6e;
        color: #e0f2fe;
        margin-top: 1;
      }
      .status span {
        font-weight: bold;
        color: #fff;
      }

      .footer {
        color: #64748b;
        margin-top: 1;
        border-top: light;
        padding-top: 1;
      }
    </style>

    <div>
      <h1>RTXUI Spatial Navigation</h1>
      <p class="info">Arrow keys / hjkl to navigate • Space / Enter to activate</p>
      
      <div class="section-title">Interactive Cards</div>
      <div class="row">
        <div class="card" focusable="true" onclick="OnButtonClick('Card 1')">Card 1</div>
        <div class="card" focusable="true" onclick="OnButtonClick('Card 2')">Card 2</div>
        <div class="card" focusable="true" onclick="OnButtonClick('Card 3')">Card 3</div>
      </div>

      <div class="section-title">Standard Components</div>
      <div class="row">
        <button onclick="OnButtonClick('Button A')">Button A</button>
        <button onclick="OnButtonClick('Button B')">Button B</button>
        <button onclick="OnButtonClick('Button C')">Button C</button>
      </div>

      <div class="row">
        <checkbox>Feature</checkbox>
        <div style="display: flex; flex-direction: column; align-items: center;">
          <span style="color: #64748b;">Horizontal</span>
          <slider value="50" style="width: 20;" />
        </div>
        <div style="display: flex; gap: 1; align-items: center; border: solid; border-color: #1e293b; padding: 1;">
           <span style="color: #64748b;">Vertical</span>
           <slider direction="vertical" value="30" width="5" />
        </div>
        <input value="Search..." style="width: 15;" />
      </div>

      <div class="section-title">Complex Layout</div>
      <div class="row">
        <div class="nested-container">
          <p style="color: #94a3b8;">Nested</p>
          <button onclick="OnButtonClick('Nested 1')">Action 1</button>
          <button onclick="OnButtonClick('Nested 2')">Action 2</button>
        </div>
        <div style="flex-grow: 1; display: flex; align-items: center; justify-content: center; border: tall; border-color: #334155; height: 7; background-color: #111827;">
           <div class="card" focusable="true" onclick="OnButtonClick('Core Card')">Core Card</div>
        </div>
      </div>

      <div class="status">
        <div>Event: <span>{last_action}</span></div>
        <div>Count: <span>{click_count}</span></div>
      </div>

      <div class="footer">
        Geometric focus search ensures intuitive movement in 2D space.
      </div>
    </div>
  )html";
};

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