Search the library

What would you like to learn?

Type two or more characters to search.

You can also browse all tutorials or topics.

Web Foundations

Build a Responsive Layout with CSS Flexbox

Build a flexible responsive layout with display flex, gap, wrapping, alignment, flexible widths, and a content-led breakpoint.

CSSHTMLResponsive design

You will learn

  • Identify a flex container, flex items, main axis, and cross axis
  • Arrange and align items with Flexbox without fixed device widths
  • Give items flexible base sizes and allow them to wrap
  • Build and test a mobile-first card layout at content-led breakpoints

Before you start

  • Understand semantic HTML and basic CSS selectors
  • Know how padding, borders, width, and border-box affect an element
On this page

Flexbox arranges a group of items in one dimension: a row or a column. It is especially useful for navigation, button groups, card rows, and layouts where items should share available space or move onto another line.

In this lesson, three learning-resource cards will stack on narrow screens. When the content has enough room, they will form a flexible row and wrap when necessary. The breakpoint follows the cards, not a named phone model.

What Flexbox solves

Normal document flow already gives you a readable single column. Flexbox adds control when related items need to:

  • sit in a row or column;
  • share extra space;
  • align along an axis;
  • keep consistent gaps;
  • shrink or grow within limits;
  • wrap onto another line.

Flexbox is one-dimensional. It works with a main axis and a cross axis. For a layout that needs deliberate rows and columns at the same time, CSS Grid may be a better tool. Choosing Flexbox here is intentional because the cards form one flexible sequence.

Flex container and flex items

Set display: flex on a parent:

.resource-list {
  display: flex;
}

.resource-list becomes the flex container. Its direct children become flex items. Content nested inside each card is not automatically a flex item of .resource-list; only the direct children participate in that flex formatting context.

<div class="resource-list">
  <article class="resource-card">...</article>
  <article class="resource-card">...</article>
  <article class="resource-card">...</article>
</div>

Main axis and cross axis

The main axis follows flex-direction. The cross axis runs perpendicular to it.

With the default flex-direction: row, the main axis runs inline—left to right in an English left-to-right document. The cross axis runs vertically.

With flex-direction: column, the main axis runs vertically and the cross axis runs horizontally.

Avoid memorizing justify-content as “horizontal” and align-items as “vertical.” Their physical direction changes with flex-direction:

  • justify-content works along the main axis;
  • align-items works along the cross axis.

Start with display: flex

This mobile-first base rule creates a flex formatting context and a vertical sequence:

.resource-list {
  display: flex;
  flex-direction: column;
}

The cards stack without requiring a narrow-screen media query. Their HTML source order stays the reading and keyboard order.

Use gap for consistent space

gap adds space between flex items without adding outside space before the first or after the last item:

