Concise rules for building accessible, fast, delightful UIs. Use MUST/SHOULD/NEVER to guide decisions.
- MUST: Use scalable design tokens from
src/theme/index.jsfor UI styling. - MUST: Avoid hardcoded values when a design token already exists.
- MUST: Avoid extending design tokens; use the existing token set.
- MUST: Use
theme({...})for any property supported by styled-system before using raw CSS declarations. - MUST: Treat the following as styled-system-supported and therefore
theme({...})-first properties: animation:animation,animationName,animationDuration,animationTimingFunction,animationDelay,animationIterationCount,animationDirection,animationFillMode,animationPlayState.background:background,backgroundImage,backgroundClip,backgroundSize,backgroundPosition,backgroundRepeat,backgroundAttachment; aliases:bgImage,bgClip,bgSize,bgPosition,bgRepeat,bgAttachment.color:color,backgroundColor,opacity,bg; alias:bgColor.typography:fontFamily,fontSize,fontStyle,fontWeight,letterSpacing,lineHeight,textAlign,textDecoration,textOverflow,textTransform,whiteSpace,wordBreak.layout:width,height,minWidth,maxWidth,minHeight,maxHeight,display,size,verticalAlign,overflow,overflowX,overflowY; aliases:w,h,minW,maxW,minH,maxH,d.space:margin,marginTop,marginRight,marginBottom,marginLeft,marginX,marginY,marginBlockStart,marginBlockEnd,marginInlineStart,marginInlineEnd,padding,paddingTop,paddingRight,paddingBottom,paddingLeft,paddingX,paddingY,paddingBlockStart,paddingBlockEnd,paddingInlineStart,paddingInlineEnd; aliases:m,mt,mr,mb,ml,mx,my,ms,me,p,pt,pr,pb,pl,px,py,ps,pe,marginStart,marginEnd,paddingStart,paddingEnd.flexbox:alignItems,alignContent,justifyItems,justifyContent,flexWrap,flexDirection,flex,flexFlow,flexGrow,flexShrink,flexBasis,justifySelf,alignSelf,order,placeItems,placeContent,placeSelf,gap; alias:flexDir.grid:gridGap,gap,gridRowGap,rowGap,gridColumnGap,columnGap,gridRow,gridColumn,gridAutoFlow,gridAutoRows,gridAutoColumns,gridTemplateRows,gridTemplateColumns,gridTemplateAreas,gridArea.border:border,borderWidth,borderStyle,borderColor,borderRadius,borderTop,borderTopLeftRadius,borderTopRightRadius,borderRight,borderBottom,borderBottomLeftRadius,borderBottomRightRadius,borderLeft,borderX,borderY,borderTopWidth,borderTopColor,borderTopStyle,borderBottomWidth,borderBottomColor,borderBottomStyle,borderLeftWidth,borderLeftColor,borderLeftStyle,borderRightWidth,borderRightColor,borderRightStyle.position:position,zIndex,top,right,bottom,left; alias:pos.shadow:boxShadow,textShadow.other:appearance,transform,transformOrigin,visibility,userSelect,pointerEvents,overflowWrap,boxSizing,cursor,resize,objectFit,objectPosition,float,fill,stroke,outline,outlineColor.transition:transition,transitionProperty,transitionDuration,transitionTiming,transitionDelay.- MUST: Decompose raw CSS declarations into styled-system keys whenever possible (for example use
py/pxinstead ofpadding,mt/mbinstead ofmargin, andborderBottom+borderBottomColorinstead of rawborder-bottom). - MUST: For border tokens, prefer tokenized border props over string interpolation:
borderBottom: 1+borderBottomColor: 'black05'(orborderColorif all sides share the same color). - MUST: Consolidate related tokenized properties into a single
theme({...})call per selector block (and per media block), instead of multiple adjacenttheme(...)calls. - MUST: Prefer responsive arrays/objects for styled-system props inside one
theme({...})call (for examplep: [3, 3, 4, 4]) instead of token-only media-query overrides. - MUST: Prefer semantic token references in
theme({...}), such asfontFamily: 'mono',fontWeight: 'bold',color: 'black',fontSize: 1,lineHeight: 0,letterSpacing: 0. - MUST: Apply the same rule inside styled components and inline
css={theme({...})}objects. - NEVER: Use raw token interpolation (for example
font-size: ${fontSizes[0]}) when the same style can be expressed viatheme({...}). - NEVER: Split tokenized style values across raw CSS and multiple
theme(...)calls when onetheme({...})object can express them. - NEVER: Add media queries only to change styled-system token values that can be expressed as responsive arrays/objects in
theme({...}). - MUST: When a component needs responsive visibility, use responsive arrays (e.g.
display: ["none", "flex"]) instead of hand-writing@mediablocks that only changedisplay. Forposition: fixed/absoluteelements, always state the targetdisplayvalue explicitly (e.g."flex","block") becauseinheritresolves against the DOM parent, not the visual stacking context, and fixed/absolute elements are out of normal flow. - SHOULD: Keep raw CSS only for unsupported/states-only patterns (for example keyframes, browser-specific values, or dynamic runtime computed styles).
- MUST: Full keyboard support per WAI-ARIA APG
- MUST: Visible focus rings (
:focus-visible; group with:focus-within) - MUST: Manage focus (trap, move, return) per APG patterns
- NEVER:
outline: nonewithout visible focus replacement
- MUST: Hit target ≥24px (mobile ≥44px); if visual <24px, expand hit area
- MUST: Mobile
<input>font-size ≥16px to prevent iOS zoom - NEVER: Disable browser zoom (
user-scalable=no,maximum-scale=1) - MUST:
touch-action: manipulationto prevent double-tap zoom - SHOULD: Set
-webkit-tap-highlight-colorto match design
- MUST: Hydration-safe inputs (no lost focus/value)
- NEVER: Block paste in
<input>/<textarea> - MUST: Loading buttons show spinner and keep original label
- MUST: Enter submits focused input; in
<textarea>, ⌘/Ctrl+Enter submits - MUST: Keep submit enabled until request starts; then disable with spinner
- MUST: Accept free text, validate after—don't block typing
- MUST: Allow incomplete form submission to surface validation
- MUST: Errors inline next to fields; on submit, focus first error
- MUST:
autocomplete+ meaningfulname; correcttypeandinputmode - SHOULD: Disable spellcheck for emails/codes/usernames
- SHOULD: Placeholders end with
…and show example pattern - MUST: Warn on unsaved changes before navigation
- MUST: Compatible with password managers & 2FA; allow pasting codes
- MUST: Trim values to handle text expansion trailing spaces
- MUST: No dead zones on checkboxes/radios; label+control share one hit target
- MUST: URL reflects state (deep-link filters/tabs/pagination/expanded panels)
- MUST: Back/Forward restores scroll position
- MUST: Links use
<a>/<Link>for navigation (support Cmd/Ctrl/middle-click) - NEVER: Use
<div onClick>for navigation
- SHOULD: Optimistic UI; reconcile on response; on failure rollback or offer Undo
- MUST: Confirm destructive actions or provide Undo window
- MUST: Use polite
aria-livefor toasts/inline validation - SHOULD: Ellipsis (
…) for options opening follow-ups ("Rename…") and loading states ("Loading…")
- MUST: Generous targets, clear affordances; avoid finicky interactions
- MUST: Delay first tooltip; subsequent peers instant
- MUST:
overscroll-behavior: containin modals/drawers - MUST: During drag, disable text selection and set
inerton dragged elements - MUST: If it looks clickable, it must be clickable
- SHOULD: Autofocus on desktop with single primary input; rarely on mobile
- MUST: Honor
prefers-reduced-motion(provide reduced variant or disable) - SHOULD: Prefer CSS > Web Animations API > JS libraries
- MUST: Animate compositor-friendly props (
transform,opacity) only - NEVER: Animate layout props (
top,left,width,height) - NEVER:
transition: all—list properties explicitly - SHOULD: Animate only to clarify cause/effect or add deliberate delight
- SHOULD: Choose easing to match the change (size/distance/trigger)
- MUST: Animations interruptible and input-driven (no autoplay)
- MUST: Correct
transform-origin(motion starts where it "physically" should) - MUST: SVG transforms on
<g>wrapper withtransform-box: fill-box
- SHOULD: Optical alignment; adjust ±1px when perception beats geometry
- MUST: Deliberate alignment to grid/baseline/edges—no accidental placement
- SHOULD: Balance icon/text lockups (weight/size/spacing/color)
- MUST: Verify mobile, laptop, ultra-wide (simulate ultra-wide at 50% zoom)
- MUST: Respect safe areas (
env(safe-area-inset-*)) - MUST: Avoid unwanted scrollbars; fix overflows
- SHOULD: Flex/grid over JS measurement for layout
- SHOULD: Inline help first; tooltips last resort
- MUST: Skeletons mirror final content to avoid layout shift
- MUST:
<title>matches current context - MUST: No dead ends; always offer next step/recovery
- MUST: Design empty/sparse/dense/error states
- SHOULD: Curly quotes (" "); avoid widows/orphans (
text-wrap: balance) - MUST:
font-variant-numeric: tabular-numsfor number comparisons - MUST: Redundant status cues (not color-only); icons have text labels
- MUST: Accessible names exist even when visuals omit labels
- MUST: Use
…character (not...) - MUST:
scroll-margin-topon headings; "Skip to content" link; hierarchical<h1>–<h6> - MUST: Resilient to user-generated content (short/avg/very long)
- MUST: Locale-aware dates/times/numbers (
Intl.DateTimeFormat,Intl.NumberFormat) - MUST: Accurate
aria-label; decorative elementsaria-hidden - MUST: Icon-only buttons have descriptive
aria-label - MUST: Prefer native semantics (
button,a,label,table) before ARIA - MUST: Non-breaking spaces:
10 MB,⌘ K, brand names
- MUST: Text containers handle long content (
truncate,line-clamp-*,break-words) - MUST: Flex children need
min-w-0to allow truncation - MUST: Handle empty states—no broken UI for empty strings/arrays
- SHOULD: Test iOS Low Power Mode and macOS Safari
- MUST: Measure reliably (disable extensions that skew runtime)
- MUST: Track and minimize re-renders (React DevTools/React Scan)
- MUST: Profile with CPU/network throttling
- MUST: Batch layout reads/writes; avoid reflows/repaints
- MUST: Mutations (
POST/PATCH/DELETE) target <500ms - SHOULD: Prefer uncontrolled inputs; controlled inputs cheap per keystroke
- MUST: Virtualize large lists (>50 items)
- MUST: Preload above-fold images; lazy-load the rest
- MUST: Prevent CLS (explicit image dimensions)
- SHOULD:
<link rel="preconnect">for CDN domains - SHOULD: Critical fonts:
<link rel="preload" as="font">withfont-display: swap
- MUST: Use existing design tokens from
src/theme/index.js(do not add new tokens for task-specific styling). - MUST:
color-scheme: darkon<html>for dark themes - SHOULD:
<meta name="theme-color">matches page background - MUST: Native
<select>: explicitbackground-colorandcolor(Windows fix)
- MUST: Inputs with
valueneedonChange(or usedefaultValue) - SHOULD: Guard date/time rendering against hydration mismatch
- SHOULD: Layered shadows (ambient + direct)
- SHOULD: Crisp edges via semi-transparent borders + shadows
- SHOULD: Nested radii: child ≤ parent; concentric
- SHOULD: Hue consistency: tint borders/shadows/text toward bg hue
- MUST: Accessible charts (color-blind-friendly palettes)
- MUST: Meet contrast—prefer APCA over WCAG 2
- MUST: Increase contrast on
:hover/:active/:focus - SHOULD: Match browser UI to bg
- SHOULD: Avoid dark color gradient banding (use background images when needed)