Skip to main content

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.

Toolbar preview


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 typeComponent
textboxTextToolbar (font family, size, color, alignment, shadow, stroke, …)
imageImageToolbar (filters, crop, stroke, AI eraser, …)
path / rect / circle / ellipse / triangleFigureToolbar (fill, stroke, radius, …)
cropimage (during crop mode)CropImageToolbar (Done / Cancel)
Multiple selectionManyToolbar
Nothing selectedDefaultToolbar (canvas size, background, add-element entry, …)

Right region (6 components + 1 business slot)

Always visible:

  • Group — group / ungroup
  • Position — position panel (alignment, layering, coordinate nudging)
  • Opacity
  • Lock
  • Duplicate
  • Remove
  • ActionControlsbusiness 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:

KeyPurposeDefault
ActionControlsRightmost business slot ("Save / Download / Publish")DownloadButton if downloadButtonEnabled is on
HistoryLeft-side undo / redo buttonsHistoryButtons
GroupGroup / ungroupGroupButton
PositionPosition panelPositionPicker
OpacityOpacity sliderOpacityPicker
LockLock / unlockLockButton
DuplicateDuplicateDuplicateButton
RemoveDeleteRemoveButton

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:

FeaturePolotnoYdesign
Override granularitycomponents={{ TextFill: MyPicker }} (drills into sub-fields)registerToolbarComponent('textbox', MyTextToolbar) (replace the whole toolbar) or fork the built-in component
unstable_registerToolbarComponentSame 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 libraryBlueprintAnt Design v6

For Polotno-style "just replace the color picker inside the text toolbar":

  1. Fork TextToolbar.tsx into a MyTextToolbar, changing only what you need;
  2. registerToolbarComponent('textbox', MyTextToolbar) to register it;
  3. 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.


Next