Components
Each one is shown live, with the markup to copy. The caption says where the imperfection is, and why it is free. The styles are in the package's imperfect.css; the markup below uses the same class names.
Hand marks and links
Seven of the fifteen hand-drawn shapes, one per family plus two more underlines. Each is an SVG symbol, drawn in currentColor, and always aria-hidden.
-
Button (perfect)
Where the imperfection is: Nowhere. The primary button is solid ink, 48px tall, with a 2px radius.
Why it is free: It is the one thing on the screen that must be easy to find and press. A border and a colour are enough.
Markup
<a class="btn btn--primary" href="/contact">Start a project</a> <button type="button" class="btn btn--quiet">Quiet button</button> -
Link (hand underline)
Where the imperfection is: The underline under the link text.
Why it is free: The text stays straight and readable. One SVG stroke sits under it, outside the text box, so the hit target is unchanged.
Markup
<a class="link-hand" href="/see-the-work">See the work<svg class="mark mark--underline" viewBox="0 0 200 12" preserveAspectRatio="none" aria-hidden="true"><use href="#m-underline-a"></use></svg></a> -
Hand marks
Where the imperfection is: The shapes themselves: underline, circle, arrow, bracket and strike, three variants each where they exist.
Why it is free: They are aria-hidden inline SVG, painted with currentColor, and never change the size of their parent.
Markup
<svg class="mark mark--underline" viewBox="0 0 200 12" preserveAspectRatio="none" aria-hidden="true"> <use href="#m-underline-a"></use> </svg>
Text marks
The edit, the flick, the highlight and the misregistration. Each one is text-safe: the words are straight and their contrast is unchanged.
-
Build it for the human user.
Edit
Where the imperfection is: The pink strike, the caret and the handwritten correction above the line.
Why it is free: The strike is an absolutely positioned SVG, and the space for the correction is reserved, so nothing reflows. Screen readers hear only the correction.
Markup
<span class="edit"> <span class="edit__old" aria-hidden="true">perfect<svg class="edit__strike" viewBox="0 0 200 24" preserveAspectRatio="none" focusable="false"><path d="…" /></svg></span> <svg class="edit__caret" viewBox="0 0 30 20" aria-hidden="true" focusable="false"><path d="…" /></svg> <span class="edit__new">human</span> </span> -
Perfect Imperfect
imperfect
Wordmark (pink flick)
Where the imperfection is: The pink flick under the name, and the kern gap in the large wordmark.
Why it is free: The flick is an SVG sized in em from the text box and painted with multiply. The name itself is plain text.
Markup
<span class="flick">Perfect Imperfect<svg class="flick__mark mark" viewBox="0 0 200 12" preserveAspectRatio="none" aria-hidden="true"><use href="#m-underline-a"></use></svg></span> -
Text on a highlight stays ink, on a yellow plate.
Highlight
Where the imperfection is: A yellow plate behind a phrase in a paragraph.
Why it is free: The plate is a paint-only background. The text uses the on-highlight ink, 14.2:1 in light and dark text on the dark highlight.
Markup
<p>Text on a <span class="hl">highlight</span> stays ink, on a yellow plate.</p> -
Large display type
Misregistration
Where the imperfection is: A hard pink offset under very large display type.
Why it is free: The shadow is capped by --misreg (1.5px), has no blur, and the ink text is unchanged, so the contrast is too.
Markup
<h1 class="display-misreg">Large display type</h1>
Prints, plates and notes
Surfaces that carry the imperfection. The tilt is on the sheet or the plate. The text and the controls are not rotated.
- LedgerPrintA two-ink sketch of a table of rows.
Print card
Where the imperfection is: The sheet: two inks, halftone dots, a misregistered second ink, a stepped top-right corner and a small settle when it loads.
Why it is free: It is a transform on a wrapper, and the caption stays straight. The button is the focusable element, and it has no rotation.
Markup
<div class="print" style="--spot:#FF48B0;--tilt:calc(var(--tilt-xs) * -1)"> <span class="print__sheet"><svg class="print__art" role="img" aria-label="A two-ink sketch of a table of rows.">…</svg></span> <span class="print__caption"> <span class="print__head"><span class="print__title">Ledger</span><span class="print__type mono">Print</span></span> <span class="print__line">A two-ink sketch of a table of rows.</span> </span> </div> -
Straight text, on a paper plate that is not.
Plate and index card
Where the imperfection is: The paper behind the text, slightly off square.
Why it is free: The plate is a ::before layer. The text sits on top of it, straight, so it never loses contrast or moves.
Markup
<div class="plate" style="--tilt:calc(var(--tilt-xs) * 1)"> <h3>Index card</h3> <p>The text is straight. Only the paper behind it turns.</p> </div> -
Start here if the screen feels off.
Margin note
Where the imperfection is: A small casual note with a hand-drawn arrow pointing at the item it annotates.
Why it is free: It is static text in a casual cut of the text font, with one SVG arrow. It moves into the margin only from 1100px, so it never causes a horizontal scroll.
Markup
<p class="note"> <svg class="note__arrow" viewBox="0 0 24 30" aria-hidden="true" focusable="false"><path d="M9 28 C7 19 11 12 17 5 M12 9 L17 4 L19 11" /></svg> <span>Start here if the screen feels off.</span> </p> -
Rule
Where the imperfection is: A hairline that stops 1px short of its neighbour.
Why it is free: A 1px pseudo-element with height calc(100% - 1px). Nothing reflows.
Markup
<div class="hairline-short" aria-hidden="true"></div> -
A repaired card.
Kintsugi repair
Where the imperfection is: A gold hairline across a repaired card.
Why it is free: It is a decorative SVG stroke. Gold is never used as text, so it has no contrast problem.
Markup
<div class="plate"> <p>A repaired card.</p> <svg class="card__repair" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true"> <path class="kintsugi-line" d="M99 0 L95 12 L100 26 L96 40 L100 56 L95 70 L99 86 L96 100" /> </svg> </div>
Perfect by rule
These never get an imperfection. They are here so the straight parts are documented too.
-
Focus ring
Where the imperfection is: Nothing. Every focus ring is a straight 3px line with a 3px gap, in the blue token.
Why it is free: Focus is how a keyboard user knows where they are. An imperfect ring would be a bug.
Markup
:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; } -
Theme toggle
Where the imperfection is: Nothing. The label names the theme it switches to, and the width is fixed.
Why it is free: The width is fixed, so the label never moves the layout. The choice is stored with a try/catch around localStorage.
Markup
<button type="button" class="theme-toggle" data-theme-toggle aria-label="Switch to dark theme">…</button> -
Dialog
Where the imperfection is: Nothing. A native dialog, straight and solid.
Why it is free: Escape closes it, focus returns to the button that opened it, and the backdrop is the only other way out.
Markup
<button type="button" class="btn btn--quiet" data-open-dialog="dlg-demo">Open the dialog</button> <dialog id="dlg-demo" class="dialog" aria-labelledby="dlg-demo-title"> <div class="dialog__inner"> <h3 id="dlg-demo-title">A perfect dialog</h3> <p>Escape closes it, and focus returns to the button.</p> <button type="button" class="btn btn--quiet" data-close>Close</button> </div> </dialog>
Next
The rules list what each component must never do. The use it page shows how to copy the CSS into a project.