frontcraft
The definitive frontend craft specification. 324 rules across 20 categories, each with a measurable threshold, priority level, and production code. Not opinions — standards.
How to Use This Skill
When implementing or reviewing frontend code, reference these rules by prefix (e.g., motion-duration-cap, a11y-contrast-aa). Each rule has:
- Name: Kebab-case identifier for automated detection
- Priority: CRITICAL (breaks UX if violated), HIGH (noticeable degradation), MEDIUM (polish issue), LOW (refinement)
- Threshold: A measurable number or condition
- Implementation: Production-ready code
Output format for findings: rule-name | priority | file:line | description
1. Motion & Timing
motion-duration-cap
Priority: CRITICAL Threshold: No transition or animation exceeds 500ms. Most should be 150-300ms.
/* PASS */ transition: transform 200ms ease-out;
/* FAIL */ transition: transform 800ms ease;
motion-duration-minimum
Priority: HIGH Threshold: No transition below 100ms. Below that, the eye cannot perceive the change and the animation wastes GPU cycles for nothing.
/* PASS */ transition: opacity 120ms ease-out;
/* FAIL */ transition: opacity 50ms ease;
motion-enter-exit-asymmetry
Priority: HIGH Threshold: Entry animations should be 1.5-2x longer than exits. Users wait for entrances but want exits instant.
.panel-enter { animation-duration: 250ms; }
.panel-exit { animation-duration: 150ms; }
motion-stagger-increment
Priority: MEDIUM Threshold: Stagger delay between list items: 30-80ms. Below 30ms looks simultaneous. Above 80ms feels sluggish.
.item:nth-child(1) { animation-delay: 0ms; }
.item:nth-child(2) { animation-delay: 50ms; }
.item:nth-child(3) { animation-delay: 100ms; }
motion-stagger-cap
Priority: HIGH Threshold: Total stagger sequence must not exceed 600ms regardless of item count. Cap at 8-10 items, then batch the rest.
const delay = Math.min(index * 50, 400);
motion-spring-stiffness
Priority: MEDIUM Threshold: Spring stiffness 100-300 for UI elements. Below 100 feels mushy. Above 300 feels mechanical.
{ type: "spring", stiffness: 200, damping: 20 }
motion-spring-damping-ratio
Priority: HIGH Threshold: Damping ratio 0.6-0.9 for UI. Below 0.6 bounces too much. Above 0.9 loses the spring character.
// damping ratio = damping / (2 * sqrt(stiffness * mass))
{ stiffness: 200, damping: 22 } // ratio ~ 0.78
motion-spring-for-gestures
Priority: CRITICAL Threshold: Always use spring animations for gesture-driven interactions (drag, swipe, pinch). Easing curves create jarring velocity discontinuities on release.
// PASS: spring continues from gesture velocity
animate(x, target, { type: "spring", velocity: gestureVelocity });
// FAIL: easing ignores release velocity
animate(x, target, { duration: 300, ease: "ease-out" });
motion-ease-out-for-entries
Priority: HIGH Threshold: Elements entering the viewport use ease-out (decelerating). Elements leaving use ease-in (accelerating). Never use ease-in for entries.
.entering { animation-timing-function: cubic-bezier(0, 0, 0.2, 1); }
.exiting { animation-timing-function: cubic-bezier(0.4, 0, 1, 1); }
motion-no-linear-transitions
Priority: HIGH Threshold: Never use linear for UI state transitions. Linear has no acceleration curve and feels robotic. Reserve linear only for continuous animations like spinners or marquees.
/* PASS */ transition: opacity 200ms ease-out;
/* PASS - continuous rotation */ animation: spin 1s linear infinite;
/* FAIL */ transition: transform 300ms linear;
motion-opacity-with-transform
Priority: MEDIUM Threshold: Fade-ins should pair opacity with a subtle transform (translateY 8-16px or scale 0.95-0.98). Opacity alone feels flat.
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
motion-gpu-compositing
Priority: CRITICAL Threshold: Only animate transform and opacity. These run on the compositor thread. Animating width, height, top, left, margin, padding triggers layout/paint and drops frames.
/* PASS */ transition: transform 200ms, opacity 200ms;
/* FAIL */ transition: width 200ms, height 200ms;
motion-will-change-sparingly
Priority: MEDIUM Threshold: Only apply will-change to elements that will animate within the next 200ms. Never set it permanently. It forces GPU layer creation and eats VRAM.
.card:hover { will-change: transform; }
motion-no-layout-thrash
Priority: CRITICAL Threshold: Never read layout properties (offsetHeight, getBoundingClientRect) then write styles in the same synchronous block. Batch reads before writes.
// PASS - batched
const heights = elements.map(el => el.offsetHeight);
elements.forEach((el, i) => { el.style.height = heights[i] + 10 + 'px'; });
motion-cancel-on-interrupt
Priority: HIGH Threshold: If a user triggers a new animation on an already-animating element, cancel or blend the current animation. Never queue.
controller.cancel();
element.animate(newKeyframes, options);
motion-scroll-driven-performance
Priority: HIGH Threshold: Scroll-linked animations must use animation-timeline: scroll() or Intersection Observer. Never use scroll event listeners with direct style manipulation.
.hero { animation: parallax linear; animation-timeline: scroll(); }
motion-reduced-motion-override
Priority: CRITICAL Threshold: All animations must respect prefers-reduced-motion: reduce. Replace motion with instant state changes or subtle opacity fades (max 150ms).
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
motion-exit-before-enter
Priority: HIGH Threshold: When swapping content, exit old before entering new. Simultaneous exit/enter creates visual noise.
motion-transform-origin-intent
Priority: MEDIUM Threshold: Set transform-origin to match interaction source. Dropdown from button: origin top. Modal from center: origin center.
.dropdown { transform-origin: top center; }
.modal { transform-origin: center center; }
motion-skeleton-pulse-timing
Priority: MEDIUM Threshold: Skeleton pulse: 1.5-2s duration, ease-in-out, infinite. Faster feels anxious. Slower feels broken.
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
.skeleton { animation: pulse 1.8s ease-in-out infinite; }
motion-spinner-minimum-display
Priority: HIGH Threshold: If a spinner is shown, display for minimum 400ms. Sub-400ms flash looks like a glitch.
await Promise.all([fetchData(), sleep(400)]);
motion-no-bounce-on-data
Priority: HIGH Threshold: Never use bouncy/elastic animations on data-carrying elements (numbers, charts, progress bars). Bounce implies imprecision.
motion-page-transition-direction
Priority: MEDIUM Threshold: Forward navigation slides left/up. Back navigation slides right/down. Consistent spatial mapping.
motion-hover-delay
Priority: MEDIUM Threshold: Hover-triggered tooltips: 200-400ms enter delay, 150ms exit delay. Prevents accidental triggers.
.tooltip { transition: opacity 150ms ease-out 300ms; }
.trigger:hover .tooltip { transition-delay: 0ms; }
motion-scroll-snap-deceleration
Priority: MEDIUM Threshold: scroll-snap-type: x mandatory for carousels, proximity for content. Mandatory on content pages traps users.
motion-progress-animation
Priority: HIGH Threshold: Progress bars must animate continuously during indeterminate states. Frozen progress bar signals crash.
2. Typography
type-body-size-minimum
Priority: CRITICAL Threshold: Body text minimum 16px (1rem). Below 16px fails readability on mobile.
body { font-size: 16px; }
type-line-height-ratio
Priority: CRITICAL Threshold: Body line-height: 1.5-1.7. Headings: 1.1-1.3. Below 1.4 for body causes line collision on mobile.
p { line-height: 1.6; } h1, h2, h3 { line-height: 1.2; }
type-measure-cap
Priority: HIGH Threshold: Maximum line length: 45-75 characters. Optimal: 65ch. Beyond 75ch, eye tracking fails.
.prose { max-width: 65ch; }
type-heading-scale
Priority: HIGH Threshold: Consistent type scale. 1.2 (minor third) for compact, 1.25 (major third) for general, 1.333 (perfect fourth) for editorial.
:root { --step-0: 1rem; --step-1: 1.25rem; --step-2: 1.563rem; --step-3: 1.953rem; --step-4: 2.441rem; }
type-paragraph-spacing
Priority: MEDIUM Threshold: Space between paragraphs: 0.75em-1.25em. Never margin-top on first paragraph.
p + p { margin-top: 1em; }
type-font-loading-swap
Priority: CRITICAL Threshold: Always use font-display: swap or optional. Never block rendering on font load.
@font-face { font-family: 'Custom'; src: url('font.woff2'); font-display: swap; }
type-font-loading-preload
Priority: HIGH Threshold: Preload critical fonts in head. Reduces FOUT by 200-500ms.
<link rel="preload" href="/fonts/body.woff2" as="font" type="font/woff2" crossorigin>
type-system-font-stack
Priority: MEDIUM Threshold: Full system font stack, not just sans-serif.
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
type-tabular-nums-for-data
Priority: CRITICAL Threshold: tabular-nums for changing numbers. Proportional figures cause layout shift.
.price, .timer, .stat, td { font-variant-numeric: tabular-nums; }
type-no-orphans
Priority: MEDIUM Threshold: Prevent single-word orphans on headings.
h1, h2, h3 { text-wrap: balance; }
type-truncation-with-title
Priority: HIGH Threshold: Truncated text must have full text via title attribute or tooltip.
<span class="truncate" title="Full text here">Full text h...</span>
type-responsive-sizing
Priority: HIGH Threshold: clamp() for fluid typography. Never viewport units alone.
h1 { font-size: clamp(1.75rem, 1.2rem + 2vw, 3rem); }
type-weight-hierarchy
Priority: HIGH Threshold: Maximum 3 font weights per page. More muddles hierarchy.
type-letter-spacing-uppercase
Priority: HIGH Threshold: Uppercase text needs 0.02em-0.08em letter-spacing.
.label { text-transform: uppercase; letter-spacing: 0.05em; }
type-no-justify
Priority: HIGH Threshold: Never text-align: justify without hyphens: auto. Creates rivers of whitespace.
type-code-font-size
Priority: MEDIUM Threshold: Inline code: 0.875em of surrounding text. Monospace renders optically larger.
type-subpixel-antialiasing
Priority: MEDIUM Threshold: -webkit-font-smoothing: antialiased for light text on dark backgrounds.
.dark-bg { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
type-vertical-rhythm
Priority: MEDIUM Threshold: All spacing multiples of base line-height unit. Body 16px/1.5 = 24px unit.
type-heading-proximity
Priority: HIGH Threshold: Space above heading 1.5-2x space below. Headings connect to content they introduce.
h2 { margin-top: 2.5em; margin-bottom: 0.75em; }
type-small-text-minimum
Priority: CRITICAL Threshold: No text below 12px anywhere. Captions, footnotes, labels, all minimum 12px.
type-link-distinction
Priority: CRITICAL Threshold: Links distinguishable by more than color. Use underline or weight. Color-only fails for colorblind users.
a { text-decoration: underline; text-underline-offset: 2px; }
type-em-for-emphasis
Priority: LOW Threshold: Use em for stress emphasis, strong for importance. Never b or i for semantic emphasis.
type-font-subsetting
Priority: HIGH Threshold: Subset fonts to needed character sets. Latin-only can reduce 200KB to 20KB.
@font-face { unicode-range: U+0000-00FF, U+0131, U+0152-0153; }
type-variable-font-optimization
Priority: MEDIUM Threshold: 3+ weights of same family: switch to variable font. 40-70% payload reduction.
type-list-spacing
Priority: MEDIUM Threshold: Multi-line list items: 0.5em-0.75em spacing. Single-line: 0.25em-0.5em.
3. Accessibility
a11y-contrast-aa
Priority: CRITICAL Threshold: Normal text: 4.5:1 contrast ratio (WCAG AA). Large text (18px bold / 24px): 3:1.
a11y-contrast-aaa
Priority: HIGH Threshold: Target 7:1 for body text (WCAG AAA). Critical for long-form reading.
a11y-contrast-non-text
Priority: HIGH Threshold: UI components and graphical objects: 3:1 against adjacent colors.
a11y-focus-visible
Priority: CRITICAL Threshold: Every interactive element: visible focus indicator. Minimum 2px solid outline, 2px offset.
:focus-visible { outline: 2px solid var(--focus-color); outline-offset: 2px; }
:focus:not(:focus-visible) { outline: none; }
a11y-focus-order
Priority: CRITICAL Threshold: Focus order matches visual order. Never tabindex > 0.
a11y-skip-link
Priority: CRITICAL Threshold: First focusable element: skip-to-content link. Visible on focus.
<a href="#main-content" class="skip-link">Skip to content</a>
a11y-heading-hierarchy
Priority: CRITICAL Threshold: Heading levels never skip. H1 > H2 > H3. One H1 per page.
a11y-alt-text
Priority: CRITICAL Threshold: Every img has alt. Decorative: alt="". Informative: descriptive text.
<img alt="Bar chart showing Q3 revenue up 12%" src="chart.png">
<img alt="" src="divider.svg" role="presentation">
a11y-aria-labels
Priority: CRITICAL Threshold: Interactive elements without visible text: aria-label or aria-labelledby.
<button aria-label="Close dialog"><svg>...</svg></button>
a11y-form-labels
Priority: CRITICAL Threshold: Every input has a visible label with matching for/id. Placeholder is not a label.
<label for="email">Email</label>
<input id="email" type="email">
a11y-error-identification
Priority: CRITICAL Threshold: Form errors via text (not just color), linked via aria-describedby, announced via role="alert".
<input aria-describedby="email-err" aria-invalid="true">
<span id="email-err" role="alert">Enter a valid email</span>
a11y-color-not-sole-indicator
Priority: CRITICAL Threshold: Color never the only way to convey information. Add icons, text, or patterns.
a11y-touch-target-size
Priority: CRITICAL Threshold: Minimum 44x44px (WCAG 2.1). Recommended 48x48px. Includes padding.
a11y-touch-target-spacing
Priority: HIGH Threshold: Minimum 8px between adjacent touch targets.
a11y-keyboard-trap
Priority: CRITICAL Threshold: Users can navigate to AND away from every component via keyboard. Focus traps only in modals.
a11y-modal-focus-trap
Priority: CRITICAL Threshold: Open modals trap focus. Tab wraps. Escape closes. Focus returns to trigger.
a11y-live-regions
Priority: HIGH Threshold: Dynamic content uses aria-live. Polite for non-urgent, assertive for critical.
<div aria-live="polite">3 items added to cart</div>
a11y-landmark-roles
Priority: HIGH Threshold: Every page: header, nav, main, footer. Multiple navs need aria-label.
a11y-language-attribute
Priority: HIGH Threshold: html lang="en" set. Screen readers use this for pronunciation.
a11y-page-title
Priority: HIGH Threshold: Unique descriptive title per page. "Page Name - Site Name".
a11y-motion-reduced
Priority: CRITICAL Threshold: Respect prefers-reduced-motion. See motion-reduced-motion-override.
a11y-zoom-200
Priority: CRITICAL Threshold: Usable at 200% zoom. No clipping, no horizontal scroll, no overlap.
a11y-text-resize
Priority: HIGH Threshold: Text resizable to 200% without loss. Use rem/em, not fixed px.
a11y-autocomplete-attribute
Priority: HIGH Threshold: Personal data fields have autocomplete attribute.
<input type="email" autocomplete="email">
a11y-table-headers
Priority: HIGH Threshold: Data tables use th with scope="col" or scope="row".
a11y-svg-accessible
Priority: HIGH Threshold: Meaningful SVGs: role="img" aria-label. Decorative: aria-hidden="true".
a11y-no-autoplay
Priority: HIGH Threshold: No auto-playing media. If unavoidable: visible pause within 3 seconds.
a11y-animation-pause
Priority: HIGH Threshold: Animations > 5 seconds must have pause mechanism. WCAG 2.2.2.
a11y-timeout-warning
Priority: HIGH Threshold: Session timeouts: warn 60 seconds before. Provide extend option.
a11y-visible-state
Priority: HIGH Threshold: All states visually distinct: default, hover, focus, active, disabled, selected, error. Minimum 2 distinguishing properties.
a11y-disabled-semantics
Priority: HIGH Threshold: Disabled elements: disabled attribute, opacity 0.4-0.5, removed from tab order.
[disabled] { opacity: 0.45; cursor: not-allowed; pointer-events: none; }
a11y-screen-reader-only
Priority: MEDIUM Threshold: sr-only class for screen-reader-only content. Never display:none for this.
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
a11y-dialog-semantics
Priority: CRITICAL Threshold: Use dialog element or role="dialog" with aria-modal="true" and aria-labelledby.
a11y-link-vs-button
Priority: HIGH Threshold: Links navigate. Buttons act. Never div onClick or a href="#" for actions.
a11y-no-tabindex-positive
Priority: CRITICAL Threshold: Never tabindex > 0. Use 0 or -1 only.
4. Color & Contrast
color-palette-limit
Priority: HIGH Threshold: Maximum 5-7 distinct hues.
color-semantic-mapping
Priority: CRITICAL Threshold: Red/destructive, green/success, yellow/warning, blue/info. Never red for positive.
color-neutral-range
Priority: HIGH Threshold: Minimum 9 neutral steps (50-900). Each visually distinguishable.
color-dark-mode-not-inverted
Priority: CRITICAL Threshold: Dark mode is NOT inverted light mode. Max white: #E0E0E0. Desaturate 10-20%.
--text-dark: #E0E0E0; --bg-dark: #121212;
color-dark-mode-elevation
Priority: HIGH Threshold: Higher elevation = lighter surface in dark mode. Each step: +3-5% white overlay.
--surface-0: #121212; --surface-1: #1E1E1E; --surface-2: #232323;
color-opacity-over-gray
Priority: MEDIUM Threshold: Text hierarchy: use opacity/alpha, not fixed grays. Adapts to any background.
--text-primary: rgba(0,0,0,0.87); --text-secondary: rgba(0,0,0,0.60);
color-state-tokens
Priority: HIGH Threshold: Define color tokens for states. Components reference tokens, never hex directly.
color-interactive-feedback
Priority: HIGH Threshold: Hover: darken 5-10%. Active: darken 10-15%. Focus: distinct outline.
.btn:hover { background: color-mix(in srgb, var(--primary) 90%, black); }
color-consistent-saturation
Priority: MEDIUM Threshold: Same-palette colors share similar saturation. Mixing vibrant/muted confuses hierarchy.
color-perceptual-uniformity
Priority: MEDIUM Threshold: Use OKLCH/OKLAB for palette generation. HSL produces uneven brightness across hues.
--primary: oklch(65% 0.15 240);
color-background-text-separation
Priority: CRITICAL Threshold: Never text directly on images/gradients without overlay or shadow to guarantee contrast.
color-brand-accent-ratio
Priority: MEDIUM Threshold: Brand color: 5-15% of visual area. More than 15% loses signal.
color-transparency-layering
Priority: MEDIUM Threshold: Never stack more than 2 semi-transparent layers.
color-forced-colors
Priority: HIGH Threshold: Support forced-colors: active (Windows High Contrast Mode).
@media (forced-colors: active) { .button { border: 2px solid ButtonText; } }
color-data-viz-palette
Priority: HIGH Threshold: Data viz: max 6-8 colors. Beyond 8, discrimination drops below 80%.
color-data-viz-colorblind
Priority: CRITICAL Threshold: Data palettes must pass deuteranopia, protanopia, tritanopia simulation.
color-gradient-direction
Priority: LOW Threshold: Gradients: top-to-bottom or left-to-right following reading direction.
color-shadow-hue
Priority: MEDIUM Threshold: Shadows tinted with surface hue at 5-10% opacity. Pure black shadows look flat.
box-shadow: 0 4px 12px oklch(25% 0.02 250 / 0.15);
color-sufficient-distinction
Priority: HIGH Threshold: Adjacent UI elements must have minimum 15% lightness difference in OKLCH to be perceived as separate.
color-status-redundancy
Priority: HIGH Threshold: Status colors always paired with icon or text. Green dot + "Online". Red badge + number.
5. Touch & Interaction
touch-target-48
Priority: CRITICAL Threshold: All tappable elements: minimum 48x48px touch area including padding.
touch-feedback-immediate
Priority: CRITICAL Threshold: Touch feedback within 100ms. Active state, ripple, or press animation.
button:active { transform: scale(0.97); }
touch-no-hover-dependency
Priority: CRITICAL Threshold: No critical functionality behind :hover. Touch devices have no hover.
touch-gesture-hint
Priority: HIGH Threshold: Custom gestures need visual hints on first encounter.
touch-swipe-threshold
Priority: HIGH Threshold: Swipe: 10-30px movement + velocity > 0.3px/ms.
const SWIPE_THRESHOLD = 20; const VELOCITY_THRESHOLD = 0.3;
touch-pull-to-refresh-distance
Priority: MEDIUM Threshold: Pull-to-refresh trigger: 60-80px.
touch-scroll-lock-on-gesture
Priority: HIGH Threshold: Lock page scroll during recognized gestures. touch-action: none on gesture target.
touch-momentum-scroll
Priority: HIGH Threshold: Custom scroll containers: -webkit-overflow-scrolling: touch or overscroll-behavior: contain.
touch-no-double-tap-zoom
Priority: MEDIUM Threshold: touch-action: manipulation on interactive areas eliminates 300ms delay.
touch-drag-cancel
Priority: HIGH Threshold: All drags support cancel. Escape or drag back to origin reverts.
touch-haptic-on-commit
Priority: MEDIUM Threshold: Haptic on commit actions only. Never on hover or scroll.
if ('vibrate' in navigator) navigator.vibrate(10);
touch-hold-delay
Priority: HIGH Threshold: Long-press: 400-600ms. Visual progress indicator during hold.
touch-rubber-band-feedback
Priority: MEDIUM Threshold: 30-50px elastic overscroll at container bounds.
touch-pinch-zoom-images
Priority: HIGH Threshold: Full-width images support pinch-to-zoom. No maximum-scale=1.
touch-edge-swipe-safety
Priority: HIGH Threshold: Reserve 20px from screen edges for system gestures.
touch-button-debounce
Priority: CRITICAL Threshold: Debounce taps: disable for 300ms or until action completes.
if (processing) return; setProcessing(true);
touch-input-font-size
Priority: CRITICAL Threshold: Input fields minimum 16px on iOS. Below 16px triggers auto-zoom.
touch-dismissible-overlays
Priority: HIGH Threshold: All overlays dismissible by tapping outside.
touch-scroll-direction-lock
Priority: MEDIUM Threshold: Lock to axis once scroll direction established.
touch-safe-area-respect
Priority: CRITICAL Threshold: Interactive elements never overlap with device safe areas (notch, home indicator).
6. Layout & Spacing
layout-spacing-scale
Priority: HIGH Threshold: Consistent spacing scale from base unit (4px or 8px). All values multiples.
:root { --space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px; --space-6: 24px; --space-8: 32px; }
layout-content-width
Priority: HIGH Threshold: Max content: 1200-1440px for apps, 720-960px for reading.
.container { max-width: 1200px; margin-inline: auto; }
layout-padding-mobile
Priority: CRITICAL Threshold: Minimum horizontal padding on mobile: 16px.
layout-z-index-scale
Priority: HIGH Threshold: Named z-index tiers: base(0), dropdown(100), sticky(200), overlay(300), modal(400), toast(500), tooltip(600).
layout-gap-over-margin
Priority: HIGH Threshold: Use gap in flex/grid instead of margin on children.
.row { display: flex; gap: 16px; }
layout-logical-properties
Priority: MEDIUM Threshold: Use margin-inline, padding-block instead of margin-left. Required for RTL.
layout-border-box
Priority: CRITICAL Threshold: box-sizing: border-box globally.
*, *::before, *::after { box-sizing: border-box; }
layout-sticky-offset
Priority: MEDIUM Threshold: Sticky headers: top: 0 + z-index above content. Account for stacking.
layout-container-queries
Priority: MEDIUM Threshold: Component-level responsiveness: container queries over media queries.
.card-container { container-type: inline-size; }
layout-grid-auto-fill
Priority: HIGH Threshold: Auto-flowing grids: auto-fill with minmax().
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 24px; }
layout-aspect-ratio
Priority: HIGH Threshold: Media containers: aspect-ratio to prevent layout shift.
.video { aspect-ratio: 16 / 9; } .thumb { aspect-ratio: 1; }
layout-scroll-margin
Priority: MEDIUM Threshold: Anchor targets: scroll-margin-top equals fixed header height.
[id] { scroll-margin-top: 80px; }
layout-overflow-clip
Priority: MEDIUM Threshold: Use overflow: clip over overflow: hidden when no scroll container needed.
layout-min-height-dvh
Priority: HIGH Threshold: Full-height: min-height: 100dvh not 100vh. Accounts for mobile browser chrome.
layout-safe-area-inset
Priority: CRITICAL Threshold: Devices with notches: env(safe-area-inset-*) padding.
body { padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); }
layout-no-horizontal-scroll
Priority: CRITICAL Threshold: No unintentional horizontal scroll. Test at 320px width.
layout-whitespace-proportion
Priority: HIGH Threshold: Larger components need proportionally larger surrounding whitespace.
layout-grid-gutter-responsive
Priority: MEDIUM Threshold: Grid gutters scale: 16px mobile, 24px tablet, 32px desktop.
layout-centering-method
Priority: MEDIUM Threshold: Use place-items: center for single-element centering. Flexbox for multi-element.
7. Responsive Design
responsive-breakpoints
Priority: HIGH Threshold: Standard: 320, 375, 768, 1024, 1280, 1536. Mobile-first.
responsive-mobile-first
Priority: HIGH Threshold: Base styles for mobile, add with min-width. Never max-width as primary.
responsive-fluid-over-fixed
Priority: HIGH Threshold: Fluid values (%, vw, clamp()) over fixed where possible.
responsive-image-srcset
Priority: HIGH Threshold: Every image: srcset with 2-3 variants and sizes attribute.
<img srcset="photo-400.jpg 400w, photo-800.jpg 800w" sizes="(max-width: 768px) 100vw, 50vw">
responsive-picture-art-direction
Priority: MEDIUM Threshold: picture with source for different crops at breakpoints.
responsive-viewport-meta
Priority: CRITICAL Threshold: viewport meta tag. Never maximum-scale=1 or user-scalable=no.
<meta name="viewport" content="width=device-width, initial-scale=1">
responsive-font-scaling
Priority: HIGH Threshold: clamp() for headings. No fixed sizes.
responsive-hide-with-purpose
Priority: HIGH Threshold: Hidden elements still accessible or alternative path exists.
responsive-table-overflow
Priority: HIGH Threshold: Tables on mobile: overflow-x: auto or convert to stacked cards below 768px.
responsive-modal-fullscreen-mobile
Priority: HIGH Threshold: Modals below 480px go full-screen or bottom sheet.
responsive-nav-hamburger-threshold
Priority: MEDIUM Threshold: Collapse nav when items dont fit at touch target size. Never collapse 3-item nav.
responsive-input-sizing
Priority: HIGH Threshold: Mobile inputs: full-width. Side-by-side only above 768px.
responsive-sticky-bottom-mobile
Priority: MEDIUM Threshold: Primary mobile CTAs in sticky bottom bar within thumb reach.
responsive-landscape-consideration
Priority: MEDIUM Threshold: Test landscape. Fixed headers + footers can consume >50% in landscape.
responsive-test-320
Priority: CRITICAL Threshold: Test at 320px minimum. iPhone SE is 320px.
responsive-container-query-components
Priority: MEDIUM Threshold: Reusable components: container queries over media queries.
8. Performance
perf-lcp-under-2500
Priority: CRITICAL Threshold: LCP under 2.5s. Core Web Vital.
perf-inp-under-200
Priority: CRITICAL Threshold: INP under 200ms. Under 100ms is good.
perf-cls-under-01
Priority: CRITICAL Threshold: CLS under 0.1. Set dimensions on images/videos.
perf-ttfb-under-800
Priority: HIGH Threshold: TTFB under 800ms. Use CDN, edge caching, SSG.
perf-js-bundle-budget
Priority: CRITICAL Threshold: Initial JS: <100KB gzipped. Each 100KB costs 350ms parse on mid-tier phone.
perf-critical-css-inline
Priority: HIGH Threshold: Inline critical CSS in head. Max 14KB (TCP congestion window).
perf-lazy-load-images
Priority: HIGH Threshold: Below-fold images: loading="lazy". LCP image: eager + fetchpriority="high".
perf-preconnect-origins
Priority: HIGH Threshold: Preconnect to critical third-party origins. Saves 100-300ms.
<link rel="preconnect" href="https://fonts.googleapis.com">
perf-image-format
Priority: HIGH Threshold: WebP or AVIF for raster images. WebP: 25-35% smaller. AVIF: 50% smaller.
perf-font-subset
Priority: HIGH Threshold: Subset fonts. Latin-only: 200KB to 20KB.
perf-third-party-async
Priority: HIGH Threshold: All third-party scripts: async or defer.
perf-no-render-blocking-css
Priority: HIGH Threshold: Non-critical CSS: media attribute or async load.
perf-code-splitting
Priority: HIGH Threshold: Route-based splitting minimum. Dynamic import() for heavy components.
perf-prefetch-likely-routes
Priority: MEDIUM Threshold: Prefetch likely next pages on hover/visibility.
perf-debounce-scroll-resize
Priority: HIGH Threshold: Throttle scroll/resize to 100ms or use rAF.
perf-virtual-list
Priority: HIGH Threshold: Lists >50 items: virtualize.
perf-image-dimensions
Priority: CRITICAL Threshold: All img/video: explicit width and height or aspect-ratio.
perf-service-worker-cache
Priority: MEDIUM Threshold: Cache static assets with service worker.
perf-dns-prefetch
Priority: LOW Threshold: DNS-prefetch for third-party domains.
perf-long-task-break
Priority: HIGH Threshold: No JS task >50ms on main thread. Break with rIC or setTimeout(0).
perf-react-memo-threshold
Priority: MEDIUM Threshold: Memoize components rendering >16ms with stable props. Profile first.
perf-reflow-batch
Priority: HIGH Threshold: Batch DOM reads before writes.
perf-css-containment
Priority: MEDIUM Threshold: contain: layout on independent components.
perf-resource-hints-order
Priority: MEDIUM Threshold: Resource hint priority: preload > preconnect > prefetch > dns-prefetch.
9. Loading States
loading-skeleton-shape
Priority: HIGH Threshold: Skeletons match shape/layout of replaced content. No generic rectangles.
loading-skeleton-animation
Priority: MEDIUM Threshold: Shimmer gradient (left-to-right wave) implies progress.
.skeleton { background: linear-gradient(90deg, #e0e0e0 25%, #f0f0f0 50%, #e0e0e0 75%); background-size: 200% 100%; animation: shimmer 1.5s ease-in-out infinite; }
loading-instant-feedback
Priority: CRITICAL Threshold: Visual feedback within 100ms of user action.
loading-spinner-delay
Priority: HIGH Threshold: Show spinner after 300-500ms delay. Most operations complete before it appears.
loading-progress-determinate
Priority: HIGH Threshold: Known progress: use determinate bar. Indeterminate spinner for unknown.
loading-optimistic-update
Priority: HIGH Threshold: Binary actions (toggle, like): update UI immediately, reconcile async.
setLiked(!liked); // optimistic
try { await api.toggleLike(id); } catch { setLiked(liked); }
loading-content-priority
Priority: HIGH Threshold: Load order: text > above-fold images > below-fold > non-critical.
loading-error-recovery
Priority: HIGH Threshold: Every loading state has error state with retry. Timeout after 10-15s.
loading-empty-state-helpful
Priority: HIGH Threshold: Empty states explain WHY and provide primary action. Not just "No results."
loading-pagination-infinite
Priority: MEDIUM Threshold: Infinite scroll: loading indicator, scroll-to-top after 3 pages, virtualize after 100 items.
loading-skeleton-reduced-motion
Priority: MEDIUM Threshold: prefers-reduced-motion: static gray blocks instead of animated skeletons.
loading-placeholder-image
Priority: MEDIUM Threshold: Images show BlurHash, LQIP, or dominant color during load.
loading-stream-long-content
Priority: MEDIUM Threshold: Stream long content as it arrives. Users read while loading.
10. Error States
error-message-human
Priority: CRITICAL Threshold: Human language, not technical codes.
error-message-specific
Priority: HIGH Threshold: Explain what went wrong AND what to do.
error-message-no-blame
Priority: HIGH Threshold: Never blame the user. "We could not find" not "You entered invalid."
error-inline-not-modal
Priority: HIGH Threshold: Validation errors inline next to field, not in modal/alert.
error-real-time-validation
Priority: MEDIUM Threshold: Validate on blur for text, immediately for toggles/selects.
error-persistent-until-fixed
Priority: HIGH Threshold: Errors visible until corrected. Never auto-dismiss.
error-404-helpful
Priority: HIGH Threshold: 404 includes search, home link, suggested pages.
error-network-offline
Priority: HIGH Threshold: Detect offline, show persistent banner, cache last content.
window.addEventListener('offline', () => showBanner('You are offline'));
error-retry-exponential
Priority: MEDIUM Threshold: Auto-retry: 1s, 2s, 4s, 8s, max 30s. Stop after 5 attempts.
error-boundary-react
Priority: CRITICAL Threshold: Error boundaries at route level and around risky components.
error-form-preserve-input
Priority: CRITICAL Threshold: Failed submission: preserve all input. Never clear on failure.
error-rate-limit-messaging
Priority: MEDIUM Threshold: Rate limit errors: tell when to retry with countdown.
error-destructive-confirmation
Priority: CRITICAL Threshold: Destructive actions: confirm with specific item name.
error-undo-over-confirm
Priority: HIGH Threshold: Prefer undo over confirmation dialogs. Undo is a real safety net.
11. Dark Mode
dark-surface-elevation
Priority: HIGH Threshold: Lighter surfaces = higher elevation. 4-5 steps.
dark-no-pure-black
Priority: HIGH Threshold: No #000000. Use #121212-#1A1A1A. Pure black causes OLED smearing.
dark-no-pure-white-text
Priority: HIGH Threshold: No #FFFFFF text. Use #E0E0E0-#EBEBEB. Pure white causes halation.
dark-desaturate-colors
Priority: HIGH Threshold: Reduce accent saturation 10-20% in dark mode.
dark-increase-lightness
Priority: HIGH Threshold: Accent L value +15-25% in OKLCH for dark mode.
dark-shadow-alternative
Priority: MEDIUM Threshold: Replace shadows with subtle borders (1px, 5-10% white opacity).
/* Dark */ border: 1px solid rgba(255,255,255,0.08);
dark-image-brightness
Priority: MEDIUM Threshold: Reduce image brightness 10-15% in dark mode. Not user content.
@media (prefers-color-scheme: dark) { img:not(.user-content) { filter: brightness(0.88); } }
dark-system-preference
Priority: HIGH Threshold: Default to prefers-color-scheme. Manual toggle persists.
dark-transition-smooth
Priority: MEDIUM Threshold: Theme switch: 200-300ms transition on background/color.
dark-code-block-theme
Priority: MEDIUM Threshold: Dark syntax theme in dark mode. Light in light mode.
dark-favicon-variant
Priority: LOW Threshold: Dark-mode favicon if logo disappears on dark chrome.
dark-meta-theme-color
Priority: MEDIUM Threshold: meta theme-color matches dark mode background on mobile.
<meta name="theme-color" content="#121212" media="(prefers-color-scheme: dark)">
12. Keyboard Navigation
keyboard-all-interactive-reachable
Priority: CRITICAL Threshold: Every interactive element reachable by Tab.
keyboard-escape-closes
Priority: CRITICAL Threshold: Escape closes topmost overlay. Focus returns to trigger.
keyboard-enter-activates
Priority: HIGH Threshold: Enter activates buttons/links. Space toggles checkboxes/buttons.
keyboard-arrow-keys
Priority: HIGH Threshold: Arrows navigate within composite widgets. Tab exits widget.
keyboard-visible-focus
Priority: CRITICAL Threshold: 2px outline, 3:1 contrast against adjacent backgrounds.
keyboard-roving-tabindex
Priority: HIGH Threshold: Composite widgets: one child in tab order. Arrows move between children.
keyboard-shortcut-discoverable
Priority: MEDIUM Threshold: Shortcuts shown in tooltips or ? dialog. Undocumented shortcuts do not exist.
keyboard-no-override-browser
Priority: HIGH Threshold: Custom shortcuts must not override browser defaults (Ctrl+F, Ctrl+S).
keyboard-search-focus
Priority: MEDIUM Threshold: Search focusable with / or Cmd+K.
keyboard-type-ahead
Priority: MEDIUM Threshold: Lists/selects support type-ahead character jump.
13. Sound Design
sound-action-feedback
Priority: MEDIUM Threshold: Success/error/warning sounds: distinct, under 200ms. Reserve for important state changes.
sound-volume-control
Priority: CRITICAL Threshold: Mute toggle that persists. Default 30-50% system volume.
sound-no-autoplay-audio
Priority: CRITICAL Threshold: Never autoplay audio. User
…(truncated)