Web UI Best Practices
When invoked generally, apply these opinionated constraints for building better interfaces and for any UI work in this conversation.
When invoked with a file specified (i.e. web-ui-best-practices <file>), review the file against all constraints below and output:
- •violations (quote the exact line/snippet)
- •why it matters (1 short sentence)
- •a concrete fix (code-level suggestion)
Stack
- •MUST use Tailwind CSS defaults unless custom values already exist or are explicitly requested
- •MUST use
motion/react(formerlyframer-motion) when JavaScript animation is required - •SHOULD use
tw-animate-cssfor entrance and micro-animations in Tailwind CSS - •MUST use
cnutility (clsx+tailwind-merge) for class logic
Components
- •MUST use accessible component primitives for anything with keyboard or focus behavior (
Base UI,Radix,React-Aria) - •MUST use the project’s existing component primitives first
- •NEVER mix primitive systems within the same interaction surface
- •SHOULD prefer
Base UIfor new primitives if compatible with the stack - •MUST add an
aria-labelto icon-only buttons - •NEVER rebuild keyboard or focus behavior by hand unless explicitly requested
Interaction
- •MUST use an
AlertDialogfor destructive or irreversible actions - •SHOULD use structural skeletons for loading states
- •NEVER use
h-screen, useh-dvh - •MUST show errors next to where the action happens
- •NEVER block paste in
inputortextareaelements
Navigation & State
- •SHOULD reflect UI state in the URL (filters, tabs, pagination, expanded panels via query params)
- •MUST use
<a>/<Link>for navigation (Cmd/Ctrl+click and middle-click should work) - •SHOULD deep-link stateful UI (if it uses
useState, consider URL sync vianuqsor similar) - •MUST require confirmation or undo window for destructive actions (never immediate)
Touch & Interaction
- •SHOULD set
touch-action: manipulationon tappable controls (prevents double-tap zoom delay) - •SHOULD set
-webkit-tap-highlight-colorintentionally - •MUST use
overscroll-behavior: containin modals/drawers/sheets - •SHOULD during drag: disable text selection,
inerton dragged elements - •SHOULD use
autoFocussparingly (desktop only, single primary input; avoid on mobile)
Animation
- •NEVER add animation unless it is explicitly requested
- •MUST animate only compositor props (
transform,opacity) - •NEVER animate layout properties (
width,height,top,left,margin,padding) - •SHOULD avoid animating paint properties (
background,color) except for small, local UI (text, icons) - •SHOULD use
ease-outon entrance - •NEVER exceed
200msfor interaction feedback - •MUST pause looping animations when off-screen
- •SHOULD respect
prefers-reduced-motion - •NEVER introduce custom easing curves unless explicitly requested
- •SHOULD avoid animating large images or full-screen surfaces
- •SHOULD never
transition: all—list properties explicitly - •SHOULD set correct
transform-origin - •SHOULD have animations be interruptible—respond to user input mid-animation
Typography
- •MUST use
text-balancefor headings andtext-prettyfor body/paragraphs - •MUST use
tabular-numsfor data - •SHOULD use
truncateorline-clampfor dense UI - •NEVER modify
letter-spacing(tracking-*) unless explicitly requested - •SHOULD use
…not... - •SHOULD use curly quotes
""not straight" - •SHOULD have non-breaking spaces:
10 MB,⌘ K, brand names - •SHOULD have loading states end with
…:"Loading…","Saving…" - •SHOULD use
font-variant-numeric: tabular-numsfor number columns/comparisons - •SHOULD have flex children need
min-w-0to allow text truncation - •SHOULD handle empty states—don't render broken UI for empty strings/arrays
- •SHOULD consider that user-generated input fields may have short, average, and very long inputs
Layout
- •MUST use a fixed
z-indexscale (no arbitraryz-*) - •SHOULD use
size-*for square elements instead ofw-*+h-*
Safe Areas & Layout
- •MUST respect
env(safe-area-inset-*)for full-bleed layouts and fixed elements on notched devices - •SHOULD avoid unwanted scrollbars (use
overflow-x-hiddenon containers and fix overflow at the source) - •SHOULD prefer flex/grid over JS measurement for layout
Dark Mode & Theming
- •MUST set
color-scheme: darkon<html>for dark themes (fixes scrollbars and native inputs) - •SHOULD set
<meta name="theme-color">to match the page background - •SHOULD set explicit
background-colorandcoloron native<select>(Windows dark mode)
Locale & i18n
- •MUST use
Intl.DateTimeFormat(no hardcoded date/time formats) - •MUST use
Intl.NumberFormat(no hardcoded number/currency formats) - •SHOULD detect language via
Accept-Language/navigator.languages, not IP
Hydration Safety
- •MUST provide
onChangefor inputs withvalue(or usedefaultValuefor uncontrolled inputs) - •MUST guard date/time rendering against hydration mismatches (server vs client)
- •SHOULD use
suppressHydrationWarningonly where truly needed
Hover & Interactive States
- •SHOULD provide
hover:states for buttons/links (visual feedback) - •SHOULD increase contrast for interactive states (hover/active/focus more prominent than rest)
Images
- •MUST set explicit
widthandheighton<img>(prevents CLS) - •SHOULD set
loading="lazy"for below-fold images - •SHOULD set
priorityorfetchpriority="high"for above-fold critical images
Performance
- •NEVER animate large
blur()orbackdrop-filtersurfaces - •NEVER apply
will-changeoutside an active animation - •NEVER use
useEffectfor anything that can be expressed as render logic - •SHOULD virtualize large lists (>50 items) (e.g.
virtuaorcontent-visibility: autowhere appropriate) - •MUST avoid layout reads during render (
getBoundingClientRect,offsetHeight,offsetWidth,scrollTop) - •SHOULD batch DOM reads/writes (avoid interleaving)
- •SHOULD prefer uncontrolled inputs; controlled inputs must be cheap per keystroke
- •SHOULD add
<link rel="preconnect">for CDN/asset domains - •SHOULD preload critical fonts (
<link rel="preload" as="font">) and usefont-display: swap
Design
- •NEVER use gradients unless explicitly requested
- •NEVER use purple or multicolor gradients
- •NEVER use glow effects as primary affordances
- •SHOULD use Tailwind CSS default shadow scale unless explicitly requested
- •MUST give empty states one clear next action
- •SHOULD limit accent color usage to one per view
- •SHOULD use existing theme or Tailwind CSS color tokens before introducing new ones
Design Direction (when aesthetics requested)
- •MUST choose a clear aesthetic direction and execute consistently (e.g., brutally minimal, editorial, utilitarian, playful)
- •SHOULD use deliberate typography (1 display + 1 body) and avoid accidental defaults; follow product font system when it exists
- •SHOULD define theme/palette via CSS variables; use one dominant base + sharp accent
- •SHOULD match implementation complexity to the aesthetic (minimal = restraint; maximal = elaborate but controlled)
- •If motion is explicitly requested: prefer one cohesive high-impact sequence (load stagger, scroll reveal) over scattered micro-interactions
- •SHOULD avoid cookie-cutter layout/component patterns; make 1-2 distinctive choices grounded in context
Content & Copy
- •SHOULD use active voice ("Install the CLI" not "The CLI will be installed")
- •SHOULD use specific button labels ("Save API Key" not "Continue")
- •SHOULD write error messages with a fix/next step, not just the problem
- •SHOULD write in second person; avoid first person
- •SHOULD use
&over "and" where space-constrained
Anti-patterns (flag these)
- •
user-scalable=noormaximum-scale=1disabling zoom - •
onPastewithpreventDefault - •
transition: all - •
outline-nonewithout afocus-visiblereplacement - •Inline
onClicknavigation without<a>/<Link> - •
<div>/<span>with click handlers (use<button>) - •Images without dimensions
- •Large arrays
.map()without virtualization - •Form inputs without labels
- •Icon buttons without
aria-label - •Hardcoded date/number formats (use
Intl.*) - •
autoFocuswithout clear justification
Rules if not found in primitives
The below rules apply if the pattern is either explicitly requested or not found in the primitive itself already.
Accessibility
- •Icon-only buttons need
aria-label - •Form controls need
<label>oraria-label - •Interactive elements need keyboard handlers (
onKeyDown/onKeyUp) - •
<button>for actions,<a>/<Link>for navigation (not<div onClick>) - •Images need
alt(oralt=""if decorative) - •Decorative icons need
aria-hidden="true" - •Async updates (toasts, validation) need
aria-live="polite" - •Use semantic HTML (
<button>,<a>,<label>,<table>) before ARIA - •Headings hierarchical
<h1>–<h6>; include skip link for main content - •
scroll-margin-topon heading anchors
Focus States
- •Interactive elements need visible focus:
focus-visible:ring-*or equivalent - •Never
outline-none/outline: nonewithout focus replacement - •Use
:focus-visibleover:focus(avoid focus ring on click) - •Group focus with
:focus-withinfor compound controls
Forms
- •Inputs need
autocompleteand meaningfulname - •Use correct
type(email,tel,url,number) andinputmode - •Never block paste (
onPaste+preventDefault) - •Labels clickable (
htmlForor wrapping control) - •Disable spellcheck on emails, codes, usernames (
spellCheck={false}) - •Checkboxes/radios: label + control share single hit target (no dead zones)
- •Submit button stays enabled until request starts; spinner during request
- •Errors inline next to fields; focus first error on submit
- •Placeholders end with
…and show example pattern - •
autocomplete="off"on non-auth fields to avoid password manager triggers - •Warn before navigation with unsaved changes (
beforeunloador router guard)