# Craft Details Guide Implementation details that separate polished products from rough ones. Most AI models miss these. --- ## 1. Focus States Focus states are for keyboard navigation. Get them wrong and your app feels broken. ### The Rule: `:focus-visible`, Not `:focus` ```css /* ❌ Shows focus ring on mouse click — annoying */ .button:focus { outline: 2px solid var(--primary); } /* ✅ Shows focus ring only on keyboard navigation */ .button:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; } ``` **Why:** `:focus` triggers on any focus (including mouse click). `:focus-visible` only triggers when user is navigating with keyboard. ### Never Remove Focus Without Replacement ```css /* ❌ NEVER — breaks keyboard navigation */ .button:focus { outline: none; } /* ✅ Replace default with custom visible focus */ .button:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--primary); } ``` ### Compound Controls: `:focus-within` For groups where focus on any child should highlight the parent: ```css /* Search input with icon */ .search-wrapper:focus-within { border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-tint); } ``` --- ## 2. Forms Forms are where users struggle most. These details reduce friction. ### Input Types and Attributes ```html ``` ### Autocomplete Matters | Field | `autocomplete` value | |-------|---------------------| | Email | `email` | | Password (login) | `current-password` | | Password (signup) | `new-password` | | Name | `name` | | Phone | `tel` | | Address | `street-address` | | Credit card | `cc-number`, `cc-exp`, `cc-csc` | | Non-auth fields | `off` (prevents password manager triggers) | ### Never Block Paste ```jsx /* ❌ NEVER — accessibility violation, user hostile */ e.preventDefault()} /> /* ✅ Let users paste */ ``` ### Disable Spellcheck Where Appropriate ```html ``` ### Labels and Hit Targets ```html ``` ```css /* No dead zones between checkbox and label */ .checkbox-wrapper { display: flex; align-items: center; gap: 8px; cursor: pointer; } ``` ### Placeholder Formatting ```html ``` ### Submit Button States ```jsx /* ✅ Button enabled until request starts, then shows spinner */ ``` ### Error Handling ```jsx /* ✅ Errors inline, focus first error on submit */
{errors.email && ( {errors.email} )}
``` ### Unsaved Changes Warning ```js // Warn before leaving with unsaved changes useEffect(() => { const handleBeforeUnload = (e) => { if (hasUnsavedChanges) { e.preventDefault(); e.returnValue = ''; } }; window.addEventListener('beforeunload', handleBeforeUnload); return () => window.removeEventListener('beforeunload', handleBeforeUnload); }, [hasUnsavedChanges]); ``` --- ## 3. Images Images are the #1 cause of layout shift (CLS). Fix them. ### Always Set Dimensions ```html Product Product ``` ### Loading Strategy ```html Hero Feature ``` ### In React/Next.js ```jsx // Critical hero image Hero // Below fold Feature ``` --- ## 4. Touch & Mobile Details that make mobile feel native. ### Tap Delay Removal ```css /* Remove 300ms tap delay on mobile */ * { touch-action: manipulation; } ``` ### Tap Highlight ```css /* Set intentionally, don't just disable */ button, a { -webkit-tap-highlight-color: rgba(0, 0, 0, 0.1); } ``` ### Modal Scroll Lock ```css /* Prevent scroll chaining in modals/drawers */ .modal, .drawer, .sheet { overscroll-behavior: contain; } ``` ### AutoFocus Rules - Desktop only — avoid on mobile (opens keyboard unexpectedly) - Single primary input per page maximum - Must be justified — not "just because" ```jsx /* ✅ Desktop-only autofocus */ ``` --- ## 5. Performance Patterns Patterns that prevent jank. ### Virtualize Large Lists Lists with 50+ items should be virtualized: ```jsx // Use virtua, react-window, or similar import { VList } from 'virtua'; {items.map(item => )} ``` **Or CSS-only for simpler cases:** ```css .long-list { content-visibility: auto; contain-intrinsic-size: 0 50px; /* estimated item height */ } ``` ### Avoid Layout Reads in Render ```jsx /* ❌ Forces synchronous layout recalculation */ function Component() { const height = elementRef.current.offsetHeight; // BAD return
; } /* ✅ Use ResizeObserver or CSS */ function Component() { const [height, setHeight] = useState(0); useLayoutEffect(() => { const observer = new ResizeObserver(([entry]) => { setHeight(entry.contentRect.height); }); observer.observe(elementRef.current); return () => observer.disconnect(); }, []); return
; } ``` ### Uncontrolled Inputs When Possible ```jsx /* ❌ Re-renders on every keystroke */ const [value, setValue] = useState(''); setValue(e.target.value)} /> /* ✅ No re-renders during typing */ /* Get value on submit */ const handleSubmit = () => { const value = inputRef.current.value; }; ``` ### Preconnect to CDNs ```html ``` --- ## 6. Accessibility Quick Wins High-impact, low-effort accessibility fixes. ### Semantic Elements ```html
Click me
Go here Go here ``` ### Keyboard Handlers Interactive custom elements need keyboard support: ```jsx
{ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleClick(); } }} > Custom button
``` ### Heading Hierarchy ```html

Page Title

Section

Subsection

Another Section

Page Title

Section

``` ### Scroll Margin for Anchors ```css /* Prevents fixed header from covering anchor targets */ [id] { scroll-margin-top: 80px; /* height of fixed header + buffer */ } ``` ### Async Updates Need Announcements ```jsx /* Toast notifications, validation messages */
{message}
``` --- ## 7. Navigation & State URL should reflect app state. Users expect to bookmark, share, and use back button. ### URL Reflects State ```jsx /* ✅ Filters, tabs, pagination in URL */ // /products?category=shoes&sort=price&page=2 /* Use nuqs or similar for easy URL state sync */ import { useQueryState } from 'nuqs'; const [category, setCategory] = useQueryState('category'); ``` ### Links Support Browser Features ```jsx /* ❌ onClick navigation — breaks Cmd+click, middle-click */
navigate('/page')}>Go
/* ✅ Proper link — all browser features work */ Go ``` ### Destructive Actions Need Confirmation ```jsx /* ❌ Immediate destructive action */ /* ✅ With confirmation */ /* Or with undo window */ // Toast: "Account deleted. Undo (10s)" ``` --- ## 8. Content Copy Rules Writing that converts. | Rule | Example | |------|---------| | **Active voice** | "Install the CLI" not "The CLI will be installed" | | **Title Case for headings/buttons** | "Save Changes" not "Save changes" | | **Numerals for counts** | "8 deployments" not "eight deployments" | | **Specific labels** | "Save API Key" not "Continue" | | **Error messages include fix** | "Email invalid. Use format: name@domain.com" | | **Second person** | "Your account" not "My account" | | **& over "and"** (space-constrained) | "Terms & Privacy" | --- ## 9. Anti-Patterns Checklist Flag these during code review: - [ ] `user-scalable=no` or `maximum-scale=1` — disables zoom, accessibility violation - [ ] `onPaste` with `preventDefault` — blocks paste, user hostile - [ ] `transition: all` — performance killer, unpredictable - [ ] `outline: none` without `:focus-visible` replacement — breaks keyboard nav - [ ] `
` for navigation — use `` or `` - [ ] `
` or `` as buttons — use `