# Responsive & multi-device best practices (2019–2026)

The distilled rules for building sites that work on every device and screen size,
based on how the device landscape and web platform actually changed over the last
seven years — plus how to apply each rule with Skynet UI. Sizes are CSS pixels
(viewport units), not hardware pixels, unless stated.

## TL;DR checklist

- Design mobile-first from **320px** up; no horizontal scroll at any width from 320 to 2560.
- Use content-driven breakpoints, not device names. Skynet UI's: **sm 640 / md 768 / lg 1024 / xl 1280 / 2xl 1536**.
- Body text **16px minimum** (also stops iOS zoom-on-focus in inputs); never disable user zoom.
- Touch targets **44×44px** (Apple 44pt / Material 48dp; WCAG 2.2 AA floor is 24×24).
- `<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">` + `env(safe-area-inset-*)` padding for notches and home indicators.
- Full-height layouts use `100svh` (stable) or `100dvh` (tracks browser chrome) — never bare `100vh` on mobile.
- Images: `srcset`/`sizes`, modern formats (AVIF/WebP), explicit `aspect-ratio` or width/height (kills CLS), `loading="lazy"` below the fold, `fetchpriority="high"` on the LCP image.
- Performance at p75 mobile: **LCP ≤ 2.5s, INP ≤ 200ms, CLS ≤ 0.1**.
- Respect `prefers-color-scheme`, `prefers-reduced-motion`, `prefers-contrast`.
- Test at minimum: 320, 360, 390, 412, 768, 1024, 1280, 1440, 1920 — plus one foldable inner screen (~800–900) and both orientations.

## How we got here: a 2019–2026 timeline

- **2019** — Google's mobile-first indexing became the default for new sites; `prefers-color-scheme` shipped and dark mode became a first-class design requirement; the first foldables (Galaxy Fold, 768px inner viewport) arrived; 360×640 was the world's most common mobile viewport.
- **2020** — Core Web Vitals announced (LCP/FID/CLS); native `loading="lazy"` landed in browsers; mobile passed half of global web traffic for good.
- **2021** — Core Web Vitals became a Google ranking signal; CSS `aspect-ratio` shipped, ending the padding-hack era of layout-shift prevention.
- **2022** — The `100vh` problem finally got real fixes: `svh`/`lvh`/`dvh` shipped (Safari 15.4, then Chrome 108); iPhone 14 Pro's Dynamic Island made `viewport-fit=cover` + safe-area insets mainstream; 390–393px became the iPhone norm.
- **2023** — Container queries (`@container`) reached every engine, letting components respond to their own box instead of the window; `:has()` went cross-browser; WCAG 2.2 landed with **2.5.8 Target Size (AA, 24×24px)**.
- **2024** — **INP replaced FID** in Core Web Vitals (March), moving the bar from first-input latency to worst-interaction latency; the Popover API went Baseline; foldables settled on ~350–430px cover screens and ~750–930px inner screens.
- **2025** — `svh`/`dvh`/`lvh` reached Baseline Widely Available; three viewports (360×800, 390×844, 393×852) came to dominate mobile traffic.
- **2026** — 1920×1080 now serves over half of desktop sessions while 2560×1440 grows; mobile stays fragmented: the top five viewports cover only ~35% of traffic, the rest spreads across hundreds of sizes. The lesson of the whole period: **design for ranges, not devices.**

## Device & viewport reference (CSS pixels)

### Phones (portrait width)

| Width | Devices | Notes |
|---|---|---|
| 320 | iPhone SE (1st gen), small/older Androids | The floor. Everything must work here. |
| 360 | Samsung Galaxy A/S series and most budget Androids | The single most common mobile width worldwide. |
| 375 | iPhone SE 2/3, 6–8, X, 11 Pro, 12/13 mini | Still huge in the installed base. |
| 390 | iPhone 12–14, iPhone 15 | Top-three worldwide. |
| 393 | Pixel 5–9, iPhone 14 Pro–16 | Top-three worldwide. |
| 402–412 | iPhone 16 Pro (402), Pixel XL & many Androids (412) | |
| 414 | iPhone 6–8 Plus, XR, 11 | |
| 428–440 | iPhone Pro Max / Plus models | Largest mainstream phones. |

### Foldables

| State | Width range | Examples |
|---|---|---|
| Folded (cover) | ~320–430 | Z Flip, Z Fold cover screens |
| Unfolded (inner) | ~717–928 | Galaxy Fold 768 → Fold2 884 → recent Folds ~900+ |

Treat the inner screen as a small tablet, the cover as a narrow phone, and never
hard-code to either — a continuous 320→1536 layout plus container queries covers
both states and the mid-fold near-square (~720) case.

### Tablets

| Width | Devices |
|---|---|
| 744 | iPad mini (portrait) |
| 768 | Classic iPad (portrait) — still the canonical tablet test width |
| 810–834 | iPad 10.2/10.9, Air, Pro 11 (portrait) |
| 1024 | iPad Pro 12.9 portrait; classic iPad landscape |
| 1080–1366 | Tablet landscape widths |

### Laptops & desktops

