Patterns

Component decision guide - when to reach for which piece, with live examples of each pattern in context.

Select vs. Dropdown Menu#

Both open a list on click - but they model different interactions. Select carries a value (part of a form, trigger echoes the selection). Dropdown Menu triggers actions (commands run, trigger stays fixed).

Select - picking a value

Click an option - the trigger updates to echo your choice. Correct for plan, country, sort order, or any form field.

Dropdown Menu - triggering actions

The trigger always reads "My account". Clicking an item runs a command - nothing is stored in the trigger.

Don't - Dropdown for value selection

Click any option. The trigger stays "Plan" - users can't tell what's selected, and the value can't be read as a form field.

Quick smell test: should the trigger echo whatever was picked? Use Select. Should it stay the same label regardless? Use Dropdown Menu.

Dialog vs. Toast#

Dialog demands a decision - it blocks the page until the user acts. Toast is a passive observer - it confirms what already happened and dismisses itself.

Dialog - requires a response

A destructive action needs explicit confirmation. The page blocks until the user decides - cancel or delete.

Toast - status feedback

The save already happened. Toast confirms it without interrupting the user's flow - it disappears on its own.

Never use a Toast to report an error the user must act on - those get missed. Use a Dialog, or inline validation next to the field instead.

Tooltip - and when to escalate#

Tooltips are supplementary labels, not content containers - they follow the WAI-ARIA tooltip role strictly. Hover the toolbar: the bubble glides between triggers via TooltipGroup.

Tooltip - labelling icon controls

Hover any button. Labels clarify icons without adding visual weight to the layout.

  • Never put a link, button, or form inside a Tooltip - screen readers won’t reach it and pointer users can’t reliably click it.
  • If the overlay needs to be clickable, use a Dropdown Menu (supports hover open).
  • If it needs a form or a destructive action, escalate to a Dialog.

Details that make interfaces feel better#

Sourced from Details that make interfaces feel better by Jakub Krehel. Worth reading in full.

No single detail here is a feature - each one is a fraction of a second, a pixel, a rendering quirk. Stacked, they’re the difference between a UI that was built and one that was designed.

Tabular numbers
0

Proportional

0

Tabular

Watch the left counter: proportional digits shift width as values change (narrow 1 vs wide 9). The right stays locked. font-variant-numeric: tabular-nums makes every digit equal-width.

Concentric border radius
content

Concentric ✓

content

Mismatched ✗

outer radius − gap = inner radius. With p-2 (8px) and a rounded-2xl (16px) wrapper, the inner surface wants rounded-lg (8px). Mismatching makes corners look accidental.

Animate icons on entry

Opacity only

Opacity + scale

Hover each button. Opacity-only pops the icon in abruptly. Adding scale makes the entry feel physical - the icon grows into place rather than materialising out of nowhere.

Shadows over solid borders

Solid border

Layered shadows

Layered box-shadows with rgba transparency adapt to any background. Solid borders assume a fixed surface colour; shadows cooperate with whatever is underneath.

Image outlines

Without

With 1px inset outline

A 1px inset outline at 10% opacity defines the edge of images and gradients against any background - especially on light-on-light or dark-on-dark surfaces where the boundary would otherwise dissolve.

Text wrap: balance

Default

Ship better products faster

text-balance

Ship better products faster

Distributes a heading's text evenly across lines, preventing a short orphaned word on the last line. Pair with text-wrap: pretty on body copy.

Font smoothing

Ag

subpixel

Ag

antialiased

-webkit-font-smoothing: antialiased switches macOS from subpixel to grayscale rendering - slightly thinner, crisper strokes. Apply once on the root; already set in globals.css.

Interruptible animations

Keyframe - hover out mid-way

Transition - hover out mid-way

Hover out of each bar mid-way. The keyframe bar snaps back to zero - it can't be interrupted. The transition bar reverses smoothly from wherever it is.

Staggered enter, subtle exit
Typography
Spacing
Color
Motion

Items cascade in with 75 ms delays - enter takes 350 ms. Exit fires all at once in 180 ms with no stagger. Exits should be faster and quieter than entrances.

Optical alignment
Button

A trailing arrow shifts the button's visual weight right - equal padding (24/24) looks off-balance. Reducing the right side to 18px restores the feel. Toggle Show Padding to measure.

Built on the principles of make-interfaces-feel-better, the skill by Jakub Krehel that we recommend installing alongside Koala.