Skip to main content
A block is a self-contained UI component. It has props to configure it and slots to compose it with other blocks. A type-safe schema describes both — driving TypeScript inference in the renderer. What makes a block different from a regular component is that it can consume services from its surrounding contexts to read and manipulate live store data (subscriptions, customers, orders) without prop drilling.

Anatomy of a block

Every block has four parts: A block is defined once with defineBlock, registered once with registerBlock, and instantiated many times through createBlockInstance.
A purple juo-block frame named 'SubscriptionCard' containing two green-bordered panels. The props panel has 'shape defined by the schema' as a subtitle at the top, followed by variant, size, and showThumbnail values. The slots panel has 'each slot exposed via juo-extension-root' as a subtitle at the top, followed by header, body, and actions slots. Below, a dashed 'renderer output' frame shows a rendered card with a thumbnail, a bold 'Your plan' heading, two lines of muted text ('Renews monthly · Next: Jul 5, 2026' and '$14.99 / month'), and a 'Manage' button on the right.

A simple block

A button with one prop — a text label. The renderer creates a real DOM element and uses effect() to keep it in sync with block.props.
as const satisfies JSONSchema plus FromSchema<typeof schema> keeps the schema and the renderer’s prop types in sync — change the schema, the TS types update.

A more advanced block

A card block with multiple typed props, nested slots, and a preset shown in the Editor’s picker.
Highlights:
  • Typed props. variant, size, and showThumbnail are fully typed inside the renderer.
  • Slots. A slot can be a single block, an array of blocks, or a plain string. createBlockInstance builds a nested block by name and merges overrides into its default initialValue.
  • Presets. Each preset provides a complete state snapshot (props + slot contents) — a named starting point for a block instance.

Block groups

Every block declares a group that identifies its origin: All three groups are first-class — there’s no behavioural difference between them.

Registering blocks

registerBlock adds the definition to the registry so it can be instantiated by name. Call it once at the top of the package entry file:
After registration, the block becomes available wherever the runtime is loaded.