# drawesome: 27 props, one eraser that never reaches the stroke data, and a manifest with no version field

> A React drawing toolbar with seven velocity sensitive instruments, 27 documented props and three export calls whose signatures do not agree. Reading the props tables against the README's own examples and the root package.json turns up five checkable defects, including an eraser that survives only in pixels and a monorepo whose dev script starts an app the README never mentions.

**benjitaylor/drawesome** — Drawing toolbar for React. 

- Repository: https://github.com/benjitaylor/drawesome
- Stars: 326 · Forks: 33
- Language: TypeScript
- License: MIT
- Published: 2026-09-18 · Updated: 2026-09-18 · Language: en
- Canonical page: https://hysenlabs.com/projects/benjitaylor-drawesome

## One install line, two identical npm links, and a logo that only resolves in dark mode

The install section is one line, `npm install drawesome`, followed by the only stated requirement: peer dependency `react >=18`. Nothing states a minimum Node version, nothing names a package manager, and nothing warns that the stylesheet is a separate side-effect import even though the quick start puts `import 'drawesome/styles.css'` on its own line after the component import.

Above that, the file opens with a `<picture>` element that declares exactly one child source, `media="(prefers-color-scheme: dark)"` pointing at `logo-dark.svg`. There is no second source for the light case and no `<img>` fallback underneath it. The repository root holds both `logo.svg` and `logo-dark.svg`, so the light asset exists in the tree while nothing in the README ever loads it. A reader on a light system reaches the heading through an empty image box.

Directly under the `<br>` sit two markdown links to `https://www.npmjs.com/package/drawesome`, one immediately after the other, both with empty link text. That is the entire badge row: no build status, no bundle size, no licence badge, and the same destination twice.

The opening line claims no dependencies beyond React. The repository root also carries `pnpm-lock.yaml` and `pnpm-workspace.yaml`, and every script in the root manifest is a pnpm filter. So the package consumer reaches npm while the repository reader reaches pnpm first, and the README documents only the first half of that split.

## Six pens named in prose, seven in the default, and a `marker` id that appears only in samples

The tools paragraph names six writing instruments: pencil, pen, brush, fineliner, highlighter, fountain pen. Add the eraser, which gets its own paragraph, and the count reaches seven. The `tools` prop defaults to all seven and is typed `PenId[]`, so the number in the default includes the eraser, while the same row's description says only which pens appear and in what order. Availability is also controlled separately by `eraser`, a boolean defaulting to true. A caller who writes `tools={['pencil','pen']}` and leaves `eraser` alone has now expressed the eraser twice, and a caller who writes `eraser={false}` still has it sitting inside the default array.

Velocity is the model for three of the six. Pencil, pen and brush thin out the faster the pointer moves. Fineliner and highlighter hold one width whatever you do. The fountain pen goes by direction instead, thick one way and hairline the other. No threshold, width range, or sampling window is given for any of them, so nothing written down separates the pencil from the pen from the brush. The closing sentence, that no two strokes come out quite the same, describes the intent rather than a value you can pin.

Two configuration samples name a tool id the prose never defines. Both use `marker`: once in `tools={['pencil', 'marker', 'highlighter']}` and again in `tools={narrow ? ['pencil', 'pen', 'marker', 'highlighter', 'brush'] : undefined}`. The paragraph calls the second instrument the pen and never uses the word marker, so that id cannot be matched to the direction sensitive description by name.

## The eraser survives in pixels and nowhere in the array the README tells you to store

`onChange` fires on every finished stroke and every erase. `getStrokes` returns `Stroke[]`. The README then says strokes are plain data: store what `onChange` gives you and hand it back as `initialStrokes`. The eraser, by its own description, takes away area rather than whole strokes, so a partial rub changes what is visible without producing a new entry in a flat array of stroke objects. The save and restore round trip the README recommends has no field in which a removed region can live.

`toSvg` is the only export that describes the current picture rather than the stroke list, and its own description, exactly what is on screen, erasing and all, is the single place erasure is treated as a first class thing. That one clause is the clearest statement in the file that the screen and the stroke array stop agreeing after the first eraser stroke.

