LmCast :: Stay tuned in

Drawably: Hand-Drawn UI Controls

Recorded: Sept. 14, 2026, 12:09 p.m.

Original Summarized

GitHub - Danilaa1/drawably: 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. · GitHub

Skip to content

Navigation MenuSign inAppearance settingsPlatformAI CODE CREATIONGitHub CopilotWrite better code with AIGitHub Copilot appDirect agents from issue to mergeMCP RegistryIntegrate external toolsDEVELOPER WORKFLOWSActionsAutomate any workflowCodespacesInstant dev environmentsIssuesPlan and track workCode ReviewManage code changesCode QualityEnforce quality at mergeAPPLICATION SECURITYGitHub Advanced SecurityFind and fix vulnerabilitiesCode securitySecure your code as you buildSecret protectionStop leaks before they startEXPLOREWhy GitHubDocumentationBlogChangelogMarketplaceView all featuresSolutionsBY COMPANY SIZEEnterprisesSmall and medium teamsStartupsNonprofitsBY USE CASEApp ModernizationDevSecOpsDevOpsCI/CDView all use casesBY INDUSTRYHealthcareFinancial servicesManufacturingGovernmentView all industriesView all solutionsResourcesEXPLORE BY TOPICAISoftware DevelopmentDevOpsSecurityView all topicsEXPLORE BY TYPECustomer storiesEvents & webinarsEbooks & reportsBusiness insightsGitHub SkillsSUPPORT & SERVICESDocumentationCustomer supportCommunity forumTrust centerPartnersView all resourcesOpen SourceCOMMUNITYGitHub SponsorsFund open source developersPROGRAMSSecurity LabMaintainer CommunityGitHub StarsArchive ProgramREPOSITORIESTopicsTrendingCollectionsEnterpriseENTERPRISE SOLUTIONSEnterprise platformAI-powered developer platformAVAILABLE ADD-ONSGitHub Advanced SecurityEnterprise-grade security featuresCopilot for BusinessEnterprise-grade AI featuresPremium SupportEnterprise-grade 24/7 supportPricingSearch/Sign inSign upAppearance settings

You signed in with another tab or window. Reload to refresh your session.
You signed out in another tab or window. Reload to refresh your session.
You switched accounts on another tab or window. Reload to refresh your session.

Dismiss alert

Danilaa1

/

drawably

Public

Notifications
You must be signed in to change notification settings

Fork
26

Star
610

Code

Issues
0

Pull requests
1

Actions

Projects

Security and quality
0

Insights

Additional navigation options

Code

Issues

Pull requests

Actions

Projects

Security and quality

Insights

mainBranchesTagsGo to fileCodeOpen more actions menuLatest commit History52 Commits52 CommitsFolders and filesNameNameLast commit messageLast commit date.github.github  assetsassets  examplesexamples  fontfont  remotionremotion  srcsrc  teststests  .gitignore.gitignore  AGENTS.mdAGENTS.md  CLAUDE.mdCLAUDE.md  LICENSELICENSE  PLAN.mdPLAN.md  README.mdREADME.md  package-lock.jsonpackage-lock.json  package.jsonpackage.json  style.cssstyle.css  style.css.d.tsstyle.css.d.ts  tsconfig.jsontsconfig.json  vitest.config.tsvitest.config.ts  View all filesRepository files navigationREADMEMIT licenseMore itemsdrawably
Hand-drawn UI controls. Every mount generates a fresh pen sketch from seeded
randomness, and the stroke boils like an animated doodle. Zero dependencies,
~9 KB of JS gzipped (React wrappers add under 1 KB) and a 3 KB stylesheet. An
optional pen font is a separate 31 KB.

Install
npm i drawably
Quick start
import { drawablyButton } from "drawably";
import "drawably/style.css";

drawablyButton(document.querySelector("#done"), { variant: "solid" });
React:
import { DrawablyButton } from "drawably/react";
import "drawably/style.css";

<DrawablyButton variant="solid" onClick={submit}>Done</DrawablyButton>
Each attach call returns a sketch handle:
const sketch = drawablyButton(el);
sketch.resketch(); // redraw with a new random seed
sketch.resketch(42); // redraw with a specific seed
sketch.destroy(); // remove the SVG and all listeners
Buttons
Three variants: outline (default), solid, scribble. Buttons also carry a
state machine for async work:

