Renderables vs Constructs

Imperative and declarative composition compared.

Comparison

The Cascade API supports both imperative and declarative approaches. Renderables give you direct access to runtime objects and allow imperative manipulation: you can create instances, call methods and set properties at any time. This is useful for low‑level control or dynamic updates.

Constructs, on the other hand, produce virtual nodes that are instantiated later. They encourage a declarative style similar to JSX or Solid/React components and make it easy to reuse factories or build higher‑order patterns.

Both models can coexist. Use renderables when you need explicit control, such as integrating with imperative APIs or performing incremental updates. Use constructs when composition and reuse are priorities. You can even mix them: instantiate a construct and then access the underlying renderables for custom behaviour.

import { BoxRenderable, TextRenderable } from "@cascadetui/core"

const panel = new BoxRenderable(renderer, { border: true, padding: 1 })
panel.add(new TextRenderable(renderer, { content: "Imperative" }))
renderer.root.add(panel)

import { Box, Text, instantiate } from "@cascadetui/core"

const vnode = Box({ border: true, padding: 1 }, Text({ content: "Declarative" }))
const node = instantiate(renderer, vnode)
renderer.root.add(node)

If you’re building a component library or want a “UI as data” style, start with constructs. If you’re building highly dynamic experiences (custom editors, terminals that stream content, animations), renderables can be a simpler mental model.