Skip to main content

Overview

The render() function mounts a JSX VNode tree and sets it as the application root. It works with the automatic JSX transform to create declarative UIs with signal-driven reactivity.

Functions

render()

Mount a VNode tree and set it as the application root.
element
VNode
required
The root JSX element to mount. Must resolve to a widget-bearing node (not a Fragment).
app
Kraken
required
The Kraken application instance (from Kraken.init()).
Returns: Instance — The mounted instance tree with cleanup bookkeeping.
import { Kraken, render } from "kraken-tui";

const app = Kraken.init();

const root = render(
  <Box width="100%" height="100%" padding={1}>
    <Text content="Hello, Kraken!" />
  </Box>,
  app
);

// Your event loop here...

app.shutdown();
render() requires a root element that resolves to a native widget. You cannot use a bare Fragment or a component that returns only a Fragment as the root.

mount()

Mount a VNode recursively, creating native widgets and binding props. Used internally by render() and the reconciler.
vnode
VNode
required
The virtual node to mount.
parentInstance
Instance | null
required
The parent instance, or null for root elements.
Returns: Instance — The mounted instance with widget handle and cleanup bookkeeping. Behavior:
  • For Fragments: mounts children directly into parent (no native node created)
  • For component functions: calls the function and mounts the returned tree
  • For intrinsic elements (Box, Text, etc.): creates native widget via FFI
  • Signal props are bound using effect() from @preact/signals-core
  • Children are mounted recursively in declaration order

unmount()

Unmount an instance: dispose signal effects, unregister event handlers, then destroy the native subtree.
instance
Instance
required
The instance to unmount.
Returns: void Lifecycle:
  1. Recursively dispose all effect() cleanups (prevents FFI calls after destruction)
  2. Clear event handler registry entries
  3. Destroy native nodes via destroySubtree() (one FFI call per widget-bearing node)
  4. For Fragments (no widget), recurse into children individually
import { render, unmount } from "kraken-tui";

const root = render(<Box><Text content="Hello" /></Box>, app);

// Later...
unmount(root);
In typical applications, you don’t need to call unmount() manually — just call app.shutdown() which cleans up all native resources.

JSX Factory Setup

Kraken uses a custom JSX runtime compatible with React’s automatic JSX transform (ADR-T20).

tsconfig.json

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "kraken-tui"
  }
}
This tells TypeScript to:
  • Use the automatic JSX transform (no React.createElement needed)
  • Import the jsx and jsxs functions from kraken-tui/jsx-runtime

Manual JSX Import (if not using automatic transform)

If you cannot use the automatic transform, import the factory manually:
import { jsx, jsxs } from "kraken-tui";

const tree = jsxs("Box", {
  width: "100%",
  children: [
    jsx("Text", { content: "Hello" }),
    jsx("Text", { content: "World" }),
  ],
});

VNode Structure

The JSX factory produces VNode descriptors:
interface VNode {
  type: string | typeof Fragment | ComponentFunction;
  props: Record<string, unknown>;
  key: string | number | null;
  children: VNode[];
}
type
string | symbol | function
  • "Box", "Text", etc. for intrinsic elements
  • Fragment symbol for child-only grouping
  • Component function for custom components
props
Record<string, unknown>
Props object (excluding children and key).
key
string | number | null
Optional key for reconciliation. Defaults to null (positional matching).
children
VNode[]
Normalized child VNodes (always an array, empty if no children).

Instance Bookkeeping

Mounted instances track signal effects and event handlers for cleanup:
interface Instance {
  widget: Widget;                    // Native widget handle (or null for Fragment)
  vnode: VNode;                      // Original VNode descriptor
  children: Instance[];              // Mounted child instances
  cleanups: (() => void)[];          // Signal effect disposers
  key: string | number | null;      // Key for reconciliation
  parent: Instance | null;           // Parent instance reference
  eventHandlers: Map<string, EventHandler>; // JSX event handlers (onClick, etc.)
}

Fragment Behavior

Fragments have no native widget — children are mounted directly into the nearest widget-bearing ancestor.
<Box>
  <>
    <Text content="Child 1" />
    <Text content="Child 2" />
  </>
</Box>
Both Text widgets become direct children of Box in the native tree.

Component Functions

Component functions receive props and return a VNode:
type ComponentFunction = (props: Record<string, unknown>) => VNode;
function Greeting({ name }: { name: string }) {
  return (
    <Box padding={1}>
      <Text content={`Hello, ${name}!`} />
    </Box>
  );
}

render(<Greeting name="World" />, app);

See Also

  • signals.mdx — Signal-driven reactivity with signal(), computed(), effect()
  • reconciler.mdx — Keyed reconciliation algorithm
  • createLoop() — Animation-aware event loop for JSX apps

Build docs developers (and LLMs) love