const button = drawablyButton(el);
button.setState("loading"); // dims the button, boils faster
button.setState("error"); // redraws in red
button.setState("success"); // redraws in green
button.setState("idle");
In React, pass the state prop; the sketch stays put and only the state
changes:
<DrawablyButton state={saving ? "loading" : "idle"}>Save</DrawablyButton>
Override the state colours with --drawably-error and --drawably-success.
For secondary or destructive actions, set tone: "neutral" (warm grey) or
tone: "danger" (red).
Controls

Function
Element it expects

drawablyButton(el, opts)
a <button>

drawablyCheckbox(el, opts)
wrapper containing <input type="checkbox">

drawablyRadio(el, opts)
wrapper containing <input type="radio">

drawablyToggle(el, opts)
wrapper containing <input type="checkbox">

drawablyInput(el, opts)
wrapper containing an <input>

drawablyTextarea(el, opts)
wrapper containing a <textarea>

drawablySelect(el, opts)
wrapper containing a <select>

drawablyDivider(el, opts)
an <hr> or div

drawablyCard(el, opts)
any block element

drawablyBadge(el, opts)
any inline element

drawablyList(el, opts)
a <ul> or <ol>; each <li> gets a sketched marker

Badges take variant: "outline" | "scribble"; lists take
marker: "dash" | "check". Selects reserve the widest option's width so
picking never shifts layout; in Chromium the options list gets a sketched
frame and pen check (appearance: base-select), Safari and Firefox keep the
OS popup.
The real inputs stay in the DOM, so keyboard, forms, labels and screen readers
all work as usual. The sketch is an aria-hidden SVG layered underneath.
Every control has a React counterpart in drawably/react: DrawablyButton,
DrawablyCheckbox, DrawablyRadio, DrawablyToggle, DrawablyInput,
DrawablyTextarea, DrawablySelect, DrawablyDivider, DrawablyCard,
DrawablyBadge, DrawablyList.
Text decoration
Annotate copy the way you would with a pen. Each attaches to an inline element
and leaves its layout alone; use them on a word or a short phrase.

Function
Draws

drawablyUnderline(el, opts)
a rough line under the text, re-sketched on hover

drawablyHighlight(el, opts)
a marker wash behind the text

drawablyCircle(el, opts)
a hand-drawn ellipse looping around the text

drawablyArrow(from, to, opts)
an arrow from one element to another

import { DrawablyUnderline, DrawablyHighlight, DrawablyCircle, DrawablyArrow } from "drawably/react";

<p>
<DrawablyUnderline>Hand-drawn</DrawablyUnderline> UI, a{" "}
<DrawablyHighlight>fresh sketch</DrawablyHighlight> on{" "}
<DrawablyCircle>every mount</DrawablyCircle>.
</p>
<DrawablyArrow from={noteRef} to={buttonRef} />
A decoration that wraps onto several lines gets one drawing per line. The
arrow's SVG is appended to <body> in document coordinates and redraws on
resize. Anchors inside a scrolling container will drift as it scrolls.
Composites
Pieces built from the controls above. One seed reproduces every stroke in
the piece; destroy() tears them all down.

Function
Markup
Draws

drawablyChip(el, opts)
<label><span><input type="checkbox"></span> text</label>
badge around the chip, sketched box on the input's wrapper

drawablyTabs(el, opts)
children are the tabs
underline on the active tab; active index or aria-selected="true"; setActive(i)

drawablyTooltip(tip, target, opts)
two elements
card around the tip, arrow to the target

drawablyAlert(el, opts)
optional [data-tag] child
card around the alert, badge on the tag

drawablySteps(el, opts)
<ol>
check-marked list

drawablyKbd(el, opts)
<kbd>
badge with a steadier hand

drawablyQuote(el, opts)
first element child, optional <footer>
highlight on the line, divider on the footer

drawablyPager(el, opts)
child <button>s
outlined pages, solid current; active index or aria-current; setPage(i)

import { DrawablyChip, DrawablyTabs, DrawablyTooltip, DrawablyAlert, DrawablySteps, DrawablyKbd, DrawablyQuote, DrawablyPager } from "drawably/react";

<DrawablyChip defaultChecked>pen</DrawablyChip>
<DrawablyTabs active={tab}><span>a</span><span>b</span></DrawablyTabs>
<DrawablyTooltip to={buttonRef}>undo</DrawablyTooltip>
<DrawablyAlert><span data-tag>new</span> Import from Attio lands Friday</DrawablyAlert>
<DrawablySteps><li>record</li><li>label</li></DrawablySteps>
<DrawablyKbd>⌘K</DrawablyKbd>
<DrawablyQuote><span>less, but better</span><footer>Rams</footer></DrawablyQuote>
<DrawablyPager active={page}><button>‹</button><button>1</button><button>›</button></DrawablyPager>
Options
All controls take the same base options:

