Customising
Change the built-ins, hide fields, hide interface, fill slots, and make the chrome look like your product.
Images: saving to your own API
The package uploads nothing itself. Two functions connect it to your storage — one to put a file somewhere, one to turn what you stored back into something a browser can show.
The PHP endpoint
The two props that use it
resolveImageUrl — one line — and every
document ever saved follows. Store URLs and you would be rewriting historical rows.
The full set of image props, including deletion and a picker for previous uploads, is in Images & data.
Labels: change any word in the interface
Every visible string is a key — 191 of them. Nothing is hardcoded inside a component.
Getting the list
Overriding
labelKey resolves through the same mechanism, so a key nothing resolves
renders as itself — which is how you notice a missing label instead of shipping a blank
control. The live demo does exactly this; the code is in
Custom blocks.
Colours: two separate things
This is the distinction that causes the most confusion, so plainly: the document and the editor around it are coloured separately, and changing one never changes the other.
| To change | Use | Affects |
|---|---|---|
| Colours on the page — headings, rules, table bands | tokens | What prints |
| Colours of the editor — buttons, panels, borders | theme / --sb-* | Only the interface |
The document's own colours
The editor's buttons and chrome
--sb-accent.
One value moves every primary button, the selected-block outline, the active inspector tab,
the drag handles and the drop indicators together — because they are all the same idea.
Changing built-in blocks
Four methods, in increasing order of violence. Each returns the registry, so they chain.
| Method | Does |
|---|---|
register(def) | Adds a new block. |
extend(type, patch) | Merges a partial patch into an existing definition. The gentlest option. |
addLayout(type, layout) | Adds one design to an existing block. |
override(def) | Replaces a definition wholesale. |
And to read it: get(type), has(type), list(), clone().
extend over override.
extend keeps everything you did not mention — including designs and schema
improvements that arrive in later versions. override freezes your copy in time.
Removing a block from the palette
Build the registry from a filtered list rather than deleting from it. That way the definition still exists for documents that already contain one, and an old document does not lose its content.
Hiding and relabelling fields
The fields prop controls individual inspector fields without touching the block
definition. The key is '{blockType}.{tab}.{fieldKey}', and * is a
wildcard at any position.
hidden | The field does not render. |
disabled | Visible, greyed out, not editable. |
label | Replaces the resolved label with your literal string. |
What each one looks like
hidden removes it.
disabled and hidden answer different questions.
Use disabled when the user should see the value but not change it — an invoice
number your system owns. Use hidden when the field is irrelevant to them and its
presence is just noise.
'lineItems.content.tax' beats 'lineItems.content.*', which beats
'*.content.*'. Order in the object is irrelevant, so you can list rules however
reads best.
content, style or template.
template is the Designs tab. 'x.designs.y' matches nothing.
Hiding interface
The ui prop takes three lists of element names. Names are hierarchical, and a
parent implies its children.
| Name | What it is |
|---|---|
toolbar | The whole toolbar. |
toolbar.save | The Save button. |
toolbar.undo / toolbar.redo | History buttons. |
toolbar.theme | Light/dark switch. |
toolbar.templates | The template picker. |
toolbar.swap | Swap the panel sides. |
toolbar.ai | The AI design button — present only when onAiDesign is passed; this hides it anyway. |
panel.blocks | The left blocks panel. |
panel.properties | The right inspector. |
show beats hide at equal or greater specificity.
So hide: ['toolbar'] with show: ['toolbar.save'] keeps the toolbar
present with only Save on it — the container survives because something inside it was
explicitly kept.
Slots and replacements
Put your own nodes into named positions.
| Slot | Where |
|---|---|
toolbarStart / toolbarEnd | Either end of the toolbar. |
blocksPanelHeader | Above the block palette. |
propertiesPanelHeader | Above the inspector. |
canvasEmptyState | Shown on an empty document. |
Replaceable components: Toolbar and EmptyState.
Fonts
Typography exists at three levels, and knowing which one you want saves a lot of confusion — they look similar and do different jobs.
| Level | Set with | Affects |
|---|---|---|
| The document default | tokens.fontFamily | Every block that has not chosen its own. |
| One block | The font field in the Style tab | That block only. Empty means "follow the document". |
| The editor's own UI | --sb-font-ui | Panels, buttons, labels. Not the document. |
Which typefaces users can pick
The fonts prop is the list offered in every font picker.
fonts REPLACES the default list — it does not extend it.
Pass three and your users see three. If you want the built-ins plus yours, say so explicitly:
Setting the document's default typeface
Loading the actual font files
The package does not load webfonts — it has no network access of any kind. Load them the way you load any other font in your application, and the builder will use them.
fonts, in tokens.fontFamily and in your
@font-face must match. A mismatch does not error — the browser silently falls back,
and the document quietly renders in something else.
onRenderPdf. See
Preview & PDF.
fonts list, or hide the control outright with
fields={{ '*.style.fontFamily': { hidden: true } }} — useful when brand
compliance matters more than user choice.
Theming the chrome
The builder's own interface — not the document — is styled with public
--sb-* custom properties. Override them and your host CSS never needs
!important.
Or in your own stylesheet, scoped to the builder's root:
The token families you can rely on:
--sb-accent, -hover, -bg, -fg | The accent colour and its variants. |
--sb-surface-0 … -1 | Panel and background surfaces. |
--sb-text-primary, --sb-border, --sb-border-strong | Text and edges. |
--sb-canvas-bg, --sb-page-bg, --sb-page-shadow | The canvas and the page sheet. |
--sb-radius-card, -control, -panel | Corner radii. |
--sb-space-1 … -12 | The spacing scale. |
--sb-font-ui, --sb-font-size-body … | Interface typography. |
--sb-success, --sb-danger, --sb-danger-bg | State colours. |
--sb-shadow-sm, -md, -lg | Elevation. |
Pass a whole theme object, or several for the user to pick from:
cssVars styles the editor; tokens styles the document.
Two different things that both look like "theme". tokens only seeds new
documents — it never rewrites one a user already made.
Panel layout
persistKey.
No exceptions, no silent local caching. If you want the layout remembered, name the key; if
you would rather store it on your server, use onLayoutChange.
Read-only mode
A viewer rather than an editor: no palette, no inspector, no drag-and-drop, no editing affordances inside blocks. Useful for approval screens and audit views where the document must be seen exactly as it will print.