Author without JSX
createElement(type, props, ...children) is the complete non-JSX API for
Pibbl component values. It uses
the same branded element protocol, component identity, child traversal, style,
hooks, events, focus policies, scheduling, and disposal as JSX. You do not need
any JSX compiler configuration to use this guide.
Install and mount
Section titled “Install and mount”pnpm add @pibbl/coreimport { FocusManagement, Fragment, Group, KeyboardNavigation, Rectangle, Text, pibbl, createElement, useSignal, type PibblController, type PibblNode,} from "@pibbl/core";
interface PosterProps { initialMessage: string;}
function Poster({ initialMessage }: PosterProps): PibblNode { const message = useSignal(initialMessage);
const cards = ["A", "B"].map((label, index) => createElement( Group, { key: label, children: null }, createElement(Rectangle, { style: { left: 32 + index * 100, top: 100, width: 84, height: 56, fill: "#f7c948", cursor: "pointer", }, onClick: () => message.set(label), }), createElement( Text, { pointerEvents: "none", style: { left: 64 + index * 100, top: 116 } }, label, ), ), );
return createElement( FocusManagement, null, createElement( KeyboardNavigation, { mode: "directional", children: null }, createElement( Group, null, createElement(Rectangle, { style: { width: 240, height: 180, fill: "#1f4bd8" }, }), createElement( Text, { pointerEvents: "none", style: { left: 24, top: 28, fill: "white" }, }, "Selected: ", message, ), createElement(Fragment, { key: "choices", children: null }, cards), ), ), );}
export function mount(canvas: HTMLCanvasElement): PibblController { return pibbl(canvas, createElement(Poster, { initialMessage: "None" }));}
export function attach(canvas: HTMLCanvasElement): () => void { const controller = mount(canvas); return () => controller.dispose();}Call controller.dispose() when the owner removes the Canvas or no longer
needs the scene. Disposal is safe to repeat.
Constructor rules
Section titled “Constructor rules”createElement(type, props, ...children);typeis a Pibbl component value or ordinary synchronous component function.propsis the component’s props object ornull.- With no variadic children, an explicitly supplied
props.childrenis preserved. - One variadic child becomes
children; several become an array. Variadic children overrideprops.children. - For a dynamic list, pass the array as one third argument:
createElement(Group, null, items.map(makeItem)). keymay be a string or number, is normalized to a string, and is removed from delivered props.- The constructor copies the top-level props object. Development builds shallow-freeze synthetic props and the element, but nested author objects are not deep-frozen.
Directly calling Rectangle(props) or another platform component does not
create an element and throws a targeted diagnostic. Always use
createElement(Rectangle, props).
Children, text, and containers
Section titled “Children, text, and containers”Group, Clip, Layer, Absolute, Overlay, Flow, Flex, and Grid
accept recursive synchronous node input. Arrays and finite iterables flatten;
booleans, null, undefined, the empty string, and zero are empty. Non-empty
raw strings and nonzero numbers outside Text are errors.
Pass textual values to Text as constructor children:
createElement(Text, { style: { left: 20, top: 20 } }, "Count: ", 0);Zero renders inside Text. Text children may be nested arrays or finite
iterables of strings, numbers, and empty values. Elements are invalid inside
textual content.
Fragment is available for an explicit keyed identity boundary:
import { Fragment, createElement, type PibblNode } from "@pibbl/core";
function keyedSection(section: { id: string; elements: PibblNode }) { return createElement( Fragment, { key: section.id, children: null }, section.elements, );}Focus and lifecycle
Section titled “Focus and lifecycle”FocusManagement and KeyboardNavigation are component values here too.
Each policy requires exactly one Pibbl element after empty values are removed;
nest them as shown in the full example. They do not create paint, layout, or an
extra identity boundary.
Hooks run only when Pibbl later invokes the component during its synchronous scheduled traversal. Calling the component directly is not a substitute for constructing an element. All hook-order, identity, failure, and ownership rules in the lifecycle guide apply unchanged.
The executable Studio lesson is
create-element-authoring.example.tsx.
Three.js without JSX
Section titled “Three.js without JSX”defineThreeLayer() returns an ordinary Pibbl component value, so it can be
passed to createElement() just like a built-in. The Three scene itself still
uses normal Three constructors rather than Pibbl elements:
const scene = createElement(ProductScene, { rotation: 0.5, missBehavior: "pass-through", style: { width: 320, height: 180 },});See Use Three.js inside Pibbl for the layer definition, picking, scheduling, and resource-ownership contracts.
Open the interactive workbench
Implementation guidance for agents
Section titled “Implementation guidance for agents”Read the Authoring, signals, and lifecycle companion for ownership, adaptation, failure modes, and verification. Agent start provides the version-selection workflow.
Documentation version
Section titled “Documentation version”Documentation built with @pibbl/core 0.0.2, revision c321a83. ALPHA — NOT FOR PRODUCTION USE.