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 end up needing a way to draw. Annotating a screenshot, marking up a frame, circling the bit that's wrong. Rather than build it again each time, I made this.
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 behaves like the thing it's named after. The pencil, pen and brush thin out the faster you move; the fineliner and highlighter hold one width whatever you do. The fountain pen goes by 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
It's opinionated, and that's the point. The defaults are meant to be the version you ship, so you don't have to think about how it looks or feels to end up with something that looks and feels right. Everything below is turning things off or moving them around, not rebuilding it.2
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. The bar above is on auto, so it's dark if you are.
placement puts it on an edge and inset and align say exactly where. draggable lets people move it themselves. swatches replaces the palette with your own colours. look="studio" lights the tools as objects instead of shading them flat, and depth sets how physical the bar looks.
The canvas is whatever you want it to be. Give background a colour, or set it to transparent and the component paints nothing at all, so whatever is behind it shows through. The demo above is sitting on a bit of graph paper drawn in CSS.
<div className="your-paper">
<Draw background="transparent" />
</div>The bar is as wide as what's in it, and a phone has more height than width. So on a small screen, stand it up and drop a few tools. The demo does this under 680px:
<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 takes size and opacity separately, so a rail can keep one without the other stacked under it. 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.
Put a ref on it to get the drawing out. toSvg() gives you a string, toPng() a file, download() saves either. What you get back is exactly what was on screen, erasing and all.
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"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.
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. ↩