agnostic/platform/opentui/element

TUI element constructors for OpenTUI. These map to @opentui/core renderables.

Types

A named raw node content bundle: (name, factory). The name is used for diffing comparisons while the factory creates the node.

pub type RawNodeContent =
  #(String, fn(opentui.Renderer) -> opentui.Node)

A factory function that receives the OpenTUI renderer and returns a raw node. Cleanup is handled by overriding destroySelf() on your renderable class — OpenTUI calls this automatically when the node is removed from the tree.

In JavaScript FFI, this is a function of type: (renderer) => RenderableNode

pub type RawNodeFactory =
  fn(opentui.Renderer) -> opentui.Node

Values

pub fn ascii_font(
  attributes: List(vattr.Attribute(msg)),
) -> vnode.Element(msg)

An ASCII art font element. Maps to ASCIIFontRenderable.

pub fn box(
  attributes: List(vattr.Attribute(msg)),
  children: List(vnode.Element(msg)),
) -> vnode.Element(msg)

A box container — the primary layout primitive in TUI. Maps to BoxRenderable.

pub fn code(
  attributes: List(vattr.Attribute(msg)),
  children: List(vnode.Element(msg)),
) -> vnode.Element(msg)

A code block with syntax highlighting. Maps to CodeRenderable.

pub fn diff(
  attributes: List(vattr.Attribute(msg)),
  children: List(vnode.Element(msg)),
) -> vnode.Element(msg)

A diff viewer. Maps to DiffRenderable.

pub fn frame_buffer(
  attributes: List(vattr.Attribute(msg)),
) -> vnode.Element(msg)

A frame buffer element for direct pixel-level rendering. Maps to FrameBufferRenderable.

pub fn frame_buffer_with_handler(
  handler: json.Json,
  attrs: List(vattr.Attribute(msg)),
) -> vnode.Element(msg)

Create a FrameBuffer element with a custom JS handler. The handler receives the FrameBufferRenderable node when mounted, allowing direct manipulation via node.setCell().

Example usage:

// spinner.ffi.mjs
export function make_spinner_handler(color, interval_ms) {
  return (node) => {
    let frameIndex = 0;
    const fg = parseColor(color);
    const bg = { r: 0, g: 0, b: 0, a: 0 };
    setInterval(() => {
      node.setCell(0, 0, ["|", "/", "-", "\\"][frameIndex], fg, bg);
      frameIndex = (frameIndex + 1) % 4;
    }, interval_ms);
  };
}

// spinner.gleam
@external(javascript, "./spinner.ffi.mjs", "make_spinner_handler")
fn make_handler(color: String, interval_ms: Int) -> Json

pub fn spinner(handler: Json, attrs: List(Attribute(msg))) -> Element(msg) {
  element.frame_buffer_with_handler(handler, attrs)
}
pub fn input(
  attributes: List(vattr.Attribute(msg)),
) -> vnode.Element(msg)

A text input field. Maps to InputRenderable.

pub fn line_number(
  attributes: List(vattr.Attribute(msg)),
  children: List(vnode.Element(msg)),
) -> vnode.Element(msg)

A line number gutter element. Maps to LineNumberRenderable. Wrap a code or textarea element as a child.

pub fn markdown(
  attributes: List(vattr.Attribute(msg)),
  children: List(vnode.Element(msg)),
) -> vnode.Element(msg)

A markdown renderer. Maps to MarkdownRenderable.

pub fn raw_node(
  name name: String,
  factory factory: fn(opentui.Renderer) -> opentui.Node,
) -> vnode.Element(msg)

Create a raw element using a named factory function. The factory receives the OpenTUI renderer at mount time and should return the created renderable node.

The name parameter is used for diffing: when the name changes, the old node is destroyed and a new one is created from the factory. When the name stays the same, the existing node persists across renders (factories are closures that get recreated each render, so we can’t compare them directly).

This allows creating custom OpenTUI renderables (spinners, animated widgets, etc.) without needing direct access to the renderer instance.

For cleanup (clearing intervals, subscriptions, etc.), override destroySelf() on your renderable class — OpenTUI calls this automatically when the node is removed from the tree.

Example usage:

// spinner.ffi.mjs
import { TextRenderable } from "@opentui/core";

class Spinner extends TextRenderable {
  #intervalId = null;
  #frameIndex = 0;

  constructor(renderer, color, interval_ms) {
    super(renderer, {});
    this.content = FRAMES[0];
    this.fg = color;
    this.#intervalId = setInterval(() => {
      this.#frameIndex = (this.#frameIndex + 1) % FRAMES.length;
      this.content = FRAMES[this.#frameIndex];
    }, interval_ms);
  }

  destroySelf() {
    if (this.#intervalId) clearInterval(this.#intervalId);
    super.destroySelf();
  }
}

export function spinner_factory(color, interval_ms) {
  return (renderer) => new Spinner(renderer, color, interval_ms);
}

// spinner.gleam
@external(javascript, "./spinner.ffi.mjs", "spinner_factory")
fn spinner_factory(color: String, interval_ms: Int) -> RawNodeFactory

pub fn spinner(color: String) -> Element(msg) {
  raw_node("text-spinner", "box", [], spinner_factory(color, 150))
}
pub fn scrollbox(
  attributes: List(vattr.Attribute(msg)),
  children: List(vnode.Element(msg)),
) -> vnode.Element(msg)

A scrollable container. Maps to ScrollBoxRenderable.

pub fn select(
  attributes: List(vattr.Attribute(msg)),
  children: List(vnode.Element(msg)),
) -> vnode.Element(msg)

A select dropdown. Maps to SelectRenderable.

pub fn slider(
  attributes: List(vattr.Attribute(msg)),
) -> vnode.Element(msg)

A slider element. Maps to SliderRenderable.

pub fn tab_select(
  attributes: List(vattr.Attribute(msg)),
) -> vnode.Element(msg)

A tab-based select element. Maps to TabSelectRenderable.

pub fn text(
  attributes: List(vattr.Attribute(msg)),
) -> vnode.Element(msg)

A styled text element. Maps to TextRenderable. Pass content(“…”) as an attribute alongside styling attributes.

pub fn textarea(
  attributes: List(vattr.Attribute(msg)),
) -> vnode.Element(msg)

A multi-line text area. Maps to TextareaRenderable.

Search Document