Welcome to Vanilla Breeze
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
Small visual and motion details that make Vanilla Breeze interfaces feel considered. Most are already in the framework — this page makes them legible and adds the few that weren't.
Jakub Krehel's Details that make interfaces feel better catalogues a dozen small techniques that lift the perceived quality of an interface without adding features. Cross-referenced against Vanilla Breeze, most were already shipped — the framework's reset and tokens already handle balanced text, tabular numerals, smoothed text, staggered entry, separate enter/exit timing, and reduced-motion respect.
This page does two things: it makes the existing primitives findable, and it adds the few details that weren't in VB yet.
| Detail | What VB does |
|---|---|
text-wrap: balance on headings |
Applied globally on h1–h6 and lead paragraphs in src/native-elements/headings/styles.css and paragraph/styles.css. Headings distribute words evenly across lines without per-page tuning. |
text-wrap: pretty on body prose |
Default on p and inside hgroup. Browsers prevent single-word orphans on the last line. |
font-variant-numeric: tabular-nums on <time> |
Default on every <time> element via src/native-elements/inline-semantics/styles.css. Updating digits don't cause horizontal jitter in dates, durations, version strings, or relative-time labels. |
-webkit-font-smoothing: antialiased |
Set in the global reset (src/base/reset.css). Text reads slightly thinner and crisper on macOS without per-component overrides. |
| Staggered entry animations | --motion-stagger-delay in src/tokens/extensions/motion-fx.css. Applied via animation-delay: calc(var(--i) * var(--motion-stagger-delay)) on list items. |
| Subdued exit timing | --motion-exit-duration: 200ms ships separately from --motion-enter-duration: 300ms, so exits feel quicker and less attention-grabbing than entrances. |
| Reduced-motion respect | Both the OS-level prefers-reduced-motion: reduce and the user-controlled :root[data-motion-reduced] attribute collapse stagger, hover lift, and enter/exit durations to zero. Set automatically by <settings-panel>. |
An outer container's border-radius and the radius of an element nested inside it should look concentric: the inner radius equals the outer radius minus the padding between them. VB ships a CSS helper that derives the inner value automatically.
/* In src/tokens/borders.css */--radius-inner: max(0px, calc(var(--_radius, var(--radius-m)) - var(--_inset, 0px)));
Parents declare their own radius in --_radius and their padding in --_inset. Children that should hug the parent's inner edge use border-radius: var(--radius-inner).
<article style=" --_radius: var(--radius-l); --_inset: var(--size-m); border-radius: var(--_radius); padding: var(--_inset); background: var(--color-surface-raised);"> <button style="border-radius: var(--radius-inner);">Inner button hugs the card edge</button></article>
A 1-pixel outline with outline-offset: -1px sits inside the image edge and gives photographs definition on light or transparent backgrounds without changing layout (which a real border would). Opt-in via data-outlined so existing imagery is unchanged.
<img src="/photo.jpg" alt="…" data-outlined> <!-- Or apply once to a <picture> wrapper --><picture data-outlined> <img src="/photo.jpg" alt="…"></picture>
The line uses color-mix(in oklch, currentColor 8%, transparent) so it adapts to the surrounding text colour and works equally well in light and dark themes.
--shadow-flush)For raised surfaces — cards, popovers, code blocks — a layered shadow reads as edge definition and a touch of elevation, where a flat border: 1px solid reads as just an outline.
/* In src/tokens/shadows.css */--shadow-flush: 0 0 0 1px hsl(0 0% 0% / 0.04), 0 1px 2px hsl(0 0% 0% / 0.06);
.card { background: var(--color-surface-raised); border-radius: var(--radius-m); box-shadow: var(--shadow-flush);}
Stack with --shadow-md or --shadow-lg to combine edge + lift: box-shadow: var(--shadow-flush), var(--shadow-md);.
A blur + scale + opacity entrance reads softer than a hard scale-in. Useful for icons, thumbnails, or any element that should feel like it focused into place rather than slid. Pair the symmetric vb-blur-out when you need an exit.
<img src="/feature.jpg" alt="…" data-animate="blur-in">
icon-wc { animation: vb-blur-in var(--motion-enter-duration) var(--motion-ease-out) both;}
Both honour reduced-motion: the keyframe still runs but at --motion-enter-duration: 0ms, which collapses to an immediate snap.
This is the article's most important subtle point and worth restating as a VB convention:
VB follows this in practice: <details>, <dialog>, hover lift, focus ring, and form-field hint reveal all use transitions. vb-fade-in, vb-pop, vb-blur-in, and the spinner are keyframe animations. When you reach for one or the other in your own code, the same rule keeps interfaces from feeling brittle.
No primitive can decide whether an icon needs margin-block-start: 1px to look centred next to text, or whether a triangle needs to shift right of its bounding box to feel balanced. The framework gives you predictable defaults so the cases that need optical adjustment stand out clearly. When they do, override with a small inline style or a one-off CSS rule. Resist the urge to invent a token for it — tokens are for repeating patterns, not per-element vibes.
data-effect values