Skip to main content

Overview

Constructs are OpenTUI’s declarative component system. They look like React or Solid components but work differently:
  • Not render functions - They’re constructors that create renderables
  • No re-rendering - They build the tree once
  • Composable - Create complex UIs from simple pieces
  • Type-safe - Full TypeScript support with proper inference
Constructs provide a cleaner, more maintainable way to build UIs compared to imperative renderable creation.

Constructs vs Renderables

There are two ways to create UI elements in OpenTUI:

Imperative (Renderables)

Declarative (Constructs)

Cleaner, more readable, and easier to compose!

Creating Constructs

Built-in Constructs

OpenTUI provides construct functions for all built-in renderables:

Custom Constructs

Create reusable components with functional constructs:
Functional constructs are just functions that return VNodes. They’re called once during tree construction.

The VNode System

What is a VNode?

A VNode (Virtual Node) is a lightweight description of a renderable:
VNodes are created by construct functions and later instantiated into actual renderables.

Instantiation

Convert a VNode to a renderable using instantiate():
You rarely need to call instantiate() manually - add() does it automatically when you pass a VNode.

The h() Function

Under the hood, construct functions use h() to create VNodes:
You can use h() directly for more control:

Method Chaining

VNodes support method chaining for renderable methods:
This works because:
  1. VNodes created from renderables are wrapped in a Proxy
  2. Method calls are captured and stored
  3. When instantiated, stored calls are replayed on the renderable

Property Assignment

You can also set properties:

Delegation

Delegation lets you route API calls to nested children. This is crucial for composable components.

The Problem

The Solution: delegate()

How Delegation Works

  1. delegate() annotates the VNode with a mapping: { focus: "username-input" }
  2. When instantiated, the renderable is wrapped in a Proxy
  3. When focus() is called, the proxy:
    • Finds the descendant with ID "username-input"
    • Calls focus() on that descendant instead
    • Caches the descendant for performance

Multiple Delegations

The delegated child must exist in the tree, or the call will fail silently. Make sure IDs match!

Composition Patterns

Container Components

Conditional Rendering

List Rendering

Slots Pattern

Complete Example

Here’s a full login form using constructs:

Best Practices

Use constructs for composition - They make complex UIs much easier to build and maintain.
Use delegation for nested APIs - Essential when you need to access child renderable methods.
Keep props interfaces - TypeScript will help catch errors at build time.
Don’t treat constructs as render functions - They’re called once to build the tree, not on every update.
Constructs are synchronous - Unlike React, there’s no reconciliation or diffing. The tree is built once.

API Reference

Functions

Types