Blocks & artifact kit

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.

counter.tsx
tsx
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.

text
// 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 change

What 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.