Skip to content

Event Handlers & Input Bindings ​

RTXUI maps terminal interactions (such as mouse clicks, keyboard presses, or input changes) to registered C++ callback functions.


1. Registering Callbacks in C++ ​

To bind a member function to template events, register it in your component constructor using the Bind() method. To register lambda expressions or standalone functions, import them using the Import() method:

cpp
struct ClickApp : public rtxui::Component<ClickApp> {
  int clicks = 0;

  void Increment() { clicks++; }

  ClickApp() {
    Bind(clicks);
    
    // Bind member function callback
    Bind(Increment);
    
    // Import a lambda with parameters
    Import("Reset", [this]() { clicks = 0; });
  }

  std::string_view Setup();
};

2. Event Binding Syntaxes ​

Once registered, you can wire callbacks to layout components in two styles:

HTML-style (onclick, onchange) ​

The attribute value is the name of a bound callback:

html
<button onclick="Increment">Increment</button>

@ shorthand (@click, @change) ​

The @ prefix is equivalent:

html
<button @click="Increment">Increment</button>

3. Event Modifiers ​

To intercept specific mouse behaviors (like context menu clicks), you can append a modifier suffix:

html
<!-- Triggered only on mouse right-click events -->
<button @click.right="ShowContextMenu">Options</button>
  • @click or @click.left: Main trigger callback.
  • @click.right: Secondary/context menu trigger.

4. Parameterized Callbacks ​

Callbacks can accept arguments from templates. The arguments are received by the C++ lambda as std::string parameters. This is extremely useful for index tracking in loops:

cpp
// Registered in constructor
Import("RemoveItem", [this](std::string index_str) {
  size_t index = std::stoull(index_str);
  items.erase(items.begin() + index);
});

To invoke a parameterized callback in a template, pass a value or an interpolated loop variable in parentheses. Inside a <for> loop, {$index} interpolates to the current item's index:

html
<ul>
  <for each="{todo_list}" as="todo">
    <li>
      <span>{todo}</span>
      <button @click="RemoveItem({$index})">Delete</button>
    </li>
  </for>
</ul>

5. Two-Way Data Binding ​

For interactive input widgets, RTXUI supports automatic two-way data bindings. When a user types text or checks a box, the bound C++ member variable is updated instantly, and programmatically changing the C++ value repaints the input field:

  • <input> / <textarea>: Bind the value attribute to a reactive string.
  • <checkbox>: Bind the checked attribute to a reactive boolean.
  • <select>: Bind the value attribute to a reactive string matching the selected option.
html
<input value="{search_query}" />
<checkbox checked="{is_enabled}">Toggle Features</checkbox>

Interactive Demo ​

Below is the interactive tab view for two-way input field bindings:

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.
//
// The <input> component with two-way binding.
//
// Editing the field writes straight back into the bound std::string, and the
// interpolated value below updates on the same frame.
#include <rtxui/rtxui.hpp>

using namespace rtxui;

class InputDemo : public Component<InputDemo> {
 public:
  std::string text = "Hello, RTXUI!";

  std::string_view view = R"html(
      <div class="container">
        <p class="title">Interactive Input Element</p>
        <p class="desc">Two-way binding to a std::string. Ctrl moves and deletes by word.</p>
        
        <input class="styled-input" value="{text}" />
        
        <div class="output-box">
          <span class="label">Live Value:</span>
          <span class="value">"{text}"</span>
        </div>
      </div>

      <style>
        self {
          --bg: rgb(13, 17, 23);
          --border: rgb(48, 54, 61);
          --text: rgb(230, 237, 243);
          --muted: rgb(139, 148, 158);
          --accent: rgb(88, 166, 255);

          display: flex;
          align-items: center;
          justify-content: center;
          width: 100%;
          height: 100%;
          background-color: var(--bg);
          color: var(--text);
        }
        .container {
          display: block;
          border: tall;
          border-color: var(--border);
          background-color: rgb(22, 27, 34);
          padding: 1 3;
          width: 62;
        }
        .title {
          color: var(--accent);
          font-weight: bold;
        }
        .desc {
          color: var(--muted);
          margin-bottom: 1;
        }
        .output-box {
          display: flex;
          justify-content: space-between;
          width: 100%;
          border-top: solid;
          border-color: var(--border);
          margin-top: 1;
          padding-top: 1;
        }
        .label {
          color: var(--muted);
        }
        .value {
          color: var(--accent);
          font-weight: bold;
        }
        .slider-wrapper, .progress-wrapper {
          display: flex;
          gap: 2;
          align-items: center;
        }
        .stats {
          color: var(--muted);
          margin-top: 1;
        }
        input, textarea, select, .styled-input, .editor {
          border: tall;
          border-color: var(--border);
          background-color: var(--bg);
          color: var(--text);
          padding: 0 1;
          width: 100%;
        }
        input:focus, textarea:focus, select:focus,
        .styled-input:focus, .editor:focus {
          border-color: var(--accent);
        }
      </style>
    )html";

  InputDemo() { Bind(text); }
};

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