Drawesome
Drawesome is a drawing toolbar for React. Seven pens, an eraser, SVG and PNG export, no dependencies beyond React. Two lines to drop into an app.
I work on a lot of creative tools, and many of them benefit from the ability to draw. Annotating a screenshot, marking up a frame, circling the bit that's wrong. I kept building the same thing over and over, so I finally made it a package.
Most of the work went into the toolbar.1 It changes shape rather than swapping panels: pick a colour and the row of pens becomes the palette, open the size controls and it becomes two sliders, minimise it and it rolls up into a disc with the tool you're holding still in it. Every state morphs smoothly into the next.
Getting started
npm install drawesomeIt fills its parent container. Set a height on the wrapper.
import { Draw } from 'drawesome'
import 'drawesome/styles.css'
<div style={{ height: 480 }}>
<Draw />
</div>The tools
Each pen behaves like the thing it's named after. The pencil, pen and brush thin out the faster you move, while the fineliner and highlighter keep a constant width. The fountain pen responds to direction instead: thick one way, hairline the other. No two strokes come out quite the same.
The eraser takes away area rather than whole strokes, so you can rub out part of a line and keep the rest.
Making it yours
Drawesome is opinionated on purpose. I wanted the defaults to be the version you actually ship, so getting something that feels right doesn't require tuning anything at all. Everything below is for turning things off or moving them around.2
You can pick the tools and the order they sit in, and switch off anything you don't want:
<Draw
tools={['pencil', 'marker', 'highlighter']}
controls={{ undo: false, clear: false }}
/>theme takes light, dark or auto, which follows the reader's system setting. The bar above is on auto, so if your system is set to dark mode, the bar will be too.
placement puts the bar on an edge, and inset and align control exactly where it sits. motion is how it arrives and leaves, coming in off whichever edge it's on. draggable lets people pick it up and move it themselves. swatches replaces the palette with your own colours.
Two props change how it's drawn: look="studio" lights the tools as objects instead of shading them flat, and depth sets how much shadow and sheen the bar gets. The demo above uses studio.
The canvas is up to you. Give background a colour, or set it to transparent and the component paints nothing at all, so whatever is behind it shows through. That's how the demo above sits on graph paper: the paper is just CSS on this page.
<div className="your-paper">
<Draw background="transparent" />
</div>The bar is only as wide as the tools in it, so on a phone the default set runs off both sides of the screen. A phone has far more height than width though, so under 680px the demo stands the bar up on the left edge and trims the toolset:
<Draw
placement={narrow ? 'left' : 'bottom'}
tools={narrow ? ['pencil', 'pen', 'marker', 'highlighter', 'brush'] : undefined}
controls={narrow ? { undo: false, clear: false, opacity: false, custom: false } : undefined}
/>controls lets you switch size and opacity off separately. That matters on the vertical rail, where two sliders stacked on top of each other end up taller than the canvas, so the phone demo keeps size and drops opacity. Turn both off and the button that opens them goes too.
chrome={false} removes the toolbar entirely and leaves you the surface. DrawSurface, Toolbar and the useDrawing hook are all exported separately if you'd rather lay the pieces out yourself.
To get the drawing out, put a ref on it. toSvg() returns a string, toPng() returns a file, and download() saves either. What you get back matches exactly what was on screen, erasing included.
await draw.current.download('sketch', 'png', 2)Props
Surface
board{ w, h }element sizebackgroundstring"#ffffff"initialStrokesStroke[][]onChange(strokes) => voidclassNamestringstyleCSSPropertiesTools
toolsPenId[]all seveneraserbooleantrueink"auto" | "shared" | "per-tool""auto"swatchesstring[]18 built-inlook"classic" | "studio""classic"gaugebooleanfalseChrome
chromebooleantrueplacement"bottom" | "left" | "right""bottom"insetnumber | string20align"start" | "center" | "end""center"draggablebooleanfalsesettings"bar" | "tool""bar"controls{ color, size, opacity, undo, clear, custom, minimize }all truedepth"flat" | "soft" | "regular" | "strong""regular"motion"rise" | "none" | { in, out, duration }"rise"theme"light" | "dark" | "auto""light"tooltipsboolean | TooltipOptionstrueshortcutsbooleantruestartMinimizedbooleanfalsedrawWhenMinimizedbooleanfalseRef handle
toSvg() => stringtoPng(scale?) => Promise<Blob>download(name?, format?, scale?) => Promise<void>getStrokes() => Stroke[]setStrokes(strokes) => voidundo() => voidredo() => voidclear() => voidgetSize() => BoardEvery pen has a keyboard shortcut, shown in its tooltip. E for the eraser, [ and ] for size, ⌘Z and ⇧⌘Z for undo and redo. Hold Shift while drawing and the stroke locks to the nearest of eight directions.
Potentially awesome
It's called Drawesome, which is a lot to live up to. For now it does the thing I made it for: the next time something I'm building needs drawing, I just drop it in.
I'll let you draw your own conclusions.
Footnotes
-
Inspired by Apple's markup tools, and how far they take the skeuomorphism. Pens in a tray beat a row of icons. ↩
-
If you want to assemble a drawing tool from parts instead, tldraw or Excalidraw will suit you better. ↩