Undo is a third store with its own reset rule. `setStrokes` replaces the drawing and its undo history, so loading a saved drawing drops the redo stack along with the undo stack. `undo`, `redo` and `clear` share one table row, take no arguments and return nothing, so a caller cannot ask whether a redo exists before firing it. `clear` sitting in that row also means there is no documented call that brings a cleared board back.

## Twenty-seven props, one of which computes its own default from another

The tables enumerate 27 props: six under Surface, six under Tools, fifteen under Chrome. The shape of that surface explains a lot. `board` takes `{ w, h }` and falls back to the element size, so an unmeasured container silently decides the coordinate system. `background` accepts any CSS colour, the literal `transparent` to paint nothing, or `checker`. `chrome={false}` removes the toolbar and leaves the surface, and three separate exports, `DrawSurface`, `Toolbar` and `useDrawing`, exist for people who want that split assembled by hand.

Several props are unions with more states than the sample code exercises: `placement` has bottom, left and right; `align` has start, center and end; `depth` has flat, soft, regular and strong; `motion` has rise, none, or an object carrying in, out and duration; `theme` has light, dark and auto. `settings` splits size and opacity between the bar and the tool, and `gauge` prints the current size on the barrel, which is presentation rather than behaviour but ships as a boolean like the rest.

`minimizeAlign` is the only prop whose default is computed. Its cell reads start or end, then says the default follows `align` and center uses end, and continues that the open bar stays at `align` while dragged bars stay where placed. Three sentences of positioning rule in a column otherwise reserved for types. `startMinimized` and `drawWhenMinimized` are the only two booleans about the collapsed state, and the second defaults to false, so the state a caller gets by default is a bar collapsed to a disc with drawing switched off.

## The export sample is a fragment, and the three export calls disagree on what they return

The sample under getting the drawing out is reproduced exactly as written:

```tsx
const draw = useRef<DrawHandle>(null)

<Draw ref={draw} />

await draw.current.download('sketch', 'png', 2)
```

Three problems sit in those five lines. `useRef<DrawHandle>(null)` types the current slot as possibly null, and the last line dereferences it with no guard. The `await` sits at module level, which is not valid inside a component function body, and the `useRef` call and the `<Draw ref={draw} />` element are shown as siblings rather than inside one component. Nothing imports `useRef`, and there is no surrounding component.

The signatures themselves do not line up either. `toSvg` returns a string synchronously. `toPng` returns a promise of a Blob and takes an optional scale multiplier. `download` returns a promise of nothing, writes the file itself, and takes a name, a format and the same scale. So a caller wanting pixels has to handle two different async shapes and one sync one, and the format argument that `download` accepts is never named in the `toPng` row. `getSize` returns a `Board`, while the `board` prop is typed as `{ w, h }`, so the exported size type is not the input type spelled the same way.

## Keyboard control is one boolean, with no way to turn off a single key

Every pen has a single key shortcut, shown in its tooltip, and `E` selects the eraser, `[` and `]` change size, `⌘Z` and `⇧⌘Z` drive undo and redo. Hold `Shift` while drawing and the stroke locks to the nearest of eight directions.

The only control over any of this is `shortcuts`, a boolean that defaults to true and is all or nothing. There is no prop to unbind `E`, no prop to remap a pen letter, and no prop to turn off the eight direction lock. A page that hosts the toolbar inside a form will find `E` landing in a text input unless it disables the whole set. A page inside anything that already binds `⌘Z` will have two undo stacks competing for one chord, and `⇧⌘Z` is written with macOS symbols only, with no documented mapping for Windows or Linux.

Because the keys are advertised in tooltips, discovering them depends on hover, and there is no line in the README listing the complete key table, so a keyboard user has to discover the mapping one tool at a time. The eight direction lock is also not exposed as a prop of any kind, which leaves it unclear whether it applies while the eraser is in hand, or while the bar is collapsed to a disc with `drawWhenMinimized` false.

## The root manifest is private, has no version field, and its dev script starts an app the README never mentions

The root `package.json` is named `drawesome-monorepo`, is marked `private`, sets `type` to module, and carries no `version` key at all. The published version therefore exists only in git tags, so anyone installing from a commit or a branch has nothing to compare against a release.

