RamosLabs DS

Foundations / Responsive

Design for the content, not the device.

There is no fixed set of screens, and there never was. Start from the smallest reasonable screen, let the content decide where it strains, and reach for a fixed breakpoint last. A layout keyed to a few famous device widths breaks the moment someone opens a split view, rotates a foldable, or reads at 200% zoom.

01

Mobile first is a discipline, not a phone rule

The small screen first forces the hard question early: what is genuinely essential. What survives is the core; what a wider screen adds is progressive enhancement layered on a base that already works. Start wide and squeeze down and you treat the small screen as an afterthought, which is where most people actually are.

01 · Base
Base is the floor

Write the small-screen layout with no media query. That is the default every browser gets, and it must stand on its own.

02 · Enhance
Enhance upward

Each min-width query only adds: more columns, a sidebar that was stacked. Never subtract from wide to reach narrow.

03 · Content
Content sets the line

Add a breakpoint where the layout actually strains, when a line runs long or a card cramps, not where a popular phone happens to end.

02

Five breakpoints, each a structural move

The system ships exactly five named tokens, all min-width thresholds. Below the first is the base, the no-query mobile layout. Treat them as the coarse points where the whole shape of a page may shift, not a grid you snap every element to.

base0
sm576px
md769px
lg992px
xl1200px
2xl1366px
TokenMin widthStructural role
base0The default. Single column, thumb-reachable, no media query. Everything enhances this.
--breakpoint-sm576pxLarge phones. Room for a two-up grid and side-by-side fields.
--breakpoint-md769pxTablets. The first real shift: a stacked shell becomes two panes, nav moves inline.
--breakpoint-lg992pxLaptops. Persistent sidebars, three columns, the full desktop chrome.
--breakpoint-xl1200pxWide desktops. Cap line length and center the shell; comfortable, not stretched.
--breakpoint-2xl1366pxVery wide. Add breathing room and max-widths, not more columns for their own sake.

Rule Key page-shell breakpoints to the five named min-width tokens, and no others.Why There is no xs because the base below sm needs no query to name it. A media condition cannot read a custom property, so the query carries the literal value, @media (min-width: 769px), while the token stays the single source of that number.

RecommendedOne component that strains between two tokens: solve it with fluid sizing or a container query, so the scale stays at five.
AvoidInventing a sixth breakpoint for that one component. It fragments the scale and every reader now has an extra threshold to reason about.
03

Media query or container query

A media query asks how wide the viewport is; right for the page shell, wrong for a component. A card does not care how wide the window is, it cares how much room it was given. A container query lets an element respond to its own container, so the same card lays out one way in a sidebar and another in the main column. Baseline since 2023.

Viewport level
Media query
@media (min-width: 992px)
  • Asks about the whole viewport.
  • Right for the page shell: global grid, top nav, where the sidebar lives.
  • Home of capability and preference features: prefers-color-scheme, prefers-reduced-motion, pointer, hover.
  • Keyed to the five breakpoint tokens.
Component level
Container query
@container (min-width: 24rem)
  • Asks how much space this element was actually given.
  • Right for reusable parts: cards, tiles, widgets that appear in many slots.
  • The parent opts in with container-type; children size to cqw units.
  • The component becomes portable: drop it anywhere and it adapts.

Rule Match the query to its scope: a media query against a token for the page shell, a container query for any part that must work in more than one slot.Why A media query asks how wide the viewport is, which is what the shell responds to. A container query asks how much room an element was actually given, which is what a portable component responds to.

RecommendedFor a capability or preference, dark mode, reduced motion, a coarse pointer, reach for a media query.
AvoidReaching for a container query there. A container knows only its own size, never color scheme, motion preference, or pointer type.
04

The best breakpoint is the one you never wrote

Between the structural thresholds, layout should flow, not jump. Intrinsic layouts lean on what CSS already does: a grid that fits as many columns as comfortably fit, type and spacing that scale as a range. The result adapts at every width, not just five, with far fewer media queries.

reflowCards wrap on their own as space allows.
auto-fitColumn count follows the container width.
minmaxEach column stays between a floor and a fair share.
zero queriesNo breakpoint wrote this. The grid did.

Live, one rule, no media query: grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)). Narrow the docs panel and the columns reflow on their own. Reach for this before any breakpoint.

Fluid type scales with the screen

This heading is clamp(1.5rem, 3vw + 1rem, 2.75rem): it grows and shrinks smoothly, held between a floor and a ceiling. Use clamp(), min(), and max() for type, spacing, and measure to erase the step changes a stack of breakpoints would cause.

Rule Reach for fluid sizing and an intrinsic grid before you write any breakpoint.Why An auto-fit grid and clamped type adapt at every width, not just at the five thresholds, and with far fewer queries to keep in sync.

RecommendedLet the layout flow: repeat(auto-fit, minmax(160px, 1fr)) for columns, clamp() for type and spacing.
AvoidStacking breakpoints to imitate that flow. It steps between thresholds and leaves more queries to maintain.
05

Safe areas and notches

On modern phones the screen is not a clean rectangle. Notches, rounded corners, and the home indicator carve in, and a layout drawn to the physical edge can bury a primary action under a system gesture. Honor the insets for anything anchored to a screen edge, and add the inset to your spacing token rather than replacing it.

The dashed inset is the safe area. The button clears the home indicator because its padding is calc(var(--space-4) + env(safe-area-inset-bottom)), not a bare token. Insets are unlocked by viewport-fit=cover; on a device with none, env() resolves to zero and spacing is unchanged.

Thumb-reach and target sizing for these anchored controls live in Foundations / Spacing. This page claims only the responsibility: keep every screen-anchored control clear of system chrome.

Rule Honor the safe-area insets for anything anchored to a screen edge.Why Notches, rounded corners, and the home indicator carve into the screen, so a control drawn to the physical edge can land under a system gesture.

RecommendedAdd the inset to your token: calc(var(--space-4) + env(safe-area-inset-bottom)). Where a device has no insets, env() resolves to zero and the spacing is unchanged.
AvoidReplacing the token with env() alone, or drawing to the physical edge. Where there are no insets the spacing collapses to zero and the control can fall under system chrome.

Sources

Mobile-first and progressive enhancement: Wroblewski, Mobile First (lukew.com) and Marcotte, Responsive Web Design (alistapart.com). Let content, not device class, set breakpoints: web.dev responsive basics (web.dev). Container queries, Baseline since 2023: MDN (developer.mozilla.org) and web.dev (web.dev/blog/cq-stable). Intrinsic layouts: Jen Simmons (talks.jensimmons.com). Fluid sizing and reflow: MDN clamp() and grid auto-fit / minmax (developer.mozilla.org). Safe areas: MDN env() (developer.mozilla.org) and Apple HIG Layout (developer.apple.com). SPA the five values and the reading of them as coarse structural ceilings are specific to this system's tokens; intrinsic web design is Simmons' coinage, not a spec term.