React adapter for blocks-schema UI documents: a recursive
renderer with layerable widget registries, binding resolution, form state, and
a visible unknown-block fallback.
pnpm add blocks-renderer blocks-schema reactBuilt with makage and published from dist, so deep imports resolve without an
exports map: blocks-renderer/registry, blocks-renderer/bindings.
import { DocumentRenderer, composeRegistry } from 'blocks-renderer';
const registry = composeRegistry(
baseRegistry, // shadcn-style primitives
appRegistry, // app-specific blocks
documentOverrides, // per-document swaps
);
<DocumentRenderer
document={document}
registry={registry}
scope={{ row, user }}
initialValues={{ title: 'Draft' }}
onSubmit={(values) => save(values)}
onAction={(action, event) => runFlow(action)}
/>;- Registry layering —
composeRegistry(...layers)mergestype → componentmaps left-to-right, later layers winning. Hosts customize widgets (inputs, textareas, rich text, custom blocks) by layering, never by forking the renderer. - Blocks — every component receives
{ node, props, children }, wherepropshas the node'sbindingsresolved against the current scope andchildrenare already rendered. - Bindings —
{{ path.to.value }}templates resolve againstscope + values; a lone placeholder yields the raw value, mixed text interpolates. - Fields — widget implementations call
useBlockField(name)for value/error wiring; constraints from the document (required, length, range,pattern) validate on change and on submit. - Unknown blocks — unregistered node types render
UnknownBlock, a visible gap instead of a crash, so documents can name blocks a host has not installed.