Editing
<BannerBuilder> is the admin half of the package. It fills its container, owns its own undo history, and hands you a document when the user saves.
import { BannerBuilder } from 'react-bannerkit/builder'
import 'react-bannerkit/builder.css'
<div style={{ height: '100vh' }}>
<BannerBuilder
template={template}
onChange={(t) => setDraft(t)}
onSave={async (t) => { await api.save(t) }}
onUploadImage={async (file) => (await upload(file)).url}
theme="system"
/>
</div>Props
| Prop | Type | Notes |
|---|---|---|
template | BannerTemplate | Optional. Omitted, a default template is created. Repaired rather than trusted. |
onChange | (t) => void | Fires on a trailing debounce once edits settle, so dragging a slider calls you once rather than sixty times. |
onSave | (t) => void | Promise<void> | Fires only on Save, or Ctrl/Cmd+S. Return a promise and the button shows progress; throw and the message is shown and announced through aria-live. |
onUploadImage | (file: File) => Promise<string> | Resolve the URL to store. Without it, images become object URLs and the editor says they will not persist. |
theme | 'light' | 'dark' | 'system' | A self-contained palette. It does not read your design tokens. |
debounceMs | number | Default 300. |
makeId | IdFactory | Injected for deterministic ids in tests. |
className, style | Merged onto the root; the bnb-root scope class is always kept. |
template is not a controlled value. Changing it does not reset an in-progress edit — the editor takes it as a starting document. Remount with a key when you genuinely want to load a different one, which is what the playground does.
Keyboard
| Keys | Does |
|---|---|
| Ctrl/Cmd+Z, Shift+Ctrl/Cmd+Z | Undo, redo. A whole drag collapses into one step. |
| Ctrl/Cmd+S | Save. |
| Escape | Deselect. |
| Arrow keys | Nudge a freely placed element; Shift for 10× steps. |
| Arrow keys on a divider | Resize the split. |
The listener is bound to the editor rather than the document, so an admin page's own shortcuts keep working.
What the editor can do
- Panels. Split any panel into columns or rows, drag the divider, delete a panel to give its space back to its sibling. A banner always keeps at least one panel, and the inspector explains that rather than just disabling a button.
- Elements. Heading, text, button, link, image, overlay, spacer, icon. Drag one out of the stack to place it freely.
- Carousels. Turn a panel into a carousel and every slide gets its own background and content. Converting copies what was already there onto slide one.
- Three breakpoints, each with an independent layout, sizing, gutter, and frame colour. Copy one screen's layout to another as a starting point.
- Preview renders the real
<BannerRenderer>, not a lookalike.
Uploads
onUploadImage receives the File and resolves the URL to store in the document. There is no built-in storage: the document holds a string, and where that string points is your decision.
Without the callback the editor falls back to URL.createObjectURL and says so in the UI — useful for a demo like this site's playground, useless for anything that has to survive a reload.
Driving it yourself
The state layer is exported with no React in it, so you can put the editor's chrome in your own shell, build a custom inspector, or test editor behaviour without a DOM:
import { createEditorState, editorReducer, inspectorModel } from 'react-bannerkit/builder'
let state = createEditorState(template)
state = editorReducer(state, { type: 'splitPanel', panelId, dir: 'cols' })
const fields = inspectorModel(state)Editor state API has the full action list and the selectors that go with it.