drawably: Hand-Drawn UI Controls That Resketch on Every Mount
Hand-drawn UI controls. Every mount generates a fresh pen sketch from seeded randomness, and the stroke boils like an animated doodle. Zero dependencies, 4 KB of JS and one stylesheet.
At a glance
- What is it?
- drawably is a zero-dependency TypeScript library that draws buttons, inputs and other controls as seeded pen sketches and boils them like an animated doodle. It is a styling layer, not a widget kit, and the README is explicit that the real inputs stay in the DOM.
- Who is it for?
- Adopt drawably if you want the sketchbook look on standard form controls and can accept that the drawing is decorative: the real <input> and <button> elements stay in the DOM, so keyboard, labels and screen readers behave as usual. Do not adopt it if you need a component framework with data binding, or if your UI lives inside scrolling containers where the arrow decoration is documented to drift.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 8 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 18, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
What drawably solves, and for whom
Most UI libraries compete on polish. drawably goes the other way: every control is rendered as an SVG pen sketch, and the stroke boils like a doodle. The README states the intent directly: every mount generates a fresh pen sketch from seeded randomness. That single sentence defines the audience. This is for people building sketchy, notebook-style or deliberately informal interfaces, and for people who want that look without giving up native form semantics. The README is explicit that the real inputs stay in the DOM, so keyboard, forms, labels and screen readers all work as usual, and the sketch is an aria-hidden SVG layered underneath. That is the actual selling point. The drawing is decoration; the control is still an <input>, a <button> or a <select>. If your product needs a conventional design system, drawably is the wrong tool and no amount of theming will fix it.
How the sketch is generated and why it boils
The mechanism is seeded randomness plus a small animation loop. Every attach call returns a sketch handle with three methods: resketch() redraws with a new random seed, resketch(42) redraws with a specific seed, and destroy() removes the SVG and all listeners. The base options table lists seed (random by default), roughness at 1, boil at 0.3, width at 2, and stroke, fill and paper colours. Setting boil to 0 renders one static path instead of an animated one. The boiling itself is described in the README's motion section: three frames of the same sketch, micro-wobbled around a shared base, cycled by pure CSS at 1200ms. That is a cheap trick, and a good one. Three pre-generated paths cost almost nothing compared with re-running a path generator per frame, and cycling them in CSS keeps the animation off the main thread. The trade-off is that the wobble is fixed at three frames, so very slow or very large strokes will read as a loop rather than as continuous motion. The README does not offer a knob for frame count.
Installing drawably and attaching your first button
The package installs from npm and ships a stylesheet you must import separately. The README's quick start shows the plain JavaScript path: import the button function, import drawably/style.css, then call the function on a <button> element with a variant option. Three variants exist: outline (the default), solid and scribble. The call returns a handle, so you can keep a reference and call resketch() later, or destroy() when the component unmounts.
import { drawablyButton } from "drawably";
import "drawably/style.css";
drawablyButton(document.querySelector("#done"), { variant: "solid" });After this runs, the README says you should see the button drawn in a pen stroke that boils gently. The underlying <button> is untouched. React users import from the drawably/react subpath instead, which the README says adds under 1 KB on top of the core. Note the element contract: drawablyButton expects a <button>, while drawablyCheckbox, drawablyRadio, drawablyToggle, drawablyInput, drawablyTextarea and drawablySelect expect a wrapper element containing the corresponding native control. Passing the input itself rather than its wrapper is the most likely first mistake.
import { DrawablyButton } from "drawably/react";
import "drawably/style.css";
<DrawablyButton variant="solid" onClick={submit}>Done</DrawablyButton>The button state machine and its colour overrides
Buttons carry a small four-state machine for async work: idle, loading, error and success. In plain JavaScript you call setState on the handle. The README says loading dims the button and makes it boil faster, error redraws it in red, and success redraws it in green. In React you pass a state prop instead, and the README notes that the sketch stays put and only the state changes. That distinction matters: a state change does not consume a new seed, so a button does not visibly jump when a save fails. The error and success colours are overridable through the --drawably-error and --drawably-success custom properties. There are also two tones, neutral (warm grey) and danger (red), intended for secondary and destructive actions. The state machine is deliberately narrow. There is no built-in retry, no timeout and no disabled-state handling beyond what the native button already does, so async orchestration stays your problem.
const button = drawablyButton(el);
button.setState("loading");
button.setState("error");
button.setState("success");
button.setState("idle");Where drawably breaks down
The arrow decoration is the clearest documented failure mode. drawablyArrow draws from one element to another, and the README states that its SVG is appended to <body> in document coordinates and redraws on resize. It then adds the consequence plainly: anchors inside a scrolling container will drift as it scrolls. There is no scroll listener mentioned, so if your layout has an internal scroll area, the arrow will separate from its target. That is a real constraint, not a nitpick, and it rules the arrow out for sidebars, modals with their own scroll and virtualised lists. The select control has a second boundary. The README says selects reserve the widest option's width so picking never shifts layout, and that in Chromium the options list gets a sketched frame and pen check via appearance: base-select, while Safari and Firefox keep the OS popup. So on two of the three major engines the most visually distinctive part of a select simply is not drawn. Text decorations have a third limit: a decoration that wraps onto several lines gets one drawing per line, which is fine for a word or a short phrase and wrong for a paragraph. The README says to use them on a word or a short phrase.
drawably against rough.js and CSS-only sketch borders
The obvious comparison is rough.js, which also generates hand-drawn SVG. The difference is scope. rough.js is a drawing primitive: you give it a shape and it returns rough paths, and you wire up the DOM, the accessibility and the state yourself. drawably ships the whole control: the function table covers buttons, checkboxes, radios, toggles, inputs, textareas, selects, dividers, cards, badges and lists, plus composites such as tabs, tooltips, alerts, steps, keyboard keys, quotes and pagers. One seed reproduces every stroke in a composite, and destroy() tears them all down. The second alternative is hand-rolled CSS borders, which are cheaper but static; they cannot boil, they cannot resketch with a new seed, and they cannot redraw in red on an error state. drawably's cost is the runtime: roughly 9 KB of JS gzipped plus a 3 KB stylesheet, with React wrappers under 1 KB extra. The README's headline says 4 KB in one place and 9 KB gzipped in another; the package.json description and the README body should be treated as the more specific figures. An optional pen font is a separate 31 KB and is not loaded unless you opt in.
Licence, maintenance and what upgrading costs
drawably is MIT licensed, with the author listed as Daniel Belyi in package.json. MIT permits commercial use and modification, and the only obligation it imposes is retaining the copyright notice and permission text; that is a summary of the licence identifier, not legal advice, and anyone shipping it in a regulated product should read LICENSE directly. The repository is not archived. The last push was on 2026-09-10, so the code has moved recently. There are no retrieved releases, and package.json shows version 0.4.2, which means the project is still on a 0.x line where minor bumps can carry breaking changes. That is the practical upgrade cost. The published file list is narrow: dist, examples, style.css, style.css.d.ts, font/font.css, font/font.css.d.ts, font/DrawablyPen.ttf, README.md, .github/agent.md and LICENSE. Because the package declares sideEffects only for style.css and font/font.css, bundlers should tree-shake unused controls, but you should confirm that against your own build rather than assume it.
Editorial conclusion
Adopt drawably if you want the sketchbook look on standard form controls and can accept that the drawing is decorative: the real <input> and <button> elements stay in the DOM, so keyboard, labels and screen readers behave as usual. Do not adopt it if you need a component framework with data binding, or if your UI lives inside scrolling containers where the arrow decoration is documented to drift. Before committing, verify two things yourself: that the package you install matches the published file list (dist, examples, style.css, font/font.css, README.md, LICENSE), and that your target browsers tolerate the Chromium-only sketched select popup, which the README says Safari and Firefox do not get.
Frequently asked questions
Does drawably replace the native form controls?
No. The README states that the real inputs stay in the DOM, so keyboard, forms, labels and screen readers all work as usual, and the sketch is an aria-hidden SVG layered underneath. The JavaScript functions expect either the control itself, as with drawablyButton and a <button>, or a wrapper containing the native control, as with drawablyCheckbox and drawablySelect.
How do I make a drawably sketch reproducible instead of random?
Pass a number as the seed option, or call resketch(42) on the handle returned by the attach call. The README's options table lists seed as random by default, and says passing a number gives a reproducible sketch. Composites take one seed that reproduces every stroke in the piece.
Can I turn off the boiling animation in drawably?
Yes. The boil option defaults to 0.3, described as pixels of frame-to-frame flicker, and setting it to 0 renders one static path. The README's motion section explains that the animation is three frames of the same sketch micro-wobbled around a shared base and cycled by pure CSS at 1200ms.
Community notes