Option
Default
What it does

seed
random
Omit for a unique sketch per mount, pass a number for a reproducible one

roughness
1
Wobble of the base sketch

boil
0.3
Px of frame-to-frame flicker; 0 renders one static path

stroke, fill, paper
pen blue / white
Colours, set as --drawably-* custom properties

width
2
Stroke width in px

The colours are plain CSS custom properties, so a theme can set them once:
:root {
--drawably-stroke: #1a1a1a;
--drawably-fill: #1a1a1a;
}
Type is Inter when the page has it loaded, falling back to system-ui. The
library loads no font unless you opt into the one below.
Motion
Strokes boil gently: three frames of the same sketch, micro-wobbled around a
shared base, cycled by pure CSS at 1200ms. Hover or press re-sketches
buttons, checkboxes, radios, toggles, underlines and circles; buttons also lift
on hover and sink on press. prefers-reduced-motion freezes everything to a
single static sketch, including the demo images above.
Font (optional)
Drawably Pen is the same strokes as a typeface: a–z, A–Z, digits and
punctuation, built by the library's own pen code (font/) into a 31 KB
TrueType. It is not loaded by style.css; nothing in the library needs it.
If you want labels in the same hand as the chrome:
import "drawably/font.css";
.drawably-button {
font-family: "Drawably Pen", Inter, sans-serif;
}
Build your own shapes
The rough renderer is exported. Each function returns an SVG path string, and
variants produces the boil frames:
import { roughRoundedRect, roughLine, roughCircle, variants } from "drawably";

const frames = variants(
(o) => roughRoundedRect(0, 0, 200, 100, 12, o),
{ seed: 7, roughness: 1, boil: 0.3 },
);
// three path strings — render them and cycle opacity
Also exported: roughEllipse, roughArrow, roughCheckmark, scribbleFill,
and the seeded PRNG mulberry32 with randomSeed.
License
MIT.
AboutHand-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.ResourcesReadmeMIT licenseActivityStars610 starsWatchers1 watchingForks26 forksReport repositoryReleasesPackagesContributorsLanguages

Footer

© 2026 GitHub, Inc.

Footer navigation

Terms

Privacy

Security

Status

Community

Docs

Contact

Manage cookies

Do not share my personal information

You can’t perform that action at this time.

The drawably library is a system designed to create hand-drawn user interface controls, where every instance generates a unique pen sketch based on seeded randomness, resulting in strokes that animate like doodles. The library is characterized by its minimal footprint, requiring zero external dependencies, comprising approximately 4 kilobytes of gzipped JavaScript and a 3 kilobyte stylesheet.

The core mechanism involves generating unique visual representations through seeded randomness; when an element mounts, it generates a fresh pen sketch. Control over this randomness is managed through options such as the seed, which allows for reproducible sketches if a specific number is provided, and roughness, which dictates the wobble of the base sketch, and boil, which controls the visual flicker of the frame-to-frame rendering. Color and stroke properties are managed using CSS custom properties, allowing for theme-based styling.

The library provides a set of primary functions for drawing interactions on standard HTML elements, encompassing various controls such as buttons, checkboxes, radio buttons, inputs, textareas, and select menus, alongside elements like dividers and cards. These functions, such as drawablyButton or drawablyCheckbox, wrap the native HTML elements, ensuring that standard accessibility features like keyboard navigation, form handling, and screen reader compatibility remain fully operational. The resulting sketches are layered beneath the actual inputs as hidden SVG elements.

Additional drawing functionality allows for annotations and complex compositions. Features include functions for drawing underlines, highlights, circles, and arrows between elements, enabling expressive annotations directly on the UI. Furthermore, the library supports composite functions for building complex UI patterns, such as chips, tabs, tooltips, alert boxes, step lists, and pagers, allowing users to sketch entire interactive components.

Buttons offer three visual variants: outline, solid, and scribble. These buttons also incorporate a state machine capable of managing asynchronous workflows, allowing them to visually transition to states like loading, error, or success, which further influence the visual rendering. The system is decoupled, utilizing React components for these drawing interactions, such as DrawablyButton, providing a clear separation between the drawing logic and the underlying structure.

The aesthetic experience can be further customized through optional font settings, where a specialized typeface, Drawably Pen, is included as an option, allowing the text labels to possess the same hand-drawn aesthetic. The system supports exporting the rough renderer, which provides the generated SVG path strings and the boil frames, which are essential for rendering the animated strokes.