Toolbar
<Toolbar /> is the horizontal bar above the canvas. It lets users change styles of the selected element (font, color, filters, …), adjust ordering (layers, alignment, grouping) and run actions like undo/redo, lock, duplicate, delete.
The toolbar automatically switches based on the selected element's type: select text, see text tools; select an image, see image tools; nothing selected, show the default toolbar.

Basic usage
import Toolbar from '@ydesign/react-editor/toolbar';
import Workspace from '@ydesign/react-editor/canvas/workspace';
const App = ({ store }) => {
return (
<div style={{ display: 'flex', height: '100%', flex: 1, flexDirection: 'column' }}>
<Toolbar store={store} />
<Workspace store={store} />
</div>
);
};
Props
type ToolbarProps = {
store: StoreType;
downloadButtonEnabled?: boolean; // show the built-in download button (default: false)
components?: ToolbarComponents; // slots, see below
};
// minimal: enable the default download button
<Toolbar store={store} downloadButtonEnabled />
Anatomy
Toolbar is divided into three regions:
┌───────────────────────────────────────────────────────────────────────────────┐
│ [History] [type-specific toolbar] [Group] [Position] [Opacity] [Lock]│
│ [Duplicate] [Remove] [ActionControls] │
└───────────────────────────────────────────────────────────────────────────────┘
└─ left ──┘└─── center (varies by selection) ───┘└────── right ──────┘└── slot ──┘
Type-specific toolbar (center)
Switches automatically via an internal ComponentsTypes map:
| Selected element type | Component |
|---|---|
textbox | TextToolbar (font family, size, color, alignment, shadow, stroke, …) |
image | ImageToolbar (filters, crop, stroke, AI eraser, …) |
path / rect / circle / ellipse / triangle | FigureToolbar (fill, stroke, radius, …) |
cropimage (during crop mode) | CropImageToolbar (Done / Cancel) |
| Multiple selection | ManyToolbar |
| Nothing selected | DefaultToolbar (canvas size, background, add-element entry, …) |
Right region (6 components + 1 business slot)
Always visible:
Group— group / ungroupPosition— position panel (alignment, layering, coordinate nudging)OpacityLockDuplicateRemoveActionControls— business slot where you drop "Save / Download / Publish" style buttons
components slot: override the right region
Every key in the components object matches a button on the right. Provide one and it replaces the default:
import { Toolbar } from '@ydesign/react-editor/toolbar';
import { DownloadButton } from '@ydesign/react-editor/toolbar/download-button';
import { Button } from 'antd';
const ActionControls = ({ store }) => (
<div style={{ display: 'flex', gap: 8 }}>
<Button type="primary" onClick={() => save(store)}>Save</Button>
<DownloadButton store={store} />
</div>
);
<Toolbar
store={store}
components={{
ActionControls,
}}
/>;
All overridable keys:
| Key | Purpose | Default |
|---|---|---|
ActionControls | Rightmost business slot ("Save / Download / Publish") | DownloadButton if downloadButtonEnabled is on |
History | Left-side undo / redo buttons | HistoryButtons |
Group | Group / ungroup | GroupButton |
Position | Position panel | PositionPicker |
Opacity | Opacity slider | OpacityPicker |
Lock | Lock / unlock | LockButton |
Duplicate | Duplicate | DuplicateButton |
Remove | Delete | RemoveButton |
Remove a default button
Pass a component that returns null:
const Empty = () => null;
<Toolbar store={store} components={{ Opacity: Empty, Lock: Empty }} />;
Compose a toolbar from built-in buttons
Every button is exported individually from @ydesign/react-editor/toolbar/*, so you can build your own bar:
import { HistoryButtons } from '@ydesign/react-editor/toolbar/history-buttons';
import { DuplicateButton } from '@ydesign/react-editor/toolbar/duplicate-button';
import { RemoveButton } from '@ydesign/react-editor/toolbar/remove-button';
import { LockButton } from '@ydesign/react-editor/toolbar/lock-button';
import { DownloadButton } from '@ydesign/react-editor/toolbar/download-button';
const MyToolbar = ({ store }) => (
<div className="my-toolbar">
<HistoryButtons store={store} />
<DuplicateButton store={store} />
<RemoveButton store={store} />
<LockButton store={store} />
<DownloadButton store={store} />
</div>
);
Register a toolbar for a new element type
When you introduce a new element type (QR code, barcode, chart, …), tell the toolbar how to render for it:
import { registerToolbarComponent } from '@ydesign/react-editor/toolbar';
import { observer } from 'mobx-react-lite';
const QRCodeToolbar = observer(({ store }) => {
const qr = store.selectedElements[0];
if (!qr) return null;
return (
<div style={{ display: 'flex', gap: 8, padding: '0 12px' }}>
<input
defaultValue={qr.data}
onChange={e => store.set({ data: e.target.value }, qr)}
placeholder="QR content"
/>
</div>
);
});
// Whenever an element with type === 'qrcode' is selected, your toolbar is used
registerToolbarComponent('qrcode', QRCodeToolbar);
Toolbar components always receive { store }. Read elements from store.selectedElements and mutate via store.set(...).
Fully replace a built-in type toolbar
registerToolbarComponent can also override built-in types. Example: a fully custom text toolbar:
import { registerToolbarComponent } from '@ydesign/react-editor/toolbar';
import { observer } from 'mobx-react-lite';
const MyTextToolbar = observer(({ store }) => {
const el = store.selectedElements[0];
if (!el) return null;
return (
<div>
{/* your UI — color, font size, bold, … */}
</div>
);
});
// Override the built-in TextToolbar
registerToolbarComponent('textbox', MyTextToolbar);
Overridable types: textbox / image / path / rect / circle / ellipse / triangle / many / cropimage.
Full example
import { Toolbar, registerToolbarComponent } from '@ydesign/react-editor/toolbar';
import { DownloadButton } from '@ydesign/react-editor/toolbar/download-button';
import Workspace from '@ydesign/react-editor/canvas/workspace';
import { Button } from 'antd';
import { observer } from 'mobx-react-lite';
// 1) Custom ActionControls: Save + Download
const ActionControls = ({ store }) => (
<div style={{ display: 'flex', gap: 8 }}>
<Button type="primary" onClick={() => saveToServer(store)}>
Save
</Button>
<DownloadButton store={store} />
</div>
);
// 2) Full replacement for the text toolbar
const MyTextToolbar = observer(({ store }) => {
/* ... */
});
registerToolbarComponent('textbox', MyTextToolbar);
// 3) Hide Opacity
const Empty = () => null;
// 4) Assemble
const App = ({ store }) => (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<Toolbar
store={store}
components={{
ActionControls,
Opacity: Empty,
}}
/>
<Workspace store={store} />
</div>
);
Differences from Polotno's Toolbar
If you're migrating from Polotno, note that the APIs are not compatible:
| Feature | Polotno | Ydesign |
|---|---|---|
| Override granularity | components={{ TextFill: MyPicker }} (drills into sub-fields) | registerToolbarComponent('textbox', MyTextToolbar) (replace the whole toolbar) or fork the built-in component |
unstable_registerToolbarComponent | ✅ | Same intent as registerToolbarComponent (no unstable_ prefix) |
downloadButtonEnabled | ✅ | ✅ same name, same behavior |
Right-side ActionControls slot | ✅ | ✅ fully aligned |
Sub-component overrides like TextFontFamily / ImageFlip | ✅ | ❌ (granularity is element-type level; replace the whole TextToolbar via registerToolbarComponent, or fork its source) |
PageDuration (per-page animation duration) | ✅ | ❌ (no Page / animation concept) |
| UI component library | Blueprint | Ant Design v6 |
For Polotno-style "just replace the color picker inside the text toolbar":
- Fork
TextToolbar.tsxinto aMyTextToolbar, changing only what you need; registerToolbarComponent('textbox', MyTextToolbar)to register it;- Import the sub-components you want to keep (font size input, etc.) from
@ydesign/react-editor/toolbar/*.
Slightly more boilerplate than Polotno's sub-field overrides, but easier to reason about long-term.