Its two dev dependencies take opposite policies: `@playwright/test` is pinned to the exact `1.63.0` while `typescript` uses the caret range `^5.7.0`. Five scripts are defined. `dev` filters to `drawesome-studio` only, so a fresh clone starts the studio app and never the library. `build` runs the `drawesome` build and then the studio build, `build:packages` runs only the library, `typecheck` recurses with `pnpm -r typecheck`, and `test:e2e` is a bare `playwright test` with `playwright.config.ts` and `tests/` sitting at the repository root.

The root listing holds both `apps/` and `packages/`, and the README names neither directory. It never mentions `drawesome-studio`, and it links no demo, playground, or hosted editor anywhere. The studio is the only thing `pnpm dev` will start and it is undocumented, so a person searching for this toolbar online gets the README and nothing to click.

## Three 0.1.x tags, the newest one second after the last push, and no changelog

There are three releases: v0.1.0 on 2026-07-29, v0.1.1 on 2026-08-07, and v0.1.2 on 2026-09-19. The last push is 2026-09-19, one second before the v0.1.2 tag was cut, which is the expected shape for a release built straight from a commit and not much else. Nothing is marked archived.

Three patch releases in seven weeks, none of them crossing 0.2.0, and the root listing contains no changelog file. A diff between 0.1.1 and 0.1.2 therefore has nowhere to be read: whether the props gained, the pen ids changed, or only the barrel shading moved is answerable only by comparing two checkouts. The same gap sits on the 0.1.0 to 0.1.1 step.

Two more details are worth knowing before you build on this. The default branch is `master`, and the root listing includes `CONTRIBUTING.md` while the README says nothing about it, so the process document is discoverable only by browsing the file list. Licence is MIT, attributed to Benji Taylor, 2026. And the two types a consumer is most likely to want, `Stroke` and `Board`, are named in passing inside the ref handle table with no export statement and no definition anywhere in the file, so a TypeScript caller has to infer both shapes from prose.

## Conclusion

drawesome is a reasonable choice for a single canvas inside a React app that already owns its own layout, and an awkward one for anything collaborative, persisted or version controlled, because the eraser never enters the stroke array it tells you to save. Treat the prop tables as a wish list rather than a contract, check that the `marker` tool id exists before you ship it, and decide early whether you can live with a private root manifest that carries no version and no changelog.

## FAQ

### What does drawesome need before it renders?

React 18 or newer as a peer dependency, and the stylesheet imported separately as `import 'drawesome/styles.css'`. The README states no minimum Node version and no package manager, even though the repository scripts are pnpm filters. A parent container needs an explicit height, since the toolbar fills its parent.

### Does drawesome record an eraser stroke in the data it hands you?

No. `onChange` fires on every erase, but `getStrokes` returns plain stroke objects with no field for a removed region, and `toSvg` is described as the only export that includes erasing. Saving the array and restoring it through `initialStrokes` will bring back anything rubbed out.

### Can two people draw on the same drawesome canvas?

Nothing in the README describes a network layer, presence, or shared document state. The only documented state path is the `onChange` callback feeding `initialStrokes` back in, which is a single client round trip. The alternatives it points at, tldraw and Excalidraw, are named in the README itself.

### Is drawesome still being worked on?

v0.1.2 was tagged on 2026-09-19, twelve days after v0.1.0 on 2026-07-29, and the repository is not archived. The root manifest is private and carries no version field, and there is no changelog, so what changed between those three tags is not written down.

### How does drawesome compare with tldraw and Excalidraw?

The README names both as better fits for assembling a drawing tool from parts, and describes drawesome as opinionated on purpose, with defaults meant to be the version you ship. Everything in the making it yours section is described as turning things off or moving them around rather than rebuilding the toolbar.

## Sources

- [benjitaylor/drawesome on GitHub](https://github.com/benjitaylor/drawesome)
- [Issues](https://github.com/benjitaylor/drawesome/issues)
- [License: MIT](https://github.com/benjitaylor/drawesome/blob/master/LICENSE)
- [README](https://github.com/benjitaylor/drawesome/blob/master/README.md)
- [Releases](https://github.com/benjitaylor/drawesome/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/benjitaylor-drawesome
