Everything is a registered block
Core blocks (heading, list, table, columns…) and the reactive kit (sliders, formulas, charts, status) share one registry and one API.
Register a block
A block is a type, a React render function, and an optional slash command. The built-in registerReactiveBlocks() and registerArtifactKit() use exactly this API — nothing is privileged.
import {registerCustomBlock} from '@book.dev/ui';
registerCustomBlock({
type: 'counter',
render: ({block, editor}) => {
const n = block.get('props')?.count ?? 0;
return <button onClick={() => editor.setProp(block, 'count', n + 1)}>{n}</button>;
},
slash: {label: 'Counter', hint: 'A click counter', make: () => ({type: 'counter', props: {count: 0}})},
});The reactive scope
Inputs publish a named value. Formulas and charts evaluate expressions over the live scope and recompute in document order, so outputs can chain.
// inputs publish names; formulas/charts read them
slider name="rate" value=5 → scope.rate = 5
formula source="rate * 12" → 60
kitchart source="[1, rate, rate*2]" → re-renders on every changeWhat ships in the kit
Inputs: slider, stepper/number, text, long text, radio, checklist, toggle, tag field, search-select, location, action button. Display: charts (line, bar, pie, scatter, funnel), status light, progress bar, tooltip and link cards. Plus structure: columns, tables, tabs, accordions and lockable groups that namespace their inputs.