A plugin-architecture drawing area for React. Pick the tools you need (free draw, shapes, text, arrows, selection, erase, undo/redo, etc.), customize the UI, or build your own tools. Written in TypeScript.
- Lightweight and modular — import only the tools you use
- Customizable UI — style via
styles/classNamesor bring your own components - Extensible — implement custom Drawing/Action tools and serialization
- Touch and mouse support
View the full docs and interactive demos in Storybook: Docs & Demos
Short demo: video
npm install @jzohdi/react-draw
# or
yarn add @jzohdi/react-draw
# or
pnpm add @jzohdi/react-drawThe smallest setup: a free-draw canvas with no toolbars.
import { ReactDraw, freeDrawTool } from "@jzohdi/react-draw";
export default function MyComponent() {
return (
<ReactDraw
drawingTools={[freeDrawTool]}
actionTools={[]}
hideTopBar={true}
hideBottomBar={true}
/>
);
}import {
ReactDraw,
// drawing tools
selectTool,
freeDrawTool,
squareTool,
circleTool,
diamondTool,
straightLineTool,
arrowTool,
textAreaTool,
// action tools
undoTool,
redoTool,
trashTool,
duplicateTool,
bringBackTool,
bringForwardTool,
// style components
ColorStyle,
BackgroundStyle,
LineWidthStyle,
OpacityStyle,
FontSizeStyle,
// menu components
ClearAllButton,
} from "@jzohdi/react-draw";
const styleComponents = {
color: { order: 3, component: ColorStyle },
background: { order: 4, component: BackgroundStyle },
lineWidth: { order: 1, component: LineWidthStyle },
opacity: { order: 0, component: OpacityStyle },
fontSize: { order: 2, component: FontSizeStyle },
}
export default function App() {
return (
<ReactDraw
drawingTools={[
selectTool,
freeDrawTool,
squareTool,
circleTool,
diamondTool,
straightLineTool,
arrowTool,
textAreaTool,
]}
actionTools={[
undoTool,
redoTool,
trashTool,
duplicateTool,
bringBackTool,
bringForwardTool,
]}
shouldSelectAfterCreate={true}
styleComponents={styleComponents}
menuComponents={[ClearAllButton]}
>
{/* Optional children overlay inside the drawing area */}
</ReactDraw>
);
}import {
serializeObjects,
deserializeData,
serializeFreeDraw,
deserializeFreeDraw,
Serializers,
Deserializers,
freeDrawTool,
} from "@jzohdi/react-draw";
const serializers: Serializers = {
[freeDrawTool.id]: serializeFreeDraw,
};
const deserializers: Deserializers = {
[freeDrawTool.id]: deserializeFreeDraw,
};
// Save: returns a string you can persist to localStorage or a DB
const saved = serializeObjects(serializers, ctx);
// Load: restore previously saved state
deserializeData(saved, deserializers, ctx);See the Storybook guide: Serialization (Saving + Loading)
Key props on ReactDraw:
drawingTools(required): array of Drawing Tools to enable (e.g.,selectTool,freeDrawTool).actionTools(required): array of Action Tools for the bottom bar (e.g.,undoTool,redoTool).layout(optional):"default" | "fit" | { width: number|string, height: number|string }.hideTopBar,hideBottomBar(optional): hide toolbars.shouldKeepHistory(optional, default true): enable undo/redo history.shouldSelectAfterCreate(optional, default true): auto-select newly created objects.isResponsive(optional): resizes objects when the container size changes.shouldCornerResizePreserveRatio(optional): preserve aspect ratio when resizing from corners.styleComponents(optional): map of style editors to display (color, lineWidth, opacity, fontSize).menuComponents(optional): custom components rendered in the bottom menu.onLoad(optional): callback with theReactDrawContextwhen ready (good for initial load/deserialize).contextGetter(optional): receive a getter function to access the liveReactDrawContextfrom outside.styles,classNames(optional): customize built-in UI via style/className maps.id(optional, default "main"): unique identifier for the drawing area.
Types are exported from the package and documented in Storybook. For full definitions, see src/types.ts in the repo and the Type Descriptions page.
- Drawing tools render in the top toolbar by default; action tools render in the bottom toolbar.
- You can explicitly place and order tools via the
postitionproperty on a tool:
postition?: { view: "top" | "bottom"; order?: number };Note: tools without
postitiondefault to top (drawing) or bottom (action) based on their type.
Use the styles and classNames props to customize built-in UI elements. The keys correspond to constants exported by the library (e.g., toolIconWrapper, bottomToolButton, menuContainer, etc.).
const styles = {
toolIconWrapper: { "&:hover": { backgroundColor: "#eee" } },
bottomToolButton: {
'&[data-disabled="false"]:hover': { backgroundColor: "#000" },
'&[data-disabled="false"]:hover > svg path': { fill: "#fff", stroke: "#fff" },
},
}
<ReactDraw
drawingTools={[selectTool, freeDrawTool]}
actionTools={[undoTool, redoTool]}
styles={styles}
/>The bottom bar can also show style editors (styleComponents) such as color, line width, opacity, and font size.
The component is client-side. In Next.js, dynamically import it client-only:
import dynamic from "next/dynamic";
const ReactDraw = dynamic(() => import("@jzohdi/react-draw").then(m => m.ReactDraw), { ssr: false });See the Next.js example in examples/.
- How do I keep bundle size small?
- Import only the tools you need. The package ships ESM/CJS builds for tree-shaking.
- Can I add my own tools?
- Yes. Implement a
DrawingToolsorActionToolsobject. See the Storybook pages: Drawing Tools.
- Yes. Implement a
- Can I control the canvas from outside ReactDraw?
- Yes. Use
contextGetterto get a liveReactDrawContext, then call utilities likecreateCircle,createImage,selectAll, or serialization APIs. See the “External Controls” story.
- Yes. Use
- Does it support touch?
- Yes, mouse and touch are supported.
- How do I save and load?
- Use
serializeObjectsanddeserializeDatawith tool-specific (de)serializers. See Serialization.
- Use
- Can I change default styles for new objects?
- When nothing is selected, updates via style controls set defaults for future objects. When objects are selected, updates apply only to the selection.
Pull requests and issues are welcome!
We use Storybook for local development.
npm run storybookBuild the library and docs:
npm run addISC — see the License.