Drawing & erasing
Switch between drawing and erasing from your own toolbar.
Most drawing tools need at least two modes: adding ink and removing it. ReactSketchCanvas exposes that switch through its ref, so your toolbar can stay in React while the canvas handles pointer input.
Switching modes
Call eraseMode(true) on the canvas ref to start erasing, and eraseMode(false) to return to drawing. Keep the active mode in component state so your toolbar can render the correct button as selected and show the right width control.
import { Eraser, Pencil, Settings2 } from "lucide-react";import { type ChangeEvent, useRef, useState } from "react";import { type EraserMode, ReactSketchCanvas, type ReactSketchCanvasRef,} from "react-sketch-canvas";export default function App() { const canvasRef = useRef<ReactSketchCanvasRef>(null); const [eraseMode, setEraseMode] = useState(false); const [eraserMode, setEraserMode] = useState<EraserMode>("mask"); const [strokeWidth, setStrokeWidth] = useState(5); const [eraserWidth, setEraserWidth] = useState(10); const handleEraserClick = () => { setEraseMode(true); canvasRef.current?.eraseMode(true); }; const handlePenClick = () => { setEraseMode(false); canvasRef.current?.eraseMode(false); }; const handleStrokeWidthChange = (event: ChangeEvent<HTMLInputElement>) => { setStrokeWidth(+event.target.value); }; const handleEraserWidthChange = (event: ChangeEvent<HTMLInputElement>) => { setEraserWidth(+event.target.value); }; const handleEraserModeChange = (mode: EraserMode) => { if (!eraseMode) { return; } setEraserMode(mode); }; return ( <div className="not-prose flex flex-col gap-4 w-full"> {/* Drafting Table Tool Panel */} <div className="flex flex-col md:flex-row md:items-center justify-between gap-4 p-4 rounded-lg border border-fd-border bg-fd-card shadow-sm text-fd-foreground"> {/* Tool Selectors */} <div className="inline-flex flex-col gap-2"> <span className="text-xs font-semibold uppercase tracking-wider text-fd-muted-foreground"> Active Tool </span> <div className="inline-flex rounded-md p-1 bg-fd-muted border border-fd-border w-fit"> <button type="button" onClick={handlePenClick} className={`inline-flex items-center gap-2 px-3 py-1.5 rounded-md text-xs font-medium transition-all duration-200 ${ !eraseMode ? "bg-fd-primary text-fd-primary-foreground shadow-sm" : "text-fd-muted-foreground hover:text-fd-foreground hover:bg-fd-accent/50" }`} > <Pencil className="w-3.5 h-3.5" /> Pen </button> <button type="button" onClick={handleEraserClick} className={`inline-flex items-center gap-2 px-3 py-1.5 rounded-md text-xs font-medium transition-all duration-200 ${ eraseMode ? "bg-fd-primary text-fd-primary-foreground shadow-sm" : "text-fd-muted-foreground hover:text-fd-foreground hover:bg-fd-accent/50" }`} > <Eraser className="w-3.5 h-3.5" /> Eraser </button> </div> </div> {/* Stroke & Eraser Width Sliders */} <div className="flex flex-1 flex-col sm:flex-row gap-4"> {/* Stroke Width Slider */} <div className={`flex flex-col flex-1 gap-2 transition-opacity duration-200 ${eraseMode ? "opacity-40" : "opacity-100"}`} > <div className="flex justify-between items-center text-xs font-semibold uppercase tracking-wider text-fd-muted-foreground"> <span htmlFor="strokeWidth">Stroke Width</span> <span className="font-mono text-fd-foreground bg-fd-muted border border-fd-border px-1.5 py-0.5 rounded text-[10px]"> {strokeWidth}px </span> </div> <input disabled={eraseMode} type="range" min="1" max="20" step="1" id="strokeWidth" value={strokeWidth} onChange={handleStrokeWidthChange} className="w-full accent-fd-primary cursor-pointer disabled:cursor-not-allowed" /> </div> {/* Eraser Width Slider */} <div className={`flex flex-col flex-1 gap-2 transition-opacity duration-200 ${!eraseMode ? "opacity-40" : "opacity-100"}`} > <div className="flex justify-between items-center text-xs font-semibold uppercase tracking-wider text-fd-muted-foreground"> <span htmlFor="eraserWidth">Eraser Width</span> <span className="font-mono text-fd-foreground bg-fd-muted border border-fd-border px-1.5 py-0.5 rounded text-[10px]"> {eraserWidth}px </span> </div> <input disabled={!eraseMode} type="range" min="1" max="20" step="1" id="eraserWidth" value={eraserWidth} onChange={handleEraserWidthChange} className="w-full accent-fd-primary cursor-pointer disabled:cursor-not-allowed" /> </div> </div> {/* Eraser Mode Toggles */} <div className="inline-flex flex-col gap-2"> <span className="flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wider text-fd-muted-foreground"> <Settings2 className="w-3.5 h-3.5" /> Eraser Mode </span> <div className="inline-flex w-fit rounded-md border border-fd-border bg-fd-muted p-1"> <button type="button" disabled={!eraseMode} aria-pressed={eraserMode === "mask"} onClick={() => handleEraserModeChange("mask")} className="inline-flex items-center rounded-md px-2 py-1.5 text-xs font-medium transition-all duration-200 hover:bg-fd-accent aria-pressed:bg-fd-primary aria-pressed:text-fd-primary-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fd-primary disabled:cursor-not-allowed disabled:opacity-45 disabled:hover:bg-transparent" > Mask </button> <button type="button" disabled={!eraseMode} aria-pressed={eraserMode === "stroke"} onClick={() => handleEraserModeChange("stroke")} className="inline-flex items-center rounded-md px-2 py-1.5 text-xs font-medium transition-all duration-200 hover:bg-fd-accent aria-pressed:bg-fd-primary aria-pressed:text-fd-primary-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fd-primary disabled:cursor-not-allowed disabled:opacity-45 disabled:hover:bg-transparent" > Stroke </button> </div> </div> </div> {/* Canvas Workspace */} <div className="relative overflow-hidden rounded-lg border border-fd-border bg-fd-card aspect-video min-h-[240px] shadow-sm"> <ReactSketchCanvas ref={canvasRef} eraserMode={eraserMode} strokeWidth={strokeWidth} eraserWidth={eraserWidth} strokeColor="var(--color-fd-primary)" canvasColor="transparent" /> </div> </div> );}export function () {
const = <ReactSketchCanvasRef>(null);
const [, ] = <"draw" | "erase">("draw");
return (
<>
< ={} />
<
="button"
={() => {
.?.(false);
("draw");
}}
={ === "draw"}
>
Draw
</>
<
="button"
={() => {
.?.(true);
("erase");
}}
={ === "erase"}
>
Erase
</>
</>
);
}Eraser mode: mask vs stroke
The eraserMode prop controls how eraser gestures are stored.
eraserMode="mask"(default) stores each eraser gesture as a mask path. The original strokes are not changed, so undo and redo replay the exact visual state.eraserMode="stroke"deletes any drawing stroke the eraser touches. There is no eraser path in the saved state.
mask is the default for backward compatibility. Pick stroke only when you want erased strokes to be removed from the path list; for example, when the result will be re-serialized and edited later.
export function () {
const [, ] = <>("mask");
return (
<>
< eraserMode={} /> <
="Eraser mode"
={}
={() => (.. as )}
>
< ="mask">Mask</>
< ="stroke">Stroke</>
</>
</>
);
}Stroke width
strokeWidth and eraserWidth are independent. Expose two sliders if your toolbar lets users change both. If you only show one width control, swap which prop it drives based on the current mode.
See also
- Colors & theming: change stroke and canvas colors.
- History: undo, redo, clear, and reset.