Prototype Sketching Skill
Make a working web app look hand-drawn so clients see it is a prototype, not a finished product. This is a look-and-feel skin only. It is purely additive and fully reversible.
Scope guardrails (read first, never violate)
- Only touch styling entry points and
index.html. Append a stylesheet, paste one SVG block, set two attributes. Nothing else. - Never edit components, templates,
.ts/.tsx/.jsx/.vuelogic, routing, state, services, or tests. - Never swap components for sketchy component libraries (e.g. wired-elements) — that rewrites markup. Forbidden.
- Never recolor or distort logos, photos, charts, or icons that must stay crisp — use the opt-out class.
- Reverting must stay trivial: removing the two
<html>attributes turns the whole skin off.
How it works (one mental model)
- One stylesheet
assets/sketch-skin.cssdefines all 5 styles + the sketchiness ramp. - One SVG block
assets/sketch-filters.htmldefines the wobble filters (#sketch-25…#sketch-100). - Activation is two attributes on
<html>:data-sketch="<style>"anddata-sketch-level="<0|25|50|75|100>". - The look comes from: handwritten font swap + hand-drawn boxes (irregular border-radius, flat fills) + an SVG displacement filter whose strength is the sketchiness level. The font carries text; the filter wobbles box decoration only (never blurs text).
Quick start (do these in order)
- Confirm the request is skin-only. If the user wants behavior/layout changes, stop — that is out of scope.
- Detect the framework and find its global stylesheet +
index.html(see recipe table below). - Pick a style (table in "Styles") and a level (
0,25,50,75, or100). - Add the skin CSS to the project's global styles:
- Copy
assets/sketch-skin.cssinto the project (e.g.src/styles.scss) or@importit. The font@importis already inside that file (online use).
- Copy
- Paste the SVG filters from
assets/sketch-filters.htmlimmediately after the opening<body>tag inindex.html. (Required —filter: url(#sketch-…)resolves only if these defs are in the DOM.) - Activate by editing the
<html>tag inindex.html:<html lang="en" data-sketch="balsamiq" data-sketch-level="50"> - Verify in the browser. To revert, delete the two
data-sketch*attributes (the appended CSS/SVG can stay dormant — they do nothing without the attributes).
Styles
data-sketch | Look | Font | Use for |
|---|---|---|---|
balsamiq | Clean greyscale wireframe, flat boxes | Balsamiq Sans | The default "this is a mockup" |
sharpie | Bold black marker, heavy stroke | Permanent Marker | Whiteboard / workshop sketch |
pencil | Light graphite, soft strokes | Gaegu | Gentle "pencil on paper" draft |
doodle | Playful, light color accents | Short Stack | Friendly, informal concepts |
blueprint | White ink on blue paper | Patrick Hand | Technical "drawing board" feel |
Sketchiness level
data-sketch-level controls how shaky the lines are. It selects a different SVG displacement filter:
| Level | Effect |
|---|---|
0 | Hand-drawn boxes + font, but straight, steady lines (cleanest) |
25 | Subtle wobble |
50 | Clearly hand-drawn (recommended default) |
75 | Loose, energetic sketch |
100 | Very rough, maximum "scribble" |
If data-sketch is set but data-sketch-level is omitted, the skin defaults to level 50.
Diagonal watermark (optional)
To stamp the whole screen as a draft, add a third <html> attribute with the text to show:
<html data-sketch="balsamiq" data-sketch-level="50" data-sketch-watermark="DRAFT">
data-sketch-watermark paints one faint, hand-lettered word diagonally across the viewport. It is a fixed, non-interactive layer (pointer-events: none), so it never blocks clicks, never changes layout, and stays in place across page and route changes. Any text works (DRAFT, PROTOTYPE, CONFIDENTIAL, a client name…). Remove the attribute to turn it off. With the overlay, a Watermark text field sets it live.
Modals, popups & SPA navigation
Because the skin is activated by attributes on <html> and styled with global [data-sketch] … selectors, anything that appears after activation inherits it automatically — dialogs, popovers, dropdown menus, tooltips and toasts (including portal-mounted ones), and every screen you navigate to in a single-page app. There is nothing to wire per component or per route.
Two things keep it consistent:
- The selector lists already cover
dialog,[popover],[role="dialog"],[aria-modal="true"],.modal,.popover, menus, tooltips and toasts, plus the native::backdrop. - The overlay script watches
<body>and re-injects the SVG filters if a framework replaces the body on a route change, so the wobble never silently drops. (With the static-attribute model, pastesketch-filters.htmlonce after<body>; SPA frameworks mount into a child root and leave it intact.)
Framework injection recipes
The skin is framework-agnostic. These are the exact targets; full details in references/implementation.md.
| Stack | Add CSS to | Paste SVG into | Set attributes on |
|---|---|---|---|
| Angular | src/styles.scss (or styles.css) | src/index.html after <body> | <html> in src/index.html |
| React / Vite | src/index.css (imported in main.tsx) | index.html after <body> | <html> in index.html |
| Vue | src/assets/main.css (imported in main.ts) | index.html after <body> | <html> in index.html |
| Static HTML | link sketch-skin.css in <head> | index.html after <body> | <html> in index.html |
Keeping specific assets crisp (opt-out)
The skin will not edit markup, but if the user explicitly asks to protect one element (logo, chart), the only allowed markup change is adding the opt-out hook to that single element:
<img src="logo.svg" class="no-sketch" alt="Logo">
.no-sketch (or data-no-sketch) removes the wobble filter and color overrides from that element. Use sparingly and only when asked.
Interactive preview overlay (optional)
When the user wants to toggle styles live (e.g. show a client a switch), use the overlay instead of static attributes:
- Add
assets/sketch-skin.cssto the global styles (as in Quick start). - Add one script tag before
</body>:<script src="sketch-toggle.js" defer></script> - Done. The script renders a floating panel with OFF + one radio per theme,
a 0–100% sketchiness slider, and a watermark text field, flips the
<html>attributes live, and remembers the choice inlocalStorage. It auto-injects the SVG filters (and re-injects them if an SPA replaces the body), so step 5 of Quick start (pastingsketch-filters.html) is not needed.
This is still skin-only and non-destructive — the script just sets attributes. Remove the tag to disable. Choose this model when the request is "let me/the client switch styles"; use static attributes for a fixed, screenshot-stable look.
Screenshot gallery
README.md shows every style and the 0→100% ladder using the PNGs in
demo/screenshots/. Those images are pre-rendered; the interactive demo app and
the Playwright capture script that generate them live in the project's
development source and are not bundled with the installed skill. The skill itself
is just assets/ + references/ — no build step, no dependencies.
Examples
Example 1 — Angular demo in Balsamiq, moderate sketch
- Input: "Make this Angular app look like a Balsamiq wireframe for tomorrow's demo."
- Actions: append
sketch-skin.csstosrc/styles.scss; paste filters intosrc/index.html; set<html data-sketch="balsamiq" data-sketch-level="50">. - Result: app runs identically; renders as a greyscale wireframe.
Example 2 — React, maximum sharpie
- Input: "React app, full whiteboard-marker look, really rough."
- Actions: import
sketch-skin.cssinmain.tsx; paste filters intoindex.html; setdata-sketch="sharpie" data-sketch-level="100".
Example 3 — Turn it off
- Input: "Switch back to the real UI."
- Action: delete
data-sketchanddata-sketch-levelfrom<html>. Done — no other change needed.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Boxes look hand-drawn but lines are perfectly straight | SVG filters not in DOM, or level 0 | Confirm sketch-filters.html is pasted after <body>; set level ≥ 25 |
| Nothing changed at all | Attributes missing or CSS not loaded | Verify <html data-sketch="…"> is present and sketch-skin.css is in the global styles |
| Text looks blurry | Filter applied to text/whole <body> | Do not add filter: to body or text; the skin filters box decoration only — re-copy sketch-skin.css unmodified |
| Fonts are not handwritten | Offline / blocked Google Fonts | Self-host fonts — see assets/fonts/README.md |
| Page feels sluggish on huge tables/grids | feDisplacementMap cost on large DOM | Lower the level, or scope the wobble selector list (see references/implementation.md) |
| A logo got desaturated/wobbly | No opt-out on that asset | Add class="no-sketch" to that single element |
| Icon fonts / SVG icons disappeared | Aggressive flatten | Add no-sketch to the icon container, or see the icon note in references/implementation.md |
Reference files (read only when needed)
references/styles.md— exact font/palette/stroke spec for each of the 5 styles, and how to add a new style.references/implementation.md— SVG filter math, per-framework step-by-step, selector tuning, offline fonts, performance, revert details.