The excellent Unlayer Image Editor as a React.js wrapper component — crop, resize, draw, text, shapes, stickers, frames, filters, and an optional AI Assistant.
Try the live demo: react-image-editor-example.vercel.app
npm install @unlayer/react-image-editorRequires React >= 18.
import React, { useRef } from 'react';
import ImageEditor from '@unlayer/react-image-editor';
const App = () => {
const editorRef = useRef(null);
return (
<ImageEditor
ref={editorRef}
image="https://example.com/photo.jpg"
options={{ theme: 'light' }}
onSave={({ dataUrl, blob }) => {
// Persist the edited image
console.info('Saved', dataUrl.length, 'bytes');
}}
onCancel={() => console.info('Editing cancelled')}
/>
);
};The component works out of the box in React Server Components environments (e.g. Next.js App Router) — it ships with the 'use client' directive and touches the DOM only inside effects.
| Prop | Type | Description |
|---|---|---|
image |
string (required) |
Image URL or base64 data URL to edit. |
options |
ImageEditorOptions |
Editor configuration: projectId, user, features, theme, locale, translations, env, offline, licenseUrl, defaultPrompt, autoSubmitPrompt, aiAssistantOpenState. |
editorId |
string |
id for the container div. Cosmetic — the editor mounts by element reference. |
minHeight |
number | string |
Minimum height of the editor container. Defaults to 500. |
style |
CSSProperties |
Styles applied to the container div. |
onLoad |
(editor) => void |
Called with the editor instance once it is mounted. |
onSave |
({ dataUrl, blob }) => void |
Called when the user saves the edited image. |
onCancel |
() => void |
Called when the user cancels editing. |
onLoadError |
() => void |
Called when the image fails to load into the canvas (CORS, 404, decode error). |
onError |
(error: Error) => void |
Wrapper-level failures: embed script load, editor creation, or image reset. Falls back to console.error when absent. |
The ref exposes { editor } — null until the editor mounts, then an instance with:
| Method | Description |
|---|---|
getImage() |
Current canvas as a data URL (flattened), or null. |
hasChanges() |
Whether there are unsaved edits. |
reset(imageUrl?) |
Reset editor state (clears undo/redo and chat), optionally load a new image. |
updateOptions(partial) |
Update options like theme / locale at runtime. |
destroy() |
Unmount the editor (the component does this automatically on unmount). |
const dataUrl = editorRef.current?.editor?.getImage();| Change | Behavior |
|---|---|
image |
Applied via reset(newImage) — clears undo/redo history and AI chat. Rapid changes are serialized and collapse to the latest value. |
options.theme, options.locale, options.translations |
Applied via updateOptions() — no remount, editor state preserved. |
Any other options key |
Full remount — the editor is destroyed and recreated with the new configuration. |
onSave / onCancel / onLoadError / onLoad / onError |
Always call the latest handler; changing them never remounts. |
Two distinct channels:
onLoadError— the editor loaded fine, but the image couldn't be loaded into the canvas (CORS, dead URL, decode error).onError— the wrapper couldn't reach a working editor: the embed script failed to load, editor creation was rejected, or re-applying a changedimagefailed. After a CDN failure the wrapper automatically resets its loader state, so a later remount retries from scratch.
The editor ships eight tools, rendered as a tab rail beside the canvas:
| Tool | What it does |
|---|---|
crop |
Crop with rotate (90° steps), flip, and a straighten slider. |
resize |
Change the output dimensions. |
filter |
One-tap presets plus adjustment sliders (brightness, contrast, saturation, hue, blur, noise…). |
draw |
Freehand brush with color, type, and size controls. |
text |
Text layers with style presets, fonts, color/background/outline/shadow. |
shapes |
Filled/outline/gradient shape palettes with drag, resize, rotate, and styling. |
stickers |
A bundled sticker library grouped by category, with color/outline/shadow for vector sets. |
frame |
Frame presets with a size slider and color picker. |
All tools are enabled by default. Configure them through options.features.imageEditor.tools — each entry is either a boolean shorthand or { enabled?: boolean; icon?: string }:
// Hide the tools you don't want
<ImageEditor
image={url}
options={{
features: {
imageEditor: {
tools: {
draw: false,
stickers: false,
frame: { enabled: false }, // object form, same effect
},
},
},
}}
/>// Allow-list style: a minimal crop-and-filter editor
<ImageEditor
image={url}
options={{
features: {
imageEditor: {
tools: {
resize: false,
draw: false,
text: false,
shapes: false,
stickers: false,
frame: false,
// crop and filter stay enabled by default
},
},
},
}}
/>// Custom tool icon: a URL, raw <svg>…</svg> markup, or a Font Awesome name
<ImageEditor
image={url}
options={{
features: {
imageEditor: {
tools: {
crop: { icon: 'fa-crop-simple' },
text: { icon: 'https://example.com/icons/text.svg' },
},
},
},
}}
/>Two things to keep in mind:
featuresis a remount-tier option (see the table above): changing the tools config destroys and recreates the editor, discarding unsaved edits — decide the toolset before mounting rather than toggling it live.features.imageEditor: falsedisables the editing UI entirely; newer editor versions also supportfeatures.imageEditor.dock: 'left' | 'right'for the rail position and acornersentry (the rounded-corners control inside Crop) — these type-check once your@unlayer/typesversion includes them.
The editor includes an optional AI Assistant for chat-based edits. It requires a projectId from your Unlayer account with the feature enabled.
<ImageEditor
image={url}
options={{
projectId: 1234, // get from console
features: { ai: { enabled: true, assistant: true } },
}}
/>Set options.locale (bundled: en, es, fr, de, it, pt, nl, ja, ko, zh) and optionally override strings with options.translations.
Try the live demo at react-image-editor-example.vercel.app, or run it locally — a Vite-based demo lives in demo/:
cd demo
npm install
npm run devCopyright (c) 2026 Unlayer. MIT Licensed.