Responsive CSS and Layout

Last update: 2023-04-16 14:49:19 +0800 +0800
Available translations: 日本語 / 中文(简体) / 中文(繁體)

Responsive layout should derive from content constraints instead of a list of device models. Intrinsic sizing, flexbox, grid, media queries, and container queries provide complementary controls.

Establish a Responsive Foundation #

The viewport declaration makes the CSS viewport match the device width:

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

Do not add user-scalable=no or a low maximum-scale; page zoom is an accessibility mechanism. Applications that position controls around an on-screen keyboard may also need the visual viewport API, because the visible area can change without the layout viewport changing.

Use border-box sizing and constrain replaced content:

css
html {
  box-sizing: border-box;
}

*,
*::before,
*::after {
  box-sizing: inherit;
}

img,
video,
svg {
  display: block;
  max-inline-size: 100%;
  block-size: auto;
}

Without these rules, padding can unexpectedly increase component width and intrinsically wide media can overflow a narrow viewport.

Center and Constrain Reading Content #

A readable column needs a maximum line length and small-screen gutters:

css
main {
  inline-size: min(100%, 72ch);
  margin-inline: auto;
  padding-inline: 1rem;
}

ch approximates the advance width of the 0 glyph. It is useful for prose measure, while rem and pixels better express controls whose size should not depend on a selected typeface. With the global border-box policy, padding-inline creates writing-mode-aware gutters inside the constrained width; it works for left-to-right, right-to-left, and vertical text without choosing physical left and right properties.

Avoid a fixed min-width on the page’s primary content. A declaration such as min-width: 480px forces horizontal scrolling on viewports narrower than 480 CSS pixels. min(), max(), and clamp() preserve constraints without making unsupported assumptions about the viewport:

css
h1 {
  font-size: clamp(2rem, 1.25rem + 3vw, 4rem);
}

Choose Flexbox or Grid #

Flexbox arranges a one-dimensional sequence along a main axis. Grid defines rows and columns and can align items across both axes.

css
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
}

flex-direction determines the main axis. justify-* properties distribute space along the main axis; align-* properties control the cross axis. align-items sets a default for children, while align-self overrides one item. flex-flow combines flex-direction and flex-wrap.

Grid suits cards whose columns should form automatically:

css
.cards {
  display: grid;
  grid-template-columns: repeat(
    auto-fit,
    minmax(min(18rem, 100%), 1fr)
  );
  gap: 1rem;
}

The inner min() prevents an 18 rem minimum from overflowing a container narrower than 18 rem. auto-fit collapses empty tracks, allowing populated cards to consume the available width.

Use minmax(0, 1fr) when grid content must be allowed to shrink below its intrinsic minimum:

css
.sidebar-layout {
  display: grid;
  grid-template-columns: minmax(12rem, 1fr) minmax(0, 3fr);
}

Add Media Queries for Environmental Constraints #

Media queries should introduce a layout change when content no longer fits:

css
@media (width < 48rem) {
  .sidebar-layout {
    grid-template-columns: 1fr;
  }
}

Range syntax expresses the boundary directly. Common media features include width, height, orientation, aspect-ratio, resolution, prefers-color-scheme, prefers-reduced-motion, hover, and pointer.

Input features matter because a narrow touchscreen and a narrow desktop window do not offer identical interaction:

css
@media (hover: hover) and (pointer: fine) {
  .menu-item:hover {
    text-decoration: underline;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    scroll-behavior: auto;
    animation-duration: 1ms;
    animation-iteration-count: 1;
    transition-duration: 1ms;
  }
}

Do not use hover: none as proof that a device is a phone. Hybrid devices can have several input mechanisms.

Use Container Queries for Reusable Components #

A component can appear in a wide page column or a narrow sidebar. Its own available space is therefore more useful than the viewport:

css
.card-region {
  container-type: inline-size;
}

.card {
  display: grid;
  gap: 1rem;
}

@container (width >= 32rem) {
  .card {
    grid-template-columns: 10rem minmax(0, 1fr);
  }
}

Containment belongs on an ancestor of the queried component. Avoid querying the size of the same element whose styles the query changes because that creates circular sizing logic.

Container queries style descendants, not the query container itself. Child items can still overflow through their intrinsic minimum sizes, so apply min-inline-size: 0 or a suitable wrapping policy where content must shrink.

Test Layout Behavior #

Test at the widths where content begins to wrap, overflow, or leave excessive space rather than only at named device presets. Include:

  • Long words, URLs, translated text, and large font settings.
  • Keyboard focus and zoom to at least 200 percent.
  • Reduced motion and both color schemes.
  • Coarse and fine pointers.
  • Empty, minimal, and maximum data sets.

A responsive layout remains valid across a continuous width range; it does not switch between a small number of screenshots.

Comments

Read or comment on GitHub (sign-in required to comment)