# 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

```sh
pnpm add @pibbl/core
```

```ts docs:complete-snippet=without-jsx
import {
  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

```ts
createElement(type, props, ...children);
```

- `type` is a Pibbl component value or ordinary synchronous component function.
- `props` is the component's props object or `null`.
- With no variadic children, an explicitly supplied `props.children` is
  preserved.
- One variadic child becomes `children`; several become an array. Variadic
  children override `props.children`.
- For a dynamic list, pass the array as one third argument:
  `createElement(Group, null, items.map(makeItem))`.
- `key` may 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

`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:

```ts
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:

```ts
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

`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](/guides/state-and-lifecycle/) apply
unchanged.

The executable Studio lesson is
[`create-element-authoring.example.tsx`](https://github.com/benlesh/pibbl/blob/main/packages/scenarios/src/learning/start/create-element-authoring.example.tsx).

## 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:

```ts
const scene = createElement(ProductScene, {
  rotation: 0.5,
  missBehavior: "pass-through",
  style: { width: 320, height: 180 },
});
```

See [Use Three.js inside Pibbl](/guides/three-js/) for the layer definition,
picking, scheduling, and resource-ownership contracts.

[Open the interactive workbench](/playground/#/workbench/create-element-authoring)

## Implementation guidance for agents

Read the [Authoring, signals, and lifecycle companion](/agents/topics/lifecycle/) for ownership, adaptation, failure modes, and verification. [Agent start](/agents/) provides the version-selection workflow.

## Documentation version

Documentation built with @pibbl/core 0.0.2, revision 272a94a. ALPHA — NOT FOR PRODUCTION USE.