| Width | Context |
|---|---|
| 1280 | Small laptops, split-screen desktop windows |
| 1366 | Budget laptops (long-time #2 desktop size) |
| 1440 | MacBook Pro default scaled resolution |
| 1536 | 1920 hardware at Windows 125% scaling — extremely common |
| 1920 | Majority desktop size in 2026 |
| 2560+ | Growing fast; cap content width so lines stay readable |

**Device-pixel ratio:** phones render at 2–3× (a 393px viewport can be a 1179px
panel). Serve 2× imagery to phones, use SVG for icons and logos, and never read
`screen.width` for layout — use CSS viewport units and media queries.

## Breakpoints that work

- **Mobile-first, `min-width` only.** Style the 320px layout as the default; add
  complexity upward. Overriding desktop styles back down is how bugs breed.
- **Break where the content breaks,** not at device names. The classic five-stop
  scale earns its keep because it tracks real clusters: phones (<640), large
  phones/small tablets (640), tablets/foldables-open (768), laptops (1024),
  desktops (1280), large desktops (1536).
- **Between breakpoints, be fluid.** Percentages, `minmax()`, `flex-wrap`,
  `clamp()` — a layout should never depend on landing exactly on a breakpoint.
- **Cap line length** on big screens: a centered max-width container (65–75ch for
  prose) beats letting text run 2560px wide.
- **Components respond to their container**, not the window, when they can:
  a card in a sidebar and the same card full-width need `@container`, not `@media`.

With Skynet UI: the `sm:` `md:` `lg:` `xl:` `2xl:` variants map exactly to
640/768/1024/1280/1536. `class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4"`
is the whole responsive grid story; `.sk-container` gives the capped, centered
column; `.hide-mobile`/`.hide-desktop` handle the odd element that only belongs
on one side of 768.

## Touch & pointer targets

- **WCAG 2.2 (2.5.8, Level AA):** interactive targets ≥ **24×24 CSS px**, or
  spaced so a 24px circle centered on each target doesn't overlap its neighbors.
- **The bar to actually design to:** Apple HIG **44×44pt**, Material **48×48dp**,
  WCAG 2.5.5 (AAA) **44×44px**. On phones, fingers average ~10mm of contact.
- Pad hit areas, not glyphs: a 16px icon inside a 44px padded button is correct.
- Hover can enhance, never gate: anything revealed on `:hover` needs a
  tap/focus path too. Style `:focus-visible` as deliberately as `:hover`.
- Use `@media (pointer: coarse)` to widen spacing for touch without penalizing
  mouse users, and keep destructive actions away from screen edges where palms
  and gesture bars live.

With Skynet UI: `.sk-btn` meets 44px height out of the box; `.sk-btn-sm` is for
mouse-dense toolbars, not primary mobile actions; every interactive component
ships `:focus-visible` rings.

## Typography & spacing

- **16px body minimum.** Below 16px, iOS Safari zooms the page when an input is
  focused — the classic "why did my form jump" bug.
- **Fluid type via `clamp()`:** e.g. `font-size: clamp(1.75rem, 1.2rem + 2.5vw, 3rem)`
  for headings scales smoothly from 320 to 1920 with zero breakpoints.
- **Measure:** 45–75 characters per line; line-height ~1.5 for body text.
- **Use rem** for type and spacing so user font-size preferences are honored.
- **Never** ship `maximum-scale=1` or `user-scalable=no` — pinch zoom is an
  accessibility right, and disabling it fails WCAG.

With Skynet UI: the type scale (`text-xs` → `text-4xl`) is rem-based; the
default body size is 16px; headings on this site use clamp-based sizes.

## Layout foundations

- Grid for two-dimensional structure, flexbox for rows — and
  `grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr))` is the single
  most useful responsive pattern of the era: cards that reflow at every width
  with no media queries at all.
- The **320px test**: every page must render without horizontal scroll at 320.
  The usual offenders: fixed widths, unbroken long strings (`overflow-wrap:
  anywhere` on user content), and media without `max-width: 100%`.
- Know the **`min-width: auto` trap**: flex/grid children refuse to shrink below
  content size until you set `min-width: 0` — the #1 cause of mystery overflow.
- **Wide content scrolls in its own box.** Tables, code blocks, and diagrams get
  an `overflow-x: auto` wrapper; the page itself never scrolls sideways.
- Use **logical properties** (`margin-inline`, `padding-block`) so layouts
  survive RTL locales for free.

With Skynet UI: `.sk-table-wrap` wraps wide tables; the navbar collapses to a
burger under 768 and scrolls overflowing links on one row above it; sidebar
layouts (`.sk-main`) stack on mobile automatically; code blocks scroll
internally.

## The viewport meta & safe areas

```html
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
```

- Without this tag, phones render a 980px desktop page and shrink it — nothing
  else in this guide works until it's present.
- `viewport-fit=cover` lets the page extend into notch/Dynamic Island territory;
  then pad fixed chrome with the insets:

```css
.sk-navbar { padding-top: env(safe-area-inset-top); }
.footer-bar { padding-bottom: env(safe-area-inset-bottom); }
```

- **Full-height layouts:** `100vh` measures the *largest* viewport on mobile, so
  content hides behind the browser's collapsing toolbars. Use `100svh` for
  stable hero/app shells (never overflows), `100dvh` when the layout should
  breathe with the chrome — and accept the reflow. All three units are Baseline
  as of 2025; `min-height: 100vh` remains an acceptable fallback line above.
- The on-screen keyboard shrinks the *visual* viewport, not these units — pin
  critical actions with `position: sticky` rather than bottom-fixed if they must
  stay visible while typing.

## Media

- **Responsive images:** `srcset` + `sizes` so a 360px phone doesn't download a
  1920px hero; `<picture>` for AVIF/WebP with fallback.
- **Reserve space:** explicit `width`/`height` attributes or CSS `aspect-ratio`
  on every image and embed — unreserved media is the top cause of layout shift.
- `loading="lazy"` for below-the-fold images and iframes;
  `fetchpriority="high"` on the LCP hero image; never lazy-load the LCP image.
- Icons and logos as **SVG** — crisp at every DPR, tinted with `currentColor`.
  (Skynet UI's 640 icons render inline at `1em` and inherit text color.)
- Background videos: compressed, muted, `playsinline`, and honoring
  `prefers-reduced-motion`.

## Performance budgets (Core Web Vitals)

Measured at the 75th percentile on mobile:

| Metric | Good | What moves it |
|---|---|---|
| LCP (Largest Contentful Paint) | ≤ 2.5s | Server time, render-blocking CSS/JS, hero image weight/priority |
| INP (Interaction to Next Paint) | ≤ 200ms | Long JS tasks, heavy hydration, main-thread contention (replaced FID in 2024) |
| CLS (Cumulative Layout Shift) | ≤ 0.1 | Unreserved images/ads/embeds, late-loading fonts, injected banners |

- Budget JavaScript hardest: the median phone is a mid-range Android, not a
  flagship. Test throttled (4× CPU, Fast 3G/4G) — not on a dev machine.
- Fonts: system stack or ≤2 families with `font-display: swap` and preload.
- One stylesheet, one deferred script, no build step — a page using this
  framework ships under ~60KB gzipped of framework total and needs no hydration.

## Dark mode, motion & user preferences

- **Dark mode is table stakes since 2019.** Respect `prefers-color-scheme` by
  default; if you offer a manual toggle, persist it and set `color-scheme` so
  form controls and scrollbars match. Keep dark themes low-glare: dark gray
  surfaces (not pure black), desaturated accents, AA contrast maintained.
- **`prefers-reduced-motion: reduce`**: gate parallax, auto-playing carousels,
  scroll-triggered animation, and large view transitions behind it.
- **`prefers-contrast: more`**: raise border and text contrast where you can, or
  ship a high-contrast theme.
- Set `<meta name="theme-color">` (per scheme) so browser chrome matches.

With Skynet UI: 9 built-in themes including `contrast`; dark-first by default;
`make_theme` (MCP) auto-derives full themes with AA-guarded body text; the
framework's animations respect reduced-motion.

## The testing matrix

Widths (portrait unless noted):

| Width | Represents |
|---|---|
| 320 | Absolute floor (iPhone SE 1, small Androids) |
| 360 | Most common Android |
| 390–393 | Modern iPhone / Pixel |
| 412 | Large Android |
| 768 | Tablet portrait / foldable inner (small) |
| 820–900 | iPad Air portrait / foldable inner (large) |
| 1024 | Tablet landscape / small laptop |
| 1280 | Laptop |
| 1440 | Desktop |
| 1920 | Majority desktop |

Beyond widths: both orientations on phone and tablet; keyboard-only pass (tab
order, visible focus, no traps); 200% browser zoom (WCAG 1.4.10 reflow: no
horizontal scroll at 320 CSS px equivalent); coarse-pointer emulation; dark and
light schemes; reduced motion on; a real mid-range Android over cellular at
least once per release.

## Applying this with Skynet UI — quick map

| Practice | Skynet UI feature |
|---|---|
| Mobile-first breakpoints | `sm:` 640 / `md:` 768 / `lg:` 1024 / `xl:` 1280 / `2xl:` 1536 variants |
| Capped content width | `.sk-container` (fluid: `.sk-container-fluid`) |
| Responsive grids | `grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4`, `gap-*` |
| Nav that fits every width | `.sk-navbar` — burger < 768px, one-row scroll for overflow above |
| Wide tables | `.sk-table-wrap` wrapper |
| Touch targets | `.sk-btn` ≥ 44px; `:focus-visible` styled everywhere |
| Show/hide by device | `.hide-mobile` / `.hide-desktop` (768px boundary) |
| Dark mode & themes | 9 themes, `data-theme`, dark-first tokens; MCP `make_theme` |
| Contrast checks | MCP `check_contrast` (WCAG ratios) |
| Icons at any DPR | 640 inline SVG icons, `1em`, `currentColor` |
| Validate generated markup | MCP `validate_markup` / `npx skynet-ui check` |

*Stats sourced from StatCounter global data and platform documentation (Apple
HIG, Material Design, W3C WCAG 2.2, web.dev Core Web Vitals), current as of
2026.*
