Skip to content
Back to skills

Responsive Design Patterns

ASecurity

Both directions produce working layouts, but very different amounts of CSS.

  • 2 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 10, 2026
designgoshellexpress

Works with

  • cli

Security analysis

A100/100

Scanned September 10, 2026

npx -y skills add snoodleboot-io/prompticorn --skill responsive-design-patterns --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Responsive Design Patterns?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Responsive Design Patterns
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/snoodleboot-io-responsive-design-patterns/badge)](https://www.skillsdirectory.com/skills/snoodleboot-io-responsive-design-patterns)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: responsive-design-patterns
description: "Both directions produce working layouts, but very different amounts of CSS."
---

# Responsive Design Patterns (Verbose)

## Core Patterns

### Mobile-First Is a CSS Volume Argument

Both directions produce working layouts, but very different amounts of CSS.

```css
/* ✅ mobile-first: each query ADDS */
.sidebar { display: none; }
.content { padding: 1rem; }

@media (min-width: 64rem) {
  .sidebar { display: block; width: 16rem; }
  .content { padding: 2rem 3rem; }
}

/* ❌ desktop-first: each query UNDOES */
.sidebar { display: block; width: 16rem; float: left; }
.content { padding: 2rem 3rem; margin-left: 16rem; }

@media (max-width: 64rem) {
  .sidebar { display: none; width: auto; float: none; }   /* three resets */
  .content { padding: 1rem; margin-left: 0; }             /* two more */
}
```

The desktop-first version must neutralize every property it set, and each reset
is itself a rule someone will later override. Mobile-first also leaves the
simplest layout as the unconditional base — what renders if a query never matches.

Express breakpoints in `rem`, not `px`: a `min-width: 48rem` query moves with the
user's root font size, so a user at 24px base text doesn't get a cramped
multi-column layout. And derive the values from where *your content* breaks —
resize until it looks wrong, put a breakpoint there — not from a device list that
goes out of date every year.

### Container Queries Solve What Media Queries Cannot

A media query asks "how wide is the viewport?" A component's correct layout
depends on how wide *it* is — and those are unrelated whenever the component
appears in more than one region.

```css
.card-area { container-type: inline-size; container-name: card; }

.card { display: block; }                    /* stacked by default */

@container card (min-width: 30rem) {
  .card { display: grid; grid-template-columns: 10rem 1fr; gap: 1rem; }
}
```

Now the same `.card` renders stacked in a 280px sidebar and horizontal in an
800px main column — same viewport, same moment. With media queries this needs
either a viewport-plus-region selector matrix (`.sidebar .card`, `.main .card`,
each with its own breakpoints) or duplicated components. Both scale badly and
break the moment someone drops the card in a new region.

| | Media query | Container query |
|---|---|---|
| Asks about | Viewport | Nearest container element |
| Portability | Breaks when moved | Moves intact |
| Right for | Page structure | Component internals |

Use media queries for the page shell (does the sidebar exist?) and container
queries for everything inside components. Note that `container-type: inline-size`
applies containment — apply it to a wrapper, not to an element whose height you
depend on.

Container query units (`cqi`, `cqw`) let internal sizing scale with the container
too: `font-size: clamp(1rem, 0.9rem + 1.5cqi, 1.5rem)`.

### Intrinsic Layouts

Many layouts need no breakpoints at all — let the algorithm respond.

```css
/* Responsive grid, no media queries */
.auto-grid {
  display: grid; gap: 1.5rem;
  grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr));
}

/* Sidebar that wraps when the main column would get too narrow */
.with-sidebar { display: flex; flex-wrap: wrap; gap: 1.5rem; }
.with-sidebar > .side { flex: 1 1 16rem; }
.with-sidebar > .main { flex: 999 1 30rem; }   /* huge grow: wins until it can't fit */
```

The `min(20rem, 100%)` matters: a bare `minmax(20rem, 1fr)` overflows whenever the
container is narrower than 20rem — exactly the mobile case. `auto-fit` collapses
empty tracks so items stretch to fill the row; `auto-fill` keeps them, preserving
column rhythm. Gallery: `auto-fit`. Dashboard: `auto-fill`.

### Fluid Typography and Spacing

```css
:root {
  --step-2: clamp(1.5rem, 1.3rem + 1vw, 2.25rem);
  --space-fluid-md: clamp(1rem, 0.8rem + 1vw, 2rem);
}
h2 { font-size: var(--step-2); }
section { padding-block: var(--space-fluid-md); }
```

The middle term must include a `rem` component. `clamp(1rem, 4vw, 2rem)` looks
fine until a user zooms: viewport units don't scale with zoom the way text does,
so the size stays pinned and fails WCAG 1.4.4 (resize text to 200%).

Cap measure for readability (`.prose { max-width: 65ch; }`) rather than reaching
for another breakpoint.

### Images

```html
<img src="/hero-800.jpg"
     srcset="/hero-400.jpg 400w, /hero-800.jpg 800w, /hero-1600.jpg 1600w"
     sizes="(min-width: 64rem) 50vw, 100vw"
     width="1600" height="900" alt="..." loading="lazy" decoding="async">
```

`srcset` + `sizes` lets the browser pick by width and device pixel ratio. The
`width`/`height` attributes let it reserve the correct box before load —
omitting them is the most common source of layout shift, and CSS `aspect-ratio`
serves the same purpose for CSS-sized media. Never set `loading="lazy"` on the
LCP image; it delays the very thing being measured.

### Input Modality

```css
@media (hover: hover) and (pointer: fine) { .row:hover .row__actions { opacity: 1; } }
@media (pointer: coarse) {
  .row__actions { opacity: 1; }        /* always visible for touch */
  .btn { min-height: 2.75rem; }        /* comfortable touch target */
}
```

Screen width is not input type: touchscreen laptops are wide and coarse, a phone
with a stylus is narrow and fine. Query the capability directly, and pair it with
a `prefers-reduced-motion: reduce` block neutralizing non-essential animation.

### Viewport Units on Mobile

Use `min-height: 100dvh` for full-height panels, not `100vh`.

`100vh` is sized against the *largest* viewport — the one with browser chrome
retracted — so a full-height element is taller than the visible area while the
address bar is showing, and its bottom is cut off. `dvh` tracks the dynamic
viewport; `svh` and `lvh` give the small and large extremes when you need a stable
value. Combine with `env(safe-area-inset-*)` so content clears notches.

## Common Anti-Patterns

❌ **Device-named breakpoints** — `@media (max-width: 375px) /* iPhone */` is
obsolete on release day.
✅ Breakpoints derived from where the content stops working.

❌ **Desktop-first `max-width` chains** — every rule needs an undo.
✅ Mobile-first `min-width`, additive only.

❌ **Viewport queries for component internals** — the component works in one
region and breaks in every other.
✅ `container-type: inline-size` on the wrapper, `@container` inside.

❌ **`minmax(20rem, 1fr)` without a `min()` guard** — overflow below 20rem.
✅ `minmax(min(20rem, 100%), 1fr)`.

❌ **Pure `vw` font sizing** — text stops responding to zoom, failing WCAG 1.4.4.
✅ `clamp()` with a `rem` term in the fluid middle.

❌ **`display: none` to "make it responsive"** — mobile users lose functionality
instead of getting a different arrangement.
✅ Reflow, collapse into a disclosure, or move into a bottom sheet.

❌ **Hover-revealed actions with no coarse-pointer path** — unreachable on touch.
✅ Gate on `(hover: hover)` and provide a persistent alternative.

❌ **`100vh` full-height panels on mobile** — clipped under browser chrome.
✅ `100dvh`, plus safe-area insets.

❌ **Images without intrinsic dimensions** — layout shift on every load.
✅ `width`/`height` attributes or `aspect-ratio`.

## Responsive Design Checklist

- [ ] Base styles are the smallest layout; queries only add
- [ ] Breakpoints in `rem`, chosen from content, not devices
- [ ] Component-internal adaptation uses container queries
- [ ] `container-type` applied to a wrapper, not a height-critical element
- [ ] Grids use `auto-fit`/`auto-fill` with a `min()` overflow guard
- [ ] Fluid type via `clamp()` with a `rem` term in the middle
- [ ] Line length capped (~65ch) for prose
- [ ] Images use `srcset` + `sizes` and declare intrinsic dimensions
- [ ] LCP image not lazy-loaded
- [ ] Hover affordances gated on `(hover: hover)`; touch targets ~44px on coarse
- [ ] `100dvh` used instead of `100vh`; `env(safe-area-inset-*)` on edge UI
- [ ] `prefers-reduced-motion` honored
- [ ] No horizontal scroll at 320px; layout verified at 200% zoom
- [ ] Nothing essential removed at small widths — only rearranged

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…