.resource-list {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

This is clearer than applying a right margin to every card and then removing it from the last one. Each card still uses padding for its internal space; the container’s gap separates cards.

Change direction deliberately

At wider sizes, the cards can use a row:

@media (width >= 42rem) {
  .resource-list {
    flex-direction: row;
  }
}

Do not change visual order with row-reverse merely to compensate for inconvenient HTML. Keep a logical source order first. Visual reordering can make keyboard focus and screen-reader reading order differ from the appearance.

Allow items to wrap

Flex items stay on one line by default. Add wrapping when items should move to a new line instead of becoming too narrow:

.resource-list {
  flex-direction: row;
  flex-wrap: wrap;
}

The shorthand flex-flow combines direction and wrapping:

.resource-list {
  flex-flow: row wrap;
}

Wrapping does not choose a fixed number of cards per row. The available width and each item’s sizing determine how many fit.

Understand justify-content

justify-content positions the group of items when the flex container has spare space along its main axis:

.resource-list {
  justify-content: flex-start;
}

Other values include center and space-between. Do not reach for space-between automatically. It can create very large gaps on wide screens and no gap when items consume all available space. A regular gap is often more predictable for cards.

Understand align-items

align-items controls items on the cross axis. In a row, align-items: stretch is the common default behavior and can make cards in a line share the line’s cross size.

Use align-items: center when items should keep their own cross size and align at the center. For content cards, stretching is often useful because card backgrounds align even when their text lengths differ.

Give items flexible sizes

The flex shorthand can define growth, shrinking, and the base size:

.resource-card {
  flex: 1 1 15rem;
}

Read those values as:

  • 1 — the item may grow and share available space;
  • 1 — the item may shrink when needed;
  • 15rem — start from a preferred main size of 15rem.

This is not a guaranteed 15rem width. It is a basis used by the Flexbox sizing algorithm. Combined with wrapping, it encourages readable cards while still adapting to the container.

Add min-width: 0 when a flex item contains long content that might otherwise resist shrinking. The content itself may also need overflow-wrap: anywhere or an internal scrolling wrapper, depending on what it contains.

Build the mobile-first base layout

Begin with a column. This works before the media query and keeps the narrowest experience simple:

.resource-list {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  margin-top: 2rem;
}

.resource-card {
  min-width: 0;
  padding: 1.5rem;
  border: 1px solid #d3d9e8;
  border-radius: 1rem;
  background: white;
}

There is no flex-basis in the column layout. A basis would size the vertical main axis here and could create needlessly tall cards. We apply the flexible basis only when the row enhancement begins.

Add a breakpoint when the content needs it

Resize the page slowly. The breakpoint belongs where two useful cards can fit without cramped headings, links, or paragraph lines:

@media (width >= 42rem) {
  .resource-list {
    flex-flow: row wrap;
  }

  .resource-card {
    flex: 1 1 15rem;
  }
}

In a production component, you might keep flex off the column rule and add it only here for conceptual clarity. In this small example, it causes no overflow in either direction, but understanding the current main axis helps you reason about what the basis means.

Use relative units such as rem for content-led breakpoints when practical. A reader who increases their default text size may then reach the multi-column arrangement only when there is enough room for that larger text.

Responsive design is not a device-width list

A page does not know that “430px means one particular phone.” Browser windows, split-screen views, zoom, embedded panels, and future devices produce many available widths.

Build a flexible base. Resize until content becomes uncomfortable. Add a breakpoint that solves that content problem. Then keep testing between your chosen widths instead of checking only the endpoints.

Build the complete resource-card page

Create two files:

flexbox-resources/
├── index.html
└── styles.css
index.htmlhtml
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Web foundation resources</title>
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <main>
      <p class="eyebrow">Learning resources</p>
      <h1>Choose a web foundation to practice</h1>
      <p class="intro">
        Each card keeps a readable width and moves when the available space changes.
      </p>

      <div class="resource-list">
        <article class="resource-card">
          <h2>Document structure</h2>
          <p>Practice the doctype, document head, and visible body.</p>
          <a href="#document-structure">Review HTML structure</a>
        </article>
        <article class="resource-card">
          <h2>The box model</h2>
          <p>See how content, padding, borders, and margins affect size.</p>
          <a href="#box-model">Review the box model</a>
        </article>
        <article class="resource-card">
          <h2>Flexbox</h2>
          <p>Arrange a one-dimensional set of items with flexible sizing.</p>
          <a href="#flexbox">Practice Flexbox</a>
        </article>
      </div>
    </main>
  </body>
</html>

The fragment links stand in for real sections in a larger page. In production, each link should point to an existing destination with a unique matching ID or a real route.

styles.csscss
* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: system-ui, sans-serif;
  background: #f5f6fb;
  color: #17203a;
}

main {
  width: min(70rem, calc(100% - 2rem));
  margin-inline: auto;
  padding-block: clamp(3rem, 10vw, 7rem);
}

.eyebrow {
  color: #3946d6;
  font-weight: 800;
  text-transform: uppercase;
}

h1 {
  max-width: 18ch;
  line-height: 1.08;
}

.intro {
  max-width: 58ch;
  line-height: 1.7;
}

.resource-list {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  margin-top: 2rem;
}

.resource-card {
  min-width: 0;
  padding: 1.5rem;
  border: 1px solid #d3d9e8;
  border-radius: 1rem;
  background: white;
}

.resource-card h2 {
  margin-top: 0;
}

.resource-card a {
  color: #2634c7;
  font-weight: 750;
}

.resource-card a:focus-visible {
  outline: 3px solid #008ba0;
  outline-offset: 3px;
}

@media (width >= 42rem) {
  .resource-list {
    flex-flow: row wrap;
  }

  .resource-card {
    flex: 1 1 15rem;
  }
}

Test several viewport widths

Use responsive browser tools or resize a normal window. Check 390px, 430px, 768px, 1024px, and a wide desktop view.

At every width, verify:

  • there is no page-level horizontal scrollbar;
  • headings and links do not collide;
  • cards wrap instead of becoming unreadably narrow;
  • the source and keyboard-focus order stay logical;
  • focus is visible without relying only on a color change;
  • the layout remains usable at 200% zoom.

Also drag slowly between the named widths. Responsive failures often occur between familiar presets.

Common mistakes

Applying display: flex to the card instead of the card list. Flexbox affects direct children. Inspect which element needs to be the container.

Forgetting flex-wrap. Items remain on one line by default and may shrink too far.

Using justify-content to create all spacing. Use gap for a steady distance between items; reserve alignment properties for alignment decisions.

Choosing a breakpoint from a device chart. Let your content show where the layout needs to change.

Reordering cards visually. Preserve meaningful DOM order so reading and focus order match what people see.

Giving cards a fixed height. Different text lengths and zoom levels need natural vertical growth.

Practice

Practice: change the cards, not the device list

Add a fourth card with a longer heading. Resize slowly and find the first width where two cards have comfortable line lengths. Try a 16rem flex basis, then 18rem, and explain why the wrapping points change. Finally press Tab through every link and confirm the focus order follows the visual sequence.

What to learn next

You have all six Web Foundations lessons in place. Combine them in the capstone: build a responsive personal landing page with semantic HTML and CSS. The project turns document structure, semantics, selectors, the box model, and Flexbox into one complete page.

Keep exploring

Continue with primary sources

Official documentation