labellife-design-tool
v2.2.5
Published
A modular React design tool library with canvas manipulation, side panels, and toolbar — drop-in replacement for polotno.
Readme
labellife-design-tool
A fully featured, modular React design editor library for building label, print, and graphic design applications. Built on top of Konva.js for high-performance canvas rendering and MobX-State-Tree for reactive state management.
Table of Contents
- Features
- Installation
- Quick Start
- Architecture Overview
- Store API
- Element Types
- UI Components
- Side Panels
- Toolbar
- Input Fields
- Image Effects & Filters
- Image Masking
- Export & Download
- Configuration
- Keyboard Shortcuts
- JSON Schema
- Dependencies
- Roadmap
- License
Features
Core Editor
- Canvas Editor — Zoom, pan, drag-and-drop, multi-select, and keyboard shortcuts
- Rich Element Types — Images, text, shapes (60+ presets), lines, SVGs, and groups
- Context-Sensitive Toolbar — Adapts controls to the selected element type
- Multi-Page Support — Add, remove, reorder, and navigate between pages
- Undo/Redo — Full history stack with snapshot-based state management
- Responsive Layout — Flexible container system that adapts to any viewport
Design Capabilities
- Image Editing — Crop, flip, border, corner radius, masks (circle, triangle, star, diamond, hexagon, pentagon, heart, cloud, cross), and visual effects (blur, brightness, grayscale, sepia, warm, cold)
- Text Editing — Inline editing, 200+ Google Fonts with auto-loading, font weight/style, text decoration, alignment, letter spacing, line height, stroke, background highlight, and curved text
- Shapes Library — 60+ vector shapes including basic geometry, arrows, decorative shapes, organic blobs, and custom SVG paths
- Lines — Solid, dashed, dotted styles with configurable arrow heads
- Shadow System — Configurable blur, offset, color, and opacity on any element
- Layer Management — Reorder, lock/unlock, show/hide, delete per element
- Grouping — Group and ungroup elements with preserved relative positioning
Template & Input System
- Input Fields — Define placeholder elements (Text, Date, Integer, Image) that prompt end-users for values when a template is loaded
- Input Field Popup — Built-in sequential dialog that collects user input for each field, with full style customization
- Required Fields — Mark input fields as required to prevent skipping
- Custom Dialog Support — Replace the default popup with your own component
Import / Export
- Export — PNG, JPEG, PDF (multi-page), and JSON serialization
- Import — Load designs from JSON, import images via drag-and-drop or file picker
Extensibility
- Pluggable Panels — Extend the side panel with custom sections
- Internationalization — Deep-merge translation system with dot-notation key resolution
- Theming — Light/dark theme support
- Unit Conversion — px, mm, cm, in, pt, pc with configurable DPI
Installation
npm install labellife-design-toolPeer Dependencies
The following must be installed in your host application:
npm install react react-dom @mui/material @mui/icons-material @emotion/react @emotion/styledQuick Start
import React, { useMemo } from 'react';
import { createStore } from 'labellife-design-tool/model/store';
import { DesignContainer, SidePanelWrap, WorkspaceWrap } from 'labellife-design-tool';
import { SidePanel } from 'labellife-design-tool/side-panel';
import { Toolbar } from 'labellife-design-tool/toolbar/toolbar';
import { ZoomButtons } from 'labellife-design-tool/toolbar/zoom-buttons';
import { Workspace } from 'labellife-design-tool/canvas/workspace';
function App() {
const store = useMemo(() => {
const s = createStore();
s.setSize(800, 600);
s.addPage();
return s;
}, []);
return (
<DesignContainer style={{ width: '100vw', height: '100vh' }}>
<SidePanelWrap>
<SidePanel store={store} />
</SidePanelWrap>
<WorkspaceWrap>
<Toolbar store={store} />
<Workspace store={store} />
<ZoomButtons store={store} />
</WorkspaceWrap>
</DesignContainer>
);
}Architecture Overview
┌──────────────────────────────────────────────────────┐
│ DesignContainer │
│ ┌────────────┬─────────────────────────────────────┐│
│ │ SidePanel │ WorkspaceWrap ││
│ │ │ ┌────────────────────────────────┐ ││
│ │ • Text │ │ Toolbar (context-sensitive) │ ││
│ │ • Shapes │ ├────────────────────────────────┤ ││
│ │ • Upload │ │ Workspace (Konva Stage) │ ││
│ │ • Input │ │ │ ││
│ │ • Bg │ │ Canvas: zoom, pan, select │ ││
│ │ • Layers │ │ Elements rendered here │ ││
│ │ │ │ Input Fields Popup (optional) │ ││
│ │ │ │ │ ││
│ │ │ ├────────────────────────────────┤ ││
│ │ │ │ ZoomButtons │ ││
│ └────────────┴──┴────────────────────────────────┘ ││
└──────────────────────────────────────────────────────┘State management uses MobX-State-Tree. The store is the single source of truth — all UI components observe it reactively.
Store API
Creating a Store
import { createStore } from 'labellife-design-tool/model/store';
const store = createStore();Canvas Size
store.setSize(800, 600); // Set canvas dimensions in pixels
store.setUnit({ unit: 'mm', dpi: 300 }); // Set working unit and DPIPage Management
const page = store.addPage(); // Add a new page (returns the page)
store.deletePages([page.id]); // Delete pages by ID
store.selectPage(page.id); // Set active page
store.selectPages([id1, id2]); // Select multiple pages
store.setPageZIndex(page.id, 0); // Reorder pages
store.activePage; // Currently active page
store.pages; // All pagesElement Management
// Add elements to the active page
store.activePage.addElement({
type: 'text',
text: 'Hello World',
x: 100, y: 100,
fontSize: 32,
fontFamily: 'Roboto',
});
store.activePage.addElement({
type: 'image',
src: 'https://example.com/photo.jpg',
x: 50, y: 50,
width: 400, height: 300,
});
// Selection
store.selectElements([elementId]); // Select elements by ID
store.selectedElements; // Array of selected elements
// Deletion
store.deleteElements([elementId]); // Delete elements by ID
// Grouping
store.groupElements([id1, id2]); // Group elements together
store.ungroupElements([groupId]); // Ungroup a group
// Find
store.getElementById(id); // Find element by ID across all pages
store.find(callback); // Find first element matching callbackElement Actions
Every element supports:
element.set({ x: 200, opacity: 0.5 }); // Update properties
element.clone(); // Duplicate the element
element.moveUp(); // Move up in layer order
element.moveDown(); // Move down in layer order
element.moveTop(); // Move to front
element.moveBottom(); // Move to back
element.setZIndex(3); // Set explicit z-index
element.toJSON(); // Serialize to plain objectHistory (Undo / Redo)
store.history.undo();
store.history.redo();
store.history.canUndo; // Boolean
store.history.canRedo; // Boolean
store.history.clear(); // Reset history
store.history.addUndoState(); // Snapshot current state
store.history.ignore(() => { ... }); // Batch changes without recordingSerialization
const json = store.toJSON(); // Export design as JSON object
store.loadJSON(json); // Load design from JSON
store.loadJSON(jsonString); // Also accepts a JSON string
store.clear(); // Reset store to empty state
store.validate(json); // Returns array of validation errorsNote:
loadJSONautomatically loads referenced Google Fonts and triggers the Input Fields popup if the template contains input field elements.
Events
const dispose = store.on('change', (data) => {
console.log('Store changed', data);
});
dispose(); // Remove listenerFont Management
store.loadFont('Open Sans'); // Load a Google Font by name
store.addFont({ fontFamily: 'Custom', url: '...' });
store.removeFont('Custom');Element Types
Image
| Property | Type | Default | Description |
|---|---|---|---|
| src | string | '' | Image URL |
| cropX, cropY | number | 0 | Crop origin (0–1 normalized) |
| cropWidth, cropHeight | number | 1 | Crop size (0–1 normalized) |
| cornerRadius | number | 0 | Rounded corners in pixels |
| flipX, flipY | boolean | false | Horizontal/vertical flip |
| clipSrc | string | '' | Mask shape identifier |
| borderColor | string | '' | Border color (hex) |
| borderSize | number | 0 | Border width in pixels |
| keepRatio | boolean | true | Lock aspect ratio on resize |
Text
| Property | Type | Default | Description |
|---|---|---|---|
| text | string | '' | Text content |
| fontSize | number | 20 | Font size in pixels |
| fontFamily | string | 'Roboto' | Font family name |
| fontWeight | string/number | 'normal' | Font weight (100–900 or keyword) |
| fontStyle | string | 'normal' | 'normal' or 'italic' |
| textDecoration | string | '' | 'underline', 'line-through', etc. |
| fill | string | '#000000' | Text color |
| align | string | 'center' | 'left', 'center', 'right' |
| verticalAlign | string | 'top' | 'top', 'middle', 'bottom' |
| stroke | string | '' | Text stroke color |
| strokeWidth | number | 0 | Text stroke width |
| lineHeight | number | 1.2 | Line height multiplier |
| letterSpacing | number | 0 | Letter spacing in pixels |
| backgroundEnabled | boolean | false | Enable text background highlight |
| backgroundColor | string | '#ffffff' | Background color |
| backgroundOpacity | number | 1 | Background opacity (0–1) |
| backgroundCornerRadius | number | 0 | Background corner radius |
| backgroundPadding | number | 0 | Background padding |
| curveEnabled | boolean | false | Enable curved text |
| curvePower | number | 0 | Curve intensity |
Figure (Shape)
| Property | Type | Default | Description |
|---|---|---|---|
| subType | string | 'rect' | Shape type (see list below) |
| fill | string | '#BFBFBF' | Fill color |
| stroke | string | '' | Stroke color |
| strokeWidth | number | 0 | Stroke width |
| cornerRadius | number | 0 | Corner radius (rect only) |
| pathData | string | '' | Custom SVG path data |
| dash | number[] | [] | Dash pattern |
Built-in shape subtypes: rect, circle, triangle, star, diamond, hexagon, polygon, path
60+ preset shapes available in the Elements panel, including: arrows, speech bubble, cross, cloud, egg, heart, house, shield, lightning, flower, sun, hourglass, leaf, drop, wave, and many more.
Line
| Property | Type | Default | Description |
|---|---|---|---|
| color | string | '#000000' | Line color |
| dash | number[] | [] | Dash pattern (e.g. [10, 5]) |
| startHead | string | '' | Start head: '', 'arrow', 'circle', 'square' |
| endHead | string | '' | End head: '', 'arrow', 'circle', 'square' |
Common Properties (All Elements)
| Property | Type | Default | Description |
|---|---|---|---|
| x, y | number | 0 | Position |
| width, height | number | 100 | Dimensions |
| rotation | number | 0 | Rotation in degrees |
| opacity | number | 1 | Opacity (0–1) |
| visible | boolean | true | Visibility |
| draggable | boolean | true | Can be dragged |
| resizable | boolean | true | Can be resized |
| contentEditable | boolean | true | Content is editable |
| selectable | boolean | true | Can be selected |
| removable | boolean | true | Can be deleted |
| name | string | '' | Display name (shown in layers) |
| custom | object | {} | Arbitrary custom data (preserved through serialization) |
Shadow (All Elements)
| Property | Type | Default | Description |
|---|---|---|---|
| shadowEnabled | boolean | false | Enable drop shadow |
| shadowBlur | number | 5 | Shadow blur radius |
| shadowOffsetX | number | 0 | Horizontal shadow offset |
| shadowOffsetY | number | 0 | Vertical shadow offset |
| shadowColor | string | 'black' | Shadow color |
| shadowOpacity | number | 1 | Shadow opacity |
UI Components
Layout
import { DesignContainer, SidePanelWrap, WorkspaceWrap } from 'labellife-design-tool';| Component | Description |
|---|---|
| DesignContainer | Root layout container. Accepts style prop for dimensions. |
| SidePanelWrap | Wrapper for the side panel area. |
| WorkspaceWrap | Flex container for toolbar, canvas, and zoom controls. |
Workspace
import { Workspace } from 'labellife-design-tool/canvas/workspace';
<Workspace store={store} />| Prop | Type | Default | Description |
|---|---|---|---|
| store | Store | required | The MobX-State-Tree store instance |
| showInputFieldsPopup | boolean | true | Show the input fields popup when a template with input fields is loaded. Set to false to disable. |
| onInputFieldsComplete | function | undefined | Callback fired after the user has completed (submitted or skipped) all input fields. Only called when the popup is enabled. |
| components | object | {} | Override slots (e.g. { PageControls: MyComponent }) |
The workspace provides:
- Infinite canvas with zoom and pan (mouse wheel + drag)
- Click selection and multi-select (Shift+click)
- Transform handles for resize and rotation
- Auto-fit to container on mount
- Background rendering per page
- Input fields popup (automatically shown when a loaded template contains input fields)
Zoom Buttons
import { ZoomButtons } from 'labellife-design-tool/toolbar/zoom-buttons';
<ZoomButtons store={store} />Provides zoom in, zoom out, and fit-to-screen controls.
Side Panels
Default Configuration
import { SidePanel } from 'labellife-design-tool/side-panel';
<SidePanel store={store} />By default, the side panel includes: Text, Elements, Upload, Background, and Layers.
Custom Section Order
import {
SidePanel,
TextSection,
ElementsSection,
UploadSection,
InputFieldsSection,
BackgroundSection,
LayersSection,
} from 'labellife-design-tool/side-panel';
<SidePanel
store={store}
sections={[
TextSection,
ElementsSection,
UploadSection,
InputFieldsSection,
BackgroundSection,
LayersSection,
]}
defaultSection="text"
/>Built-in Sections
| Section | Tab Name | Description |
|---|---|---|
| TextSection | Text | Add heading, subheading, and body text presets |
| ElementsSection | Elements | 60+ shapes and 6 line styles |
| UploadSection | Upload | Drag-and-drop or file picker for images |
| InputFieldsSection | Input Fields | Add placeholder input elements (Text, Date, Integer, Image) for template-based workflows |
| BackgroundSection | Background | Color picker with 22 preset colors |
| LayersSection | Layers | Layer list with visibility, lock, reorder, and delete |
Creating Custom Sections
import { SectionTab } from 'labellife-design-tool/side-panel';
const MyCustomSection = {
name: 'my-panel',
Tab: (props) => (
<SectionTab name="My Panel" {...props}>
<MyIcon />
</SectionTab>
),
Panel: ({ store }) => (
<div>
<h3>My Custom Panel</h3>
{/* Your custom panel content */}
</div>
),
};
<SidePanel store={store} sections={[MyCustomSection, TextSection, LayersSection]} />Section API:
| Property | Type | Description |
|---|---|---|
| name | string | Unique identifier |
| Tab | Component | Receives { onClick, active } — renders the sidebar tab |
| Panel | Component | Receives { store } — renders the panel content |
| visibleInList | boolean | Set to false to hide the tab (panel still accessible programmatically) |
Toolbar
import { Toolbar } from 'labellife-design-tool/toolbar/toolbar';
<Toolbar store={store} />The toolbar is context-sensitive — it automatically shows relevant controls based on the selected element type:
| Element Type | Available Controls | |---|---| | Image | Flip (H/V), Effects, Fit to page, Apply/Remove mask, Crop | | Text | Font family, Font size, Bold, Italic, Underline, Alignment, Fill color, Stroke | | Figure | Fill color, Stroke color, Border width, Corner radius | | Line | Line color | | All | Opacity, Move up/down, Lock/Unlock, Duplicate, Delete |
The toolbar includes built-in Import and Download buttons by default.
Customizing Toolbar Buttons
<Toolbar
store={store}
components={{
// Add custom action buttons
ActionControls: ({ store }) => (
<button onClick={() => store.saveAsImage()}>Export</button>
),
// Replace or hide the Import button
ImportButton: null, // Set to null to hide
// Replace or hide the Download button
DownloadButton: MyDownload, // Provide a custom component
}}
/>Input Fields
Input fields allow template designers (admins) to define placeholder elements that prompt end-users for values when a template is loaded. This is useful for creating reusable templates where certain fields — such as a name, date, or quantity — need to be filled in by the user.
Overview
The input fields system consists of three parts:
- Admin Panel (
InputFieldsSection) — A side panel where the admin adds input field elements to the template and configures their prompt text and whether they are required. - Metadata — Each input field element stores its configuration in the
customproperty, which is preserved through JSON serialization. - User Popup — When a template containing input fields is loaded via
store.loadJSON(), a sequential dialog automatically prompts the user to fill in each field.
Supported Input Types
| Type | Description | Input Control |
|---|---|---|
| text | Free-form text entry | Text input |
| date | Date value with configurable format | Date picker |
| integer | Whole number value | Number input (integers only) |
| image | Image upload placeholder | File picker with preview |
Adding Input Fields (Admin Side)
Include InputFieldsSection in your side panel sections:
import { InputFieldsSection } from 'labellife-design-tool/side-panel';
<SidePanel
store={store}
sections={[TextSection, ElementsSection, InputFieldsSection, LayersSection]}
/>For each input type, the admin can configure:
- Prompt Text — The message displayed to the end-user (e.g. "Enter your name", "Select the event date").
- Required — Whether the field must be filled in. If required, the Skip button is hidden and the Confirm button is disabled until a value is entered.
- Date Format (date type only) — Choose from
MM-DD-YYYY,Month Name-DD-YYYY,Month Name-DD,YYYY, orMM-DD.
Input Field Metadata
Each input field element stores the following in its custom property:
{
"inputField": true,
"inputType": "text",
"placeholder": "Enter text",
"promptText": "Please enter your name",
"required": false
}For date fields, an additional dateFormat property is included:
{
"inputField": true,
"inputType": "date",
"placeholder": "Select date",
"promptText": "When is the event?",
"required": true,
"dateFormat": "MM-DD-YYYY"
}Controlling the Popup
The input fields popup is rendered inside the <Workspace> component and is enabled by default. When store.loadJSON() is called with a template that contains input field elements, the popup automatically appears.
// Popup enabled (default) — dialog appears after loadJSON if input fields exist
<Workspace store={store} />
// Popup disabled — input fields retain their template placeholder values
<Workspace store={store} showInputFieldsPopup={false} />
// With completion callback
<Workspace
store={store}
onInputFieldsComplete={() => {
console.log('All input fields have been filled!');
// e.g. navigate, save, show a toast, etc.
}}
/>When showInputFieldsPopup is set to false, all input fields are treated as if they were skipped — their original template values are preserved regardless of whether they are marked as required.
Customizing the Popup Appearance
Use setInputFieldsConfig to override the default dialog styles:
import { setInputFieldsConfig } from 'labellife-design-tool';
setInputFieldsConfig({
// Style overrides (applied via MUI sx prop)
dialogStyle: { borderRadius: 16 },
titleStyle: { color: '#1a1a1a', fontFamily: 'Georgia' },
promptStyle: { fontSize: 15 },
inputStyle: { borderRadius: 8 },
submitButtonStyle: { backgroundColor: '#4caf50', '&:hover': { backgroundColor: '#388e3c' } },
skipButtonStyle: { color: '#757575' },
// Button text overrides
submitButtonText: 'Apply',
skipButtonText: 'Leave as is',
});Providing Custom Dialog Components
You can replace the built-in popup with your own component — either globally for all types, or per input type.
Per-type custom dialogs (override specific types only):
import { setInputFieldsConfig } from 'labellife-design-tool';
setInputFieldsConfig({
CustomTextDialog: MyTextPopup, // Used for text fields
CustomDateDialog: MyDatePopup, // Used for date fields
CustomIntegerDialog: MyNumberPopup, // Used for integer fields
CustomImageDialog: MyImagePopup, // Used for image fields
});Global custom dialog (catches any type without a per-type override):
setInputFieldsConfig({
CustomDialog: MyGenericPopup, // Fallback for all types
});Mix and match — per-type takes priority over global:
setInputFieldsConfig({
CustomImageDialog: MyImagePopup, // Only image fields use this
CustomDialog: MyGenericPopup, // Text, date, and integer use this
});Resolution order: CustomTextDialog / CustomDateDialog / CustomIntegerDialog / CustomImageDialog > CustomDialog > built-in dialog.
Note: When
showInputFieldsPopup={false}is set on<Workspace>, no popup is shown regardless of any custom dialog configuration.
Props passed to every custom dialog component:
| Prop | Type | Description |
|---|---|---|
| field | object | The current input field element (MST node with id, custom, etc.) |
| fields | array | All pending input field elements |
| currentIndex | number | Zero-based index of the current field |
| totalCount | number | Total number of fields to process |
| onSubmit(elementId, value) | function | Call to set the value and advance to the next field |
| onSkip(elementId) | function | Call to skip the field and advance |
| onComplete() | function | Call when all fields are processed |
Example custom dialog:
const MyImagePopup = ({ field, currentIndex, totalCount, onSubmit, onSkip }) => {
const promptText = field.custom?.promptText || 'Upload an image';
const handleFile = (e) => {
const file = e.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (ev) => onSubmit(field.id, ev.target.result);
reader.readAsDataURL(file);
};
return (
<div className="my-dialog">
<h3>{promptText}</h3>
<p>Field {currentIndex + 1} of {totalCount}</p>
<input type="file" accept="image/*" onChange={handleFile} />
{!field.custom?.required && (
<button onClick={() => onSkip(field.id)}>Skip</button>
)}
</div>
);
};Accessing Input Fields Programmatically
// Get all input field elements across all pages
const fields = store.inputFields;
// Get pending fields awaiting user input
const pending = store._pendingInputFields;
// Manually trigger the input fields flow (called automatically by loadJSON)
store.requestInputFields();
// Clear all pending fields
store.clearPendingInputFields();Image Effects & Filters
The effects panel is accessible via the Effects button in the image toolbar.
Presets
| Preset | Description | |---|---| | Natural | No filters (reset all) | | Grayscale | Desaturate to black & white | | Sepia | Warm vintage tone | | Warm | Boost red/green, reduce blue | | Cold | Boost blue/green, reduce red |
Adjustable Effects
| Effect | Property | Range | Description |
|---|---|---|---|
| Blur | blurEnabled, blurRadius | 0–100 | Gaussian blur |
| Brightness | brightnessEnabled, brightness | -1 to 1 | Lighten or darken |
Border & Corner Radius
| Property | Description |
|---|---|
| borderColor | Border color (hex string) |
| borderSize | Border width in pixels (0–50) |
| cornerRadius | Rounded corners in pixels (0–200) |
Shadow
Configurable shadow with blur, offset X/Y, and color — accessible from the effects panel.
Image Masking
The mask panel is accessible via the Apply Mask button in the image toolbar. Masks clip the image to a predefined shape.
Available mask shapes: Circle, Triangle, Star, Diamond, Hexagon, Pentagon, Heart, Cloud, Cross
To remove a mask, click Remove Mask in the mask panel.
Programmatically:
element.set({ clipSrc: 'circle' }); // Apply mask
element.set({ clipSrc: '' }); // Remove maskExport & Download
Image Export
const dataURL = await store.toDataURL({
mimeType: 'image/png', // 'image/png' or 'image/jpeg'
quality: 1, // 0–1 (JPEG quality)
pixelRatio: 2, // Resolution multiplier
});
const blob = await store.toBlob({ mimeType: 'image/png' });
await store.saveAsImage({ fileName: 'my-design.png' });PDF Export
const pdfDataURL = await store.toPDFDataURL({ pixelRatio: 2, dpi: 300 });
await store.saveAsPDF({ fileName: 'my-design.pdf' });JSON Export
const json = store.toJSON();
store.saveAsJSON({ fileName: 'my-design.json' });
store.loadJSON(json);Configuration
Internationalization (i18n)
The library supports full internationalization via a nested translation system with dot-notation key resolution.
import { setTranslations, t } from 'labellife-design-tool/config';
setTranslations({
toolbar: {
flip: 'Spiegelen',
effects: 'Effecten',
download: 'Downloaden',
import: 'Importeren',
undo: 'Ongedaan maken',
redo: 'Opnieuw',
// ... see full key reference below
},
sidePanel: {
text: 'Tekst',
elements: 'Elementen',
upload: 'Uploaden',
background: 'Achtergrond',
layers: 'Lagen',
inputFields: {
tab: 'Invoervelden',
title: 'Invoervelden',
required: 'Verplicht',
// ...
},
},
inputFieldsDialog: {
title: 'Invoer vereist',
submit: 'Bevestigen',
skip: 'Overslaan',
stepIndicator: 'Veld {current} van {total}',
},
});
// Translations are deep-merged — safe to call multiple times
setTranslations({ toolbar: { flip: 'Flip' } });
setTranslations({ toolbar: { effects: 'Effects' } }); // Does not overwrite toolbar.flip
// Use the translation function anywhere
t('toolbar.flip'); // Returns translated value
t('toolbar.flip', 'Flip'); // Returns default if key not foundtoolbar.flip, toolbar.flipHorizontally, toolbar.flipVertically, toolbar.effects,
toolbar.fitToBackground, toolbar.clip, toolbar.removeMask, toolbar.crop, toolbar.fill,
toolbar.textStroke, toolbar.border, toolbar.cornerRadius, toolbar.color, toolbar.opacity,
toolbar.up, toolbar.down, toolbar.lockedDescription, toolbar.unlockedDescription,
toolbar.duplicateElements, toolbar.removeElements, toolbar.download, toolbar.import,
toolbar.fileType, toolbar.quality, toolbar.undo, toolbar.redo, toolbar.blur,
toolbar.brightness, toolbar.shadow, toolbar.offsetX, toolbar.offsetY,
toolbar.group, toolbar.ungroup
sidePanel.text, sidePanel.elements, sidePanel.shapes, sidePanel.lines,
sidePanel.upload, sidePanel.uploadTip, sidePanel.background, sidePanel.layers,
sidePanel.noLayers, sidePanel.headerText, sidePanel.subHeaderText, sidePanel.bodyText,
sidePanel.inputFields.tab, sidePanel.inputFields.title, sidePanel.inputFields.description,
sidePanel.inputFields.textInput, sidePanel.inputFields.dateInput,
sidePanel.inputFields.integerInput, sidePanel.inputFields.imageInput,
sidePanel.inputFields.required, sidePanel.inputFields.promptTextHint,
sidePanel.inputFields.dateFormat
inputFieldsDialog.title, inputFieldsDialog.submit, inputFieldsDialog.skip,
inputFieldsDialog.stepIndicatorTheming
import { setTheme, getTheme } from 'labellife-design-tool/config';
setTheme('light'); // or 'dark'Input Fields Dialog Configuration
See Input Fields — Customizing the Popup Appearance for full details.
import { setInputFieldsConfig, getInputFieldsConfig } from 'labellife-design-tool';
setInputFieldsConfig({
dialogStyle: {}, // MUI sx overrides for the Dialog paper
titleStyle: {}, // MUI sx overrides for the Dialog title
promptStyle: {}, // MUI sx overrides for the prompt text
inputStyle: {}, // MUI sx overrides for the input field
submitButtonStyle: {}, // MUI sx overrides for the Confirm button
skipButtonStyle: {}, // MUI sx overrides for the Skip button
submitButtonText: '', // Override Confirm button label
skipButtonText: '', // Override Skip button label
CustomDialog: null, // Custom dialog for all types (global fallback)
CustomTextDialog: null, // Custom dialog for text fields only
CustomDateDialog: null, // Custom dialog for date fields only
CustomIntegerDialog: null,// Custom dialog for integer fields only
CustomImageDialog: null, // Custom dialog for image fields only
});Keyboard Shortcuts
| Shortcut | Action |
|---|---|
| Delete / Backspace | Delete selected elements |
| Ctrl+Z | Undo |
| Ctrl+Shift+Z / Ctrl+Y | Redo |
| Ctrl+D | Duplicate selected elements |
| Ctrl+A | Select all elements on the page |
| Escape | Deselect all |
| Mouse wheel | Zoom in/out |
JSON Schema
The design JSON follows this structure:
{
"width": 800,
"height": 600,
"unit": "px",
"dpi": 72,
"schemaVersion": 1,
"fonts": [
{ "fontFamily": "Open Sans", "url": "...", "styles": [] }
],
"pages": [
{
"id": "page-1",
"background": "#ffffff",
"children": [
{
"id": "el-1",
"type": "text",
"x": 100,
"y": 100,
"width": 300,
"height": 60,
"text": "Hello World",
"fontSize": 32,
"fontFamily": "Roboto",
"fill": "#000000",
"custom": {}
},
{
"id": "el-2",
"type": "text",
"x": 200,
"y": 200,
"width": 250,
"height": 40,
"text": "Sample Text",
"fontSize": 20,
"custom": {
"inputField": true,
"inputType": "text",
"placeholder": "Enter text",
"promptText": "What is your name?",
"required": true
}
}
]
}
]
}Dependencies
Peer Dependencies
These must be installed in your host application:
| Package | Version |
|---|---|
| react | ^17.0.0 || ^18.0.0 || ^19.0.0 |
| react-dom | ^17.0.0 || ^18.0.0 || ^19.0.0 |
| @mui/material | ^5.0.0 || ^6.0.0 |
| @mui/icons-material | ^5.0.0 || ^6.0.0 |
| @emotion/react | ^11.0.0 |
| @emotion/styled | ^11.0.0 |
Bundled Dependencies
These are included in the package — no need to install separately:
| Package | Purpose |
|---|---|
| konva + react-konva | Canvas rendering |
| mobx + mobx-state-tree + mobx-react-lite | State management |
| jspdf | PDF generation |
| uuid | Unique ID generation |
WordPress Integration
A ready-to-use WordPress plugin starter is included at examples/wordpress/. It provides:
- A pre-configured Vite + React app that imports the library with all peer dependencies
- A WordPress plugin PHP file with shortcode support (
[labellife_design_tool]) - Predictable build output (no hashed file names) for easy
wp_enqueue_script - A step-by-step README with setup, customization, and troubleshooting instructions
Quick Setup
# 1. Copy examples/wordpress/ into your WP plugins directory
cp -r examples/wordpress/ /path/to/wp-content/plugins/labellife-design-tool/
# 2. Install dependencies and build
cd /path/to/wp-content/plugins/labellife-design-tool/
npm install
npm run build
# 3. Activate the plugin in WP Admin → Plugins
# 4. Add [labellife_design_tool] shortcode to any pageCustomize the editor by editing src/App.jsx — same API as documented above. Rebuild with npm run build after changes.
See the full guide: examples/wordpress/README.md
Roadmap
All four input field types (Text, Date, Integer, Image) are fully implemented. Future enhancements under consideration:
- Drag-and-drop image upload in the input fields popup dialog
- URL-based image input as an alternative to file upload
License
MIT
