API reference
Props, types, and error behavior for @joy-dom/react.
<Joy> (component)
The primary entry point. Renders a Joy DOM document as React elements.
<Joy spec={spec} components={{ "contact-button": ContactButton }} />Props (JoyProps)
| Prop | Type | Required | Description |
|---|---|---|---|
spec | Spec | TemplateSpec | Yes | The Joy DOM document. Directives resolve against the document's state on every render. |
initialState | Json | No | Overrides the template's state region as the state seed. |
directives | DirectiveRegistry | No | Directive registry replacing the default $event/$get/$if/$for pack. |
components | Record<string, JoyComponent> | No | Custom node registry, keyed by kebab-case type name. |
onEvent | JoyEventHandler | No | Listener for event actions reaching the host. Mutations never arrive here. |
onError | JoyErrorHandler | No | Listener for interactions that stop on a typed ActionError. Without it, errors log. |
responsive | "media" | "container" | No | How breakpoints resolve: viewport @media (default) or @container against its own box. |
ref | Ref<HTMLDivElement> | No | Ref to the host div that holds the shadow root, useful for measuring or screenshotting. |
<Joy> renders into its own shadow root, so the document's global ids and classes stay isolated from the host page.
Types
JoyProps
The document owns its state: seeded from the template's state region (or initialState), changed only by actions. See Templates and state.
type JoyProps = JoyRenderOptions & {
/** Ref to the host element that holds the shadow root. */
ref?: Ref<HTMLDivElement>;
spec: TemplateSpec | Spec;
/** Overrides the template's `state` seed. */
initialState?: Json;
directives?: DirectiveRegistry;
};JoyRenderOptions
The options <Joy> accepts alongside spec:
type JoyRenderOptions = {
components?: Record<string, JoyComponent>;
onEvent?: JoyEventHandler;
onError?: JoyErrorHandler;
renderNode?: NodeRenderer;
responsive?: "media" | "container";
};JoyComponent<N>
A custom component:
type JoyComponent<N extends Node = Node> = ComponentType<JoyComponentProps<N>>;JoyComponentProps<N>
What custom components receive: RenderedNodeProps plus the rendered children and the original node.
type JoyComponentProps<N extends Node = Node> = RenderedNodeProps & {
children?: ReactNode;
node: N;
};| Prop | What's in it |
|---|---|
children | Rendered child nodes and primitive values. |
node | The raw Joy DOM node. Read it for anything the renderer doesn't surface directly. |
id | Resolved node id (from props.id). |
className | The document's class list for the node, joined into a string. |
style | Resolved inline style. Built-in nodes only; undefined for custom types. |
alt | img-only. The resolved alt attribute. |
src | img-only. The resolved src attribute. |
For a custom node, style, alt, and src stay unset. Style it from the cascade via className. The on* handlers are passed whenever the node binds a matching action, and the component decides which element fires them.
RenderedNodeProps
The bag of props the renderer passes to every node (text, image, block, or custom):
type RenderedNodeProps = {
alt?: string;
id?: string;
className?: string;
src?: string;
style?: CSSProperties;
onClick?: (event: SyntheticEvent) => void;
onFocus?: (event: SyntheticEvent) => void;
onBlur?: (event: SyntheticEvent) => void;
onChange?: (event: SyntheticEvent) => void;
};The on* handlers are wired on nodes whose props carry a matching event reference. Each one runs the named handler from the document's root handlers region as one interaction: mutations change the document's state, event actions (and references with no matching handler) reach onEvent. Custom components receive the handlers and choose where to attach them.
JoyEventHandler
Passed as onEvent. It fires for every event action that reaches the host, after the interaction's state changes have applied:
type JoyEvent = {
name: string;
params: Record<string, Json>;
/** The node whose binding raised the event; absent for host-dispatched actions. */
node?: Node;
};
type JoyEventHandler = (event: JoyEvent, handle: JoyHandle) => void;Branch on event.name; event.params is the action's params with directive reads resolved.
JoyHandle
The host's way back into the document. Anything done through it runs as a new interaction, queued after the one that raised the event:
type JoyHandle = {
dispatch: (action: Action) => void;
/** Snapshot of the settled state, a copy safe to keep or mutate. */
getState: () => Json;
};JoyErrorHandler
Passed as onError. Receives the typed ActionError when an interaction stops early. It carries identity (what went wrong), verb, path, and at (the failing action's position in nested sequences):
type JoyErrorHandler = (error: ActionError) => void;renderSpec (function)
Stateless render of an already-resolved Spec, with no runtime and no directives. Bound actions reach options.dispatch when their event fires. Without it, bindings stay inert.
function renderSpec(spec: Spec, options?: JoySpecOptions): ReactElement;Use it for static server rendering or test harnesses; <Joy> is the stateful entry point.
Error behavior
- A template fails to resolve. The resolve pass throws a
ResolveErrorfrom@joy-dom/core, carryingidentity(what went wrong) andmarker(which directive). Missing state data does not throw; it reads asnulland the render degrades. - An interaction fails. A mutation hitting the wrong shape stops its interaction with a typed
ActionError, reported throughonError(or the console). Applied actions stay applied; the render never crashes on one. - A document fails
SpecSchemavalidation at the boundary you control.@joy-dom/reactdoes not callSpecSchema.parseitself, so validate before passing. - A document references a custom node with no entry in
components. The renderer throws. This is intentional. Silent fallback hides the real bug. - Check a document before rendering with
validateSpec(spec)from@joy-dom/core. It returns{ path, message }findings for everything the renderer would refuse (e.g. a container with children and no explicitdisplay), so tools can report instead of crash. - A malformed
padding,margin, orborderRadiusshape is dropped silently. Validate upstream withSpecSchemato catch it.
Why doesn't the renderer validate?
The renderer trusts its input. Validation is a step you choose to run at the boundary (network, file load). Running it on every render would be wasteful. See Tutorial › Publishing for the recommended pattern.
Where next
- Tutorial, build a document from scratch, one chapter at a time.
- Specification, the document format this renderer consumes.