Rougher Stuff is a shared geometry, rendering, routing, animation, and annotation stack for expressive web interfaces. It evolves Rough.js, Perfect Arrows, and Rough Notation together while keeping each package useful on its own.
Interactive playground · Documentation · Contributing
| Package | Purpose |
|---|---|
roughjs |
Canvas/SVG rough rendering, semantic drawables, retained updates, and animation |
@rougher-stuff/perfect-arrows |
Renderer-independent arrow routes, bounds, and decorations |
@rougher-stuff/rough-notation |
DOM/Range annotations, shared layers, connectors, and callouts |
@rougher-stuff/geometry |
Canonical paths, transforms, metrics, intersections, and rounded contours |
@rougher-stuff/animation |
Framework-neutral progress controllers and timelines |
@rougher-stuff/markdown |
Markdown directives, semantic themes, block annotations, and callouts |
Install only the layer you need:
pnpm add roughjs
pnpm add @rougher-stuff/perfect-arrows
pnpm add @rougher-stuff/rough-notation roughjs
pnpm add @rougher-stuff/markdown roughjsDraw to Canvas:
import rough from 'roughjs';
const canvas = document.querySelector('canvas')!;
const drawing = rough.canvas(canvas);
drawing.roundedRectangle(20, 20, 220, 120, {
cornerRadius: 18,
fill: '#f0c84b',
fillStyle: 'hachure',
seed: 42,
});Create renderer-independent arrow geometry:
import { createArrow, toSvgPath } from '@rougher-stuff/perfect-arrows';
const arrow = createArrow({
from: { x: 20, y: 40 },
to: { x: 240, y: 160 },
route: { type: 'swoop', bend: 0.4 },
end: { type: 'open-arrow', size: 12 },
});
const pathData = toSvgPath(arrow.body);Annotate an interface:
import { annotate } from '@rougher-stuff/rough-notation';
const annotation = annotate(document.querySelector('#important')!, {
type: 'highlight',
color: '#f0c84b',
seed: 42,
});
annotation.show();Read the getting started guide for SVG, retained rendering, connectors, and foundation APIs.
Rougher Stuff separates nominal geometry from rough compilation, paint, rendering, and progress:
nominal geometry → rough compilation → render plan → SVG / Canvas → interaction
The foundation packages provide exact geometry and normalized time. Product packages add rendering, arrow routing, and DOM-aware annotations without reversing that dependency direction. See the architecture guide for details.
This repository uses pnpm workspaces, Turbo, Vitest, Playwright, Oxlint, and Oxfmt. Node.js 22 matches CI.
corepack enable
pnpm install --frozen-lockfile
pnpm dev # product site and playground
pnpm docs:dev # documentation siteBefore opening a pull request, run the same quality gate as GitHub Actions:
pnpm format
pnpm lint
pnpm test
pnpm buildVisual tests and benchmarks remain explicit because they depend on browsers or stable machine conditions:
pnpm test:visual
pnpm bench
pnpm bench:roughjs:compare
pnpm bench:perfect-arrows:compare
pnpm bench:notation:compareapps/playground product site and interactive demos
apps/docs VitePress documentation
packages/* publishable libraries
scripts benchmark and maintenance scripts
visual-tests upstream Rough.js visual fixtures
Pushes to main run CI and publish the playground plus documentation to GitHub Pages. The deployment workflow assembles apps/playground/dist at the site root and apps/docs/.vitepress/dist under /docs/.
To reproduce the Pages build locally:
pnpm build:siteRepository maintainers must select GitHub Actions as the Pages source once in Settings → Pages. Dependabot checks workspace dependencies weekly and Actions monthly.
Rougher Stuff builds on the original work of Preet Shihn (Rough.js and Rough Notation) and Steve Ruiz (Perfect Arrows). Consolidated geometry algorithms retain their upstream notices in packages/geometry/THIRD_PARTY_NOTICES.md.
Financial contributions to the upstream Rough.js project are accepted through Open Collective.
MIT. Package-level upstream attribution and notices live beside each package.