Customizations
@ydesign/react-editor has been designed for deep customization from day one. You can drop it in as a complete, ready-to-use design editor, or drill down layer by layer — replacing, removing, or adding any piece of UI and canvas behavior — until you end up with an editor that is entirely your own.
This guide covers the 8 most common customization scenarios and the real APIs that back them.
💡 Throughout this page,
storerefers to the MobX-State-Tree instance returned bycreateStore({ key })orcreateDesignEditorApp(...).store.editoris theEditorinstance from@ydesign/corethat holds every canvas handler.
1. Full control of the Side Panel
The left-side panel is the area where products diverge the most. Rather than hard-coding it, @ydesign/react-editor exposes it as a Section array. Every built-in Section can be cherry-picked by name, reordered freely, or replaced wholesale.
1.1 Show only the panels you need
Pass a list of section names to createDesignEditorApp or <SidePanel />:
import { createDesignEditorApp } from '@ydesign/react-editor';
createDesignEditorApp({
container: document.getElementById('root')!,
key: 'YOUR_API_KEY',
// Only show these 5 panels
sections: ['templates', 'text', 'photos', 'shapes', 'background'],
});
Built-in section names:
| Name | Description |
|---|---|
templates | Template gallery |
text | Text (fonts, styles, presets) |
photos | Image library |
shapes | Shapes |
upload | Upload local assets |
background | Background color / image |
layers | Layer management |
size | Canvas size |
idphoto | ID photo |
inpaint | AI eraser |
1.2 Remove a default panel
import {
SidePanel,
TemplatesSection,
TextSection,
PhotosSection,
ShapesSection,
LayersSection,
// BackgroundSection is omitted — it won't appear
} from '@ydesign/react-editor/side-panel';
const sections = [TemplatesSection, TextSection, PhotosSection, ShapesSection, LayersSection];
<SidePanel store={store} sections={sections} />;
1.3 Add a custom panel
A Section needs just three things: a name, a Tab (the vertical icon button on the side), and a Panel (the content shown when that tab is active).
import { observer } from 'mobx-react-lite';
import { SectionTab } from '@ydesign/react-editor/side-panel';
import type { Section } from '@ydesign/react-editor/side-panel';
import { Sparkles } from 'lucide-react';
const AIPanelSection: Section = {
name: 'ai',
Tab: observer(props => (
<SectionTab name="AI Assistant" {...props}>
<Sparkles size={20} />
</SectionTab>
)),
Panel: observer(({ store }) => {
return (
<div>
<h3>Generate image with AI</h3>
<button
onClick={async () => {
const url = await callYourAIService();
// Add an element to the canvas via store.editor
store.editor?.objectsHandler.addImage({ src: url });
}}
>
Generate
</button>
</div>
);
}),
};
// Combine with built-in sections
import { DEFAULT_SECTIONS } from '@ydesign/react-editor/side-panel';
<SidePanel store={store} sections={[...DEFAULT_SECTIONS, AIPanelSection]} />;
1.4 Replace a default panel (e.g. plug in your own asset library)
Replacing PhotosSection is straightforward — build your own Section with name: 'photos' and swap it into the array:
import { DEFAULT_SECTIONS } from '@ydesign/react-editor/side-panel';
const MyPhotosSection: Section = {
name: 'photos',
Tab: /* ... */,
Panel: observer(({ store }) => {
const [list, setList] = useState([]);
useEffect(() => {
fetch('/my-api/photos').then(r => r.json()).then(setList);
}, []);
return (
<div>
{list.map(item => (
<img
key={item.id}
src={item.thumbnail}
onClick={() => store.editor?.objectsHandler.addImage({ src: item.url })}
/>
))}
</div>
);
}),
};
const sections = DEFAULT_SECTIONS.map(s => (s.name === 'photos' ? MyPhotosSection : s));
2. Toolbar customization
The top toolbar switches its contents automatically based on what is selected on the canvas (text / image / path / multi-selection, etc.). Use the components slot to add your own buttons on the right of the default toolbar:
import Toolbar from '@ydesign/react-editor/toolbar';
import { DownloadButton } from '@ydesign/react-editor/toolbar/download-button';
<Toolbar
store={store}
components={{
ActionControls: () => (
<>
<MyCloudSaveButton store={store} />
<DownloadButton store={store} />
</>
),
}}
/>;
Want to replace the whole toolbar? Just render your own component instead of <Toolbar />. Every built-in button (DownloadButton, DuplicateButton, RemoveButton, LockButton, HistoryButtons, and so on) is exported independently and can be composed freely:
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';
const MyToolbar = ({ store }) => (
<div className="my-toolbar">
<HistoryButtons store={store} />
<DuplicateButton store={store} />
<RemoveButton store={store} />
{/* ...your custom buttons */}
</div>
);
3. Workspace (canvas) customization
<Workspace /> itself is just the canvas container. Change its appearance with CSS, and its behavior through the handlers under store.editor.
3.1 Tweak the canvas appearance
/* your global CSS */
.polotno-app-container {
background: #1e1e1e; /* the whole editor background */
}
.polotno-workspace-container {
background: radial-gradient(circle, #2a2a2a, #181818);
}
3.2 Tweak the canvas behavior
Use the handlers exposed by @ydesign/core:
// Zoom behavior
store.editor?.zoomHandler.zoomToFit();
store.editor?.zoomHandler.setZoom(1.5);
// Smart guidelines
store.editor?.guidelinesHandler.enable();
store.editor?.guidelinesHandler.disable();
// Rulers
store.editor?.workareaHandler.setRulesVisible(true);
// Bleed
store.editor?.workareaHandler.setBleed(3); // mm
store.editor?.workareaHandler.setBleedVisible(true);
See the full list of handlers in the Overview.
4. Internationalization (i18n)
All built-in UI labels live in a reactive translation dictionary. The default language is English; swapping or extending it takes a single call.
import { setTranslations, getTranslations } from '@ydesign/react-editor';
// 1) Fully replace — switch UI to Chinese
setTranslations({
sidePanel: {
text: '文字',
templates: '模板',
photos: '图片',
shapes: '形状',
upload: '上传',
background: '背景',
layers: '图层',
size: '尺寸',
myFonts: '我的字体',
uploadTip: 'Drag files here, or click the upload button',
},
toolbar: {
undo: '撤销',
redo: '重做',
opacity: '透明度',
position: '位置',
layering: '图层',
alignLeft: '左对齐',
flipHorizontally: '水平翻转',
// ...
},
});
// 2) Want to inspect the full key structure?
console.log(getTranslations());
Your own components can reuse the same system:
import { observer } from 'mobx-react-lite';
import { translate, t } from '@ydesign/react-editor/utils/l10n';
const MyPanel = observer(() => {
return <div>{translate('sidePanel.myCustomLabel')}</div>;
// `t` is an alias of `translate`
});
5. Assets & resources: uploads, APIs, backend integration
5.1 Custom image upload
By default local uploads are inlined as base64, which bloats your JSON payload over time. You are strongly encouraged to push images to your own object storage:
import { setUploadFunc } from '@ydesign/react-editor/side-panel/upload-panel';
setUploadFunc(async (file: File) => {
const formData = new FormData();
formData.append('file', file);
const res = await fetch('https://your-api.com/upload', {
method: 'POST',
body: formData,
});
const { url } = await res.json();
return url; // return a short URL usable directly in <img>
});
5.2 Switch the backend host / replace an endpoint
@ydesign/react-editor ships a handful of built-in APIs (font list, template list, image upload, AI eraser, etc.). You can switch the whole host in one call:
import { setBaseURL } from '@ydesign/react-editor';
setBaseURL('https://api.your-company.com');
Or override a single endpoint:
import { setAPI } from '@ydesign/react-editor';
// Point template list to your own system
setAPI('templateList', () => ({
url: 'https://your-api.com/v2/design-templates',
}));
// Upload image — `method` / `url` / `data` are all supported
setAPI('uploadImage', () => ({
method: 'POST',
url: 'https://your-api.com/assets/upload',
}));
Overridable API keys: fontList, templateList, templateDetail, uploadImage, psdParse, createRecord, updateRecord, recordDetail, resourcesDetail, teamFontList, inpaint.
5.3 Customize background color presets
import { setBackgroundColorsPreset } from '@ydesign/react-editor/side-panel/background-panel';
setBackgroundColorsPreset(['#ffffff', '#000000', '#f5f5f5', '#ff4d4f', '#1677ff', '#52c41a']);
5.4 Resource loading timeouts
Useful when loading large images or custom fonts from slow networks:
import { setAssetLoadTimeout, setFontLoadTimeout, setFontLoadTimeoutCallback } from '@ydesign/react-editor';
setAssetLoadTimeout(30_000); // 30s
setFontLoadTimeout(15_000);
setFontLoadTimeoutCallback(msg => message.warning(msg));
6. Data loading & export
The store is your data boundary. Loading, saving, exporting, and collaboration all happen through it.
6.1 Load a design from your backend
const json = await fetch('/api/designs/42').then(r => r.json());
store.loadJSON(json);
6.2 Save / auto-sync
import { reaction } from 'mobx';
// Debounce-sync any state change back to the server
let timer: any;
reaction(
() => store.toJSON(),
json => {
clearTimeout(timer);
timer = setTimeout(() => {
fetch('/api/designs/42', {
method: 'PUT',
body: JSON.stringify(json),
});
}, 1000);
}
);
6.3 Export image / PDF
Export APIs live on store / editor:
// Export the current canvas as PNG
const dataUrl = await store.toDataURL({ multiplier: 2, format: 'png' });
// Export as Blob and download
const blob = await store.toBlob({ multiplier: 2 });
ExportOptions accepts multiplier, format ('jpeg' / 'png' / 'webp'), quality, width, height, filter, and more.
7. Theming & styling
@ydesign/react-editor is built with Ant Design v6 + styled-components + Tailwind v4.
7.1 Ant Design theme (palette, radius, typography)
Wrap the editor in <ConfigProvider />:
import { ConfigProvider, theme } from 'antd';
import { DesignEditorApp } from '@ydesign/react-editor';
<ConfigProvider
theme={{
algorithm: theme.darkAlgorithm, // dark theme
token: {
colorPrimary: '#ff6a00',
borderRadius: 6,
fontFamily: 'Inter, -apple-system, sans-serif',
},
}}
>
<DesignEditorApp store={store} />
</ConfigProvider>;
7.2 Override local styles
Container class names are stable, so plain global CSS gets you quite far:
.polotno-app-container {
font-family: 'Inter', sans-serif;
}
.polotno-side-panel-tab {
color: #999;
}
.polotno-side-panel-tab.active {
color: #ff6a00;
}
8. Drop down to Core: extend canvas capabilities
If you need something the handler layer does not yet cover (for example adding a custom element type, plugging in QR codes / barcodes / charts), you can extend @ydesign/core directly.
8.1 Custom hotkeys
// Built-in hotkeys are preserved; add your own on top
store.editor?.hotkeyHandler.bind({
'ctrl+s, command+s': e => {
e.preventDefault();
saveToServer();
},
'ctrl+shift+d, command+shift+d': () => {
store.editor?.objectsHandler.duplicate();
},
});
8.2 Subscribe to canvas events
Editor extends EventManager, so you can subscribe to internal events:
store.editor?.on('object:added', obj => {
console.log('new element', obj);
});
store.editor?.on('selection:changed', ids => {
console.log('selection changed', ids);
});
8.3 Write your own Handler (advanced)
Every built-in handler extends Base, sharing four context properties: editor, state, config, and canvas. Following the same pattern, you can add your own handler to Editor and get the same lifecycle guarantees as the built-ins:
import { Base } from '@ydesign/core';
export class QRCodeHandler extends Base {
add(text: string) {
// Call your QR generation logic, then insert into the canvas
const svg = generateQRCode(text);
this.editor.objectsHandler.addSVG({ src: svg });
}
}
Further reading
- Overview — learn about the Core / UI two-layer architecture
- Editor configuration — all fields of
EditorConfigexplained - Side Panel overview
- Toolbar
- Theme
- Store API
If your use case is not covered here, please open an issue on GitHub — or better, send a PR to improve this page.