A drop-in CSS design system that makes any web page look like marker on cream paper. Tokens, components, and utilities for the hand-drawn aesthetic — with one wobbly SVG filter doing the heavy lifting.
sk-, so it and jawnpaper (np-) run on the same page.
<head> and one class to <body>:
<link rel="stylesheet" href="jawnpaper.css">
<script src="jawnpaper.js" defer></script>
<body class="notebook">No npm, no build step. Or grab it from the CDN / npm i jawnpaper. See it in the wild →
Drag, click, and pick. Live changes apply to the whole page. Copy the :root block when you find a theme you like.
Nine ready-made themes. Click to apply. Sticks to ?theme=… in the URL so you can share a vibe.
"Jawn" is Philly slang — a stand-in for any noun: a thing, a place, a person. It's the most flexible word in American English. That's the bet here too: one wobbly filter, a dozen color tokens, and a small kit of components that can be any thing.
This started as the border-shape playground — one CSS rule that wobbles an SVG border. Then it became "what if a whole page was that?" The lack of design is the design. The hand-lettered fonts, the marker-on-cream feel, the slightly tilted notes — it should look like someone sketched it during a meeting.
Everything is wired through CSS custom properties on :root. Override any of them in your own stylesheet to retheme.
#ffe87a#e85a4f#5b9bd5#6ec275#a780e8#ff924f#f48fb1#4ec9b0#fdfaf2#f6efd8#ede4c5#1a1a1aThree font families, all hand-lettered. Headings use Caveat, body uses Patrick Hand, the stamp class uses Patrick Hand SC for that all-caps marker feel.
A lede paragraph uses .np-lede for a slightly oversized intro line that signals "start reading here."
Body copy is Patrick Hand. Inline code sits on a yellow highlighter swatch. Underlined phrases use a CSS gradient. Colored text via utilities.
The basic container. Wobbly ink border drawn by a ::before pseudo-element so it doesn't affect the contents.
Use .np-card on any block element.
<div class="np-card">
...
</div>Same card with a deeper paper background via .np-bg-deep.
Yellow background reads as a sticky note.
Wrap a grid in .np-tilt-children to give every Nth card a subtle pinned-note rotation. Tilts are auto-disabled below 720px and for prefers-reduced-motion.
Highlighted callouts for asides, warnings, and tips. The default is yellow; modifier classes change the wash color.
Use <a class="np-button"> for links and <button class="np-button"> for actions. 44px minimum tap height built in.
Use .np-toolbar on a wrapping element for an evenly-spaced flex group:
Paper folder tabs that switch panels. Native ARIA — keyboard arrows work.
Tab one panel. Use role="tab", aria-controls, and a role="tabpanel" for each.
Tab two panel. The script wires up arrow-key cycling and click activation.
Tab three panel. Add as many as you want; layout wraps on mobile.
Native <details>/<summary> dressed up. Free a11y, no JS needed for open/close.
No. Drop in three files (CSS, JS, optional SVG) and add class="notebook" to <body>. That's it.
Sure. Set --np-wobble-scale: 0 in your own CSS and the borders go straight. Or use the slider in the playground above.
The base jawnpaper.js only injects the SVG filter. The extended behaviors (theme switcher, playground, tabs, etc.) are all opt-in via class names or data attributes. Loading the script doesn't change anything until you opt in.
Native <dialog> styled as a sticky-note overlay. Add data-jp-open="dialog-id" to a button.
Page-level callouts in four flavors. Use .np-alert with a modifier and an icon span.
filter; modern browsers only.@import for Google Fonts is render-blocking — see the README for faster options.Tags are small inline labels: draft urgent info done fyi
Stamps are bigger, rotated, and meant to grab attention: experimental draft approved final
Solid-fill badges for status pills: new urgent info live beta
Sketched keycaps for keyboard hints: press ctrl+k to focus search, or ⌘+/ to open the cheat sheet. Use .np-kbd.
Circle a phrase: like this. Or in a different color: blue oval, green oval, purple oval.
Underline a phrase with a marker swipe: default yellow, red, blue, green.
Inline code uses a yellow swatch automatically when inside .notebook, or with the .np-code class anywhere.
For multi-line code, wrap a <pre> in .np-codebox:
function wobble(element) {
element.style.filter = 'url(#np-wobble)';
return element;
}
const card = document.querySelector('.card');
wobble(card);| component | class | what it does |
|---|---|---|
| card | .np-card | Bordered paper container. |
| note | .np-note | Highlighter callout. Add --red / --blue / etc. for color. |
| button | .np-button | Pill button with 44px min height. Add --primary or --danger. |
| tag | .np-tag | Small inline label. Same color modifiers. |
| stamp | .np-stamp | Rotated red label. Eye-catching. |
| circled | .np-circled | Wobbly oval annotation around a phrase. |
Breadcrumbs (use a <nav>+<ol> with .np-breadcrumbs):
Pagination (use a <nav>+<ul> with .np-pagination):
A wobbly outline filled with marker scribbles. Set aria-valuenow for screen readers and the inline width on the fill.
.np-bubble.
The most important step a man can take is the next one. Dalinar Kholin, Oathbringer
For things outside a form. Click an item to toggle it. Adds a marker strikethrough on done items.
:rootVertical anchored list. Use <ol class="np-timeline"> with .np-event children.
Tabs, accordion, modal, alert, breadcrumbs, pagination, progress, timeline, checklist, badge, kbd. Theme presets, live playground, four example pages, and a CDN/npm story.
Cards, notes, buttons, tags, stamps, circled, code, tables, forms, bubbles. The wobble filter, generalized into a system.
A single page experimenting with SVG-filtered borders. The seed that became this whole thing — about six hours before the system around it.
| class | what it does |
|---|---|
.np-container | Centered max-width 960px container with fluid padding. |
.np-grid | Auto-fit responsive grid. Each cell is at least 310px or 100% of the parent. |
.np-stack | Vertical stack with consistent spacing between children. |
.np-stack--lg / --sm | Bigger or smaller stack gap. |
.np-tilt-children | Auto-rotate every 3rd child a fraction of a degree (pinned-note effect). |
.np-wobble-border | Apply a wobbly ink border to any element. |
.np-tilt-left / --right / -2 / -3 | One-off tilt utilities. |
Opt-in extras. Click a button to flip the flourish on for this page, or scrub through the inline samples below.
Add .np-reveal to any element. The script fades it in via IntersectionObserver. Disabled for prefers-reduced-motion.
<div class="np-card np-reveal">…</div>Re-randomizes the SVG turbulence seed every ~5s so the page never freezes into a static image. Click below, then watch any wobbly border for a beat.
<body class="notebook" data-jp-breathe>An SVG ink trail follows the pointer. Click to switch on, then move around the page. Disabled for reduced-motion users.
<body class="notebook np-pen-cursor">Random hand-drawn marks scattered around the page edges. Hidden on small screens. The shapes the script picks from:
<body class="notebook notebook--doodled">A subtle SVG noise layer for paper-fiber texture. Sample at twice the page intensity:
<body class="notebook notebook--grainy">An alternative to the squiggle for a rougher break:
<hr class="np-divider--torn">Four full pages built entirely from jawnpaper components. They're fictional — but they show what real sites would look like.
Hot honey carbonara. Tickable ingredients, numbered steps, "tested" stamp.
The Bloomfield Beat issue four. Articles, callouts, reader letters.
Ada Velour, freelance worry consultant. Tag-cloud skills, certification stamps.
Phantom Bookbinder LLC bills the Marlow Estate. Line items, "overdue" stamp.
All visual choices are tokens on :root. To retheme, override the variables in your own stylesheet after loading jawnpaper.css:
:root {
--np-paper: #f0e6d2; /* swap the cream for parchment */
--np-ink: #2c1810; /* sepia ink */
--np-yellow: #d4af37; /* gold highlighter */
--np-red: #8b0000; /* deep crimson */
--np-font-display: 'Kalam', cursive;
}Or scope the override under a class for per-page themes:
.midnight {
--np-paper: #1c1c2e;
--np-paper-deep: #2a2a40;
--np-ink: #f0f0f5;
--np-yellow: #ffd166;
}Optional. Drop in jawnpaper-elements.js and write tags instead of class strings. Same look, framework-friendly.
<script src="jawnpaper-elements.js" defer></script>
<jp-card variant="yellow">
<h3>a card</h3>
<p>same as <code>.np-card.np-bg-yellow</code>.</p>
</jp-card>
<jp-note variant="red">a red note</jp-note>
<jp-button variant="primary">primary</jp-button>
<jp-stamp variant="green">approved</jp-stamp>The variant attribute is observed — change it at runtime and the right modifier classes get swapped on the host element. No Shadow DOM, so jawnpaper's CSS still styles content directly.
Three ways to grab jawnpaper, in order of laziness:
Paste two lines into your <head>. No download, no build step.
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/jamditis/stash/docs/jawnpaper/jawnpaper.css">
<script defer
src="https://cdn.jsdelivr.net/gh/jamditis/stash/docs/jawnpaper/jawnpaper.js"></script>Pin to a commit for production by inserting @<sha> after the repo segment (e.g. stash@abc1234/docs/...). See CDN.md.
For projects with a bundler (Vite, webpack, esbuild, …):
npm install jawnpaper// in your entry file
import 'jawnpaper/jawnpaper.css';
import 'jawnpaper/jawnpaper.js';Drop the three files into your project. The STARTER.html in this folder is a complete, working copy you can rename to index.html.
Modern browsers (Chrome / Edge / Safari / Firefox 2023+). The whole system depends on filter: url(#…) and color-mix(), both of which are universally baseline now. prefers-reduced-motion disables card tilts and smooth-scroll. Print stylesheet drops the wobble for clean printouts.
examples · CDN · starter · source · born from the border-shape playground