Details
Interfaces that feel right rarely come from a single thing. They are small details that compound. Every ssntl/ui component goes through these twelve.
Based on “Details that make interfaces feel better” by Jakub Krehel (opens in a new tab)01 · Text that wraps well
text-balance spreads a title over even lines; text-pretty keeps a paragraph from ending on a lonely word. They go together: balance on the title, pretty on the description. ssntl/ui ships them in the base layer.
Designing interfaces that feel natural and intuitive
Great design is invisible. It guides people without them ever noticing.
Designing interfaces that feel natural and intuitive
Great design is invisible. It guides people without them ever noticing.
<h2 className="text-balance">…</h2>
<p className="text-pretty">…</p>02 · Concentric radii
A surface inside another takes the outer radius minus the gap between them, so the corners stay parallel. Move the controls: on the left the radius follows the formula, on the right it repeats and the inner corner looks swollen.
<div className="rounded-outer-lg p-concentric-2">
<div className="rounded-inner">…</div>
</div>03 · Icons that change with context
When one icon replaces another they cross-fade with opacity, scale and blur (0.25 → 1, 4px → 0) instead of jumping. Both stay in the DOM, so the swap has an enter and an exit. The copy button on this site does it.
<span className="icon-swap" data-swapped={copied}>
<Copy />
<Check />
</span>04 · Crisp text
On macOS text can render heavier than designed. antialiased on the layout thins it for the whole app. The difference only shows on Mac screens.
Subpixel rendering
Default font smoothing uses subpixel antialiasing on macOS.
Antialiased rendering
Grayscale antialiasing renders thinner, crisper text.
<body className="font-sans antialiased">…</body>05 · Tabular numbers
When a number changes, its digits should be the same width so nothing shifts. Count again and watch the right side: the width doesn't move.
Q 1,248,750
IncorrectQ 1,248,750
Correct<span className="tabular-nums">Q 1,248,750</span>06 · Interruptible animations
Transitions head to the latest state and can be interrupted; keyframes run on a fixed timeline and restart. Transitions for interactions, keyframes for sequences that run once. Click several times while it turns.
// Interaction: a transition retargets mid-way.
<div className="transition-[rotate] duration-(--duration-menu) ease-out" />07 · Staggered entrances
Instead of animating one block, the title, the description and the actions enter separately, 100ms apart. A title can split into words at 80ms. For rare entrances only, never for frequent interactions.
Publish your store today
Twelve products and a domain, ready.
Publish your store today
Twelve products and a domain, ready.
Publish your store today
Twelve products and a domain, ready.
<h2 className="animate-enter stagger-0">…</h2>
<p className="animate-enter stagger-1">…</p>
<div className="animate-enter stagger-2">…</div>
// By words: enter-words drops the step to 80ms.
<span className="inline-block animate-enter enter-words stagger-3">store</span>08 · Subtle exits
What leaves needs less attention than what arrives. Instead of sliding its full width, the panel travels 30% and lets opacity and blur do the rest.
// With Motion: the exit travels less than the entrance.
<motion.div
initial={{ x: "calc(-100% - 4px)" }}
animate={{ x: 0 }}
exit={{ x: "-30%", opacity: 0, filter: "blur(4px)" }}
transition={{ type: "spring", duration: 0.45, bounce: 0 }}
/>09 · Optical alignment
Sometimes centering by the numbers looks wrong. A button with an icon takes 4px less padding on the icon side, and the play triangle moves slightly right because its weight sits left. Button does it with data-icon.
<Button>
<Play data-icon="inline-start" />
Play
</Button>10 · Shadows instead of borders
A transparent three-layer shadow adds depth and takes the color of any background; a solid border keeps its grey and looks pasted on. In dark mode it reads as a 1px border. Hover the cards.
<div className="border-shadow hover:border-shadow-strong">…</div>11 · Image outlines
A 1px black (white in dark) outline at 10% gives images a consistent edge on any surface. Pure black or white: a tinted grey reads as dirt.
Incorrect
Correct<img className="image-outline" … />12 · Type scale
When supporting text is as big as the title, the reader has nowhere to start. Map each role to a step of the system's scale: heading 22, body 17, caption 13. Hierarchy comes from size, weight and color together.
Design for a quick glance
Alex Morgan · 12 June 2026 · 4 min read
People scan a page before they read it. A clear title tells them what to expect, a short introduction adds context, and the details answer their questions.
- Heading:
- 15px
- Supporting:
- 17px
- Paragraph:
- 17px
<h3 className="text-heading">Design for a quick glance</h3>
<p className="text-caption text-muted-foreground">Alex Morgan · 12 June 2026</p>
<p className="text-body text-muted-foreground">…</p>Install the utilities
border-shadow, image-outline, icon-swap and animate-enter come in one file. Concentric radii have their own.
/* npx shadcn@latest add @ssntl/details @ssntl/radius-concentric */
@import "../styles/ssntl/details.css";
@import "../styles/ssntl/radius-concentric.css";