Skip to content

Arrange content with Flexbox

You will use Flexbox to arrange the profile header, navigation links, and project entries. The layout will distribute available space, wrap when necessary, preserve the HTML source order, and remain usable at narrow widths and 200% browser zoom.

Navigation bars, button groups, card rows, toolbars, and groups of controls often need a one-dimensional layout. Flexbox lets the browser resolve that layout from the available space instead of relying on fixed positions.

What you will practice

  • Distinguish a flex container from its direct-child flex items.
  • Identify the main axis and cross axis for row and column layouts.
  • Use gap, justify-content, align-items, and flex-wrap for spacing, distribution, alignment, and line wrapping.
  • Use flex-basis, flex-grow, and flex-shrink to control how items respond to available space.
  • Preserve meaningful HTML source order and explain why visual reordering can reduce accessibility.
  • Use browser DevTools to inspect a flex container, its axes, gaps, and item sizes.
  • New: Flex containers, flex items, the main and cross axes, display: flex, flex-direction, gap, justify-content, align-items, flex-wrap, flex-basis, flex-grow, flex-shrink, order, and the Flexbox inspector in DevTools.
  • Reused: The html-profile project, semantic HTML, classes, the box model, border-box sizing, fluid page widths, logical spacing, normal flow, browser reloading, and DevTools inspection.

Starting point

Before you start

  • The html-profile folder from the preceding box-model lesson.
  • index.html and styles.css at the same folder level.
  • A profile header with a navigation list, a Projects section with at least two article elements, and the existing project-entry class.
  • A page that already uses border-box sizing, a fluid width, a 48rem maximum width, and deliberate box-model spacing.
  • VS Code and a current version of Edge, Chrome, or Firefox.
Current state
The page uses normal flow. The header content, navigation items, and project entries therefore appear as separate block boxes from top to bottom.
First action
Open html-profile/index.html and find the ul inside the page navigation. Add class="nav-list" to its opening tag.
First checkpoint
The page header and navigation links form flexible rows, and both rows wrap instead of leaving the viewport when space becomes limited.
Help trigger
Use the recovery note or ask for help if a Flexbox rule affects the wrong elements, content leaves the viewport, source order changes, or DevTools does not identify the selected element as a flex container.

You have completed the lesson when:

  • the page header is a flex container that distributes its direct children and can wrap them;
  • the navigation list is a flex container with no list markers and a consistent gap between links;
  • the Projects section contains a project-list wrapper around the existing project articles;
  • the project list can show entries in a row when space permits and on separate lines when it does not;
  • each project entry has an explicit basis, grow factor, and shrink factor;
  • the visible order matches the meaningful HTML source order;
  • all links and fragment destinations still work;
  • all content remains visible at a narrow viewport and 200% browser zoom; and
  • you can inspect one container in DevTools and explain its main axis, cross axis, gaps, wrapping, and item sizes.

Flexbox starts with one element that becomes the flex container. Its direct children become flex items.

Container comparison

display creates the flex formatting context

The same three direct children use normal block flow first and Flexbox second.

display: block

Normal block flow places each child on a separate line.

display: flex

Flexbox places the direct children along the main axis.

display: flex makes the selected element a flex container. Its direct children become flex items and start on one row by default.

The declaration display: flex changes layout behavior. It does not change the HTML meaning of the container or its children.

Only direct children become flex items. In this structure, the three article elements are flex items. Their nested headings and paragraphs are not flex items of project-list.

Flex container and direct-child flex items
<div class="project-list">
<article class="project-entry">
<h3>Semantic profile</h3>
<p>A profile page built with meaningful page regions.</p>
</article>
<article class="project-entry">
<h3>Reference journal</h3>
<p>A journal with independent entries and related notes.</p>
</article>
<article class="project-entry">
<h3>Link collection</h3>
<p>A small set of descriptive links to public work.</p>
</article>
</div>

Every flex container has two axes:

  • The main axis is the direction in which flex items are placed.
  • The cross axis runs perpendicular to the main axis.

flex-direction sets the main axis. The default value is row.

Axis comparison

flex-direction sets the main axis

Changing the main axis also changes which direction justify-content controls.

flex-direction: row

Main axis
Left to right
Cross axis
Top to bottom

Items follow the horizontal main axis.

flex-direction: column

Main axis
Top to bottom
Cross axis
Left to right

Items follow the vertical main axis.

row uses a horizontal main axis in this left-to-right example. column rotates the main axis so the items run from top to bottom.

This lesson uses left-to-right English content. In that context, row starts at the left and runs to the right. Writing direction can change where a row starts, so think in terms of the main axis instead of treating row as a permanent synonym for left-to-right.

The axis model determines which alignment property to use:

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

The header will use one flex row for its direct children. The navigation list inside it will use a second flex row for the links.

In index.html, find the ul inside the page-level nav. Add the nav-list class and keep the existing list items and links unchanged:

html-profile/index.html — navigation list
<ul class="nav-list">
<li><a href="#about">About</a></li>
<li><a href="#skills">Skills</a></li>
<li><a href="#projects">Projects</a></li>
<li><a href="#contact">Contact</a></li>
</ul>

The list remains a semantic list. CSS changes its placement, not its meaning.

gap creates consistent space between neighboring flex items. The gap belongs to the container and does not add space at its outer edges.

Spacing comparison

gap adds space between flex items

The item sizes stay the same. Only the space between neighboring items changes.

gap: 0

Neighboring item boundaries touch.

gap: 1rem

A 1rem gap separates each neighboring pair.

gap creates consistent internal spacing between flex items. It does not add space around the outside edge of the container.

Use gap for spacing inside these flex containers. It avoids assigning different margins to the first and last items.

Distribute main-axis space with justify-content

Section titled “Distribute main-axis space with justify-content”

justify-content controls how a container distributes free space on its main axis. Free space is the room left after the browser accounts for item sizes and gaps.

Main-axis comparison

justify-content distributes free space on the main axis

These row examples have the same item sizes and different main-axis distribution.

justify-content: flex-start

All free space remains after the item group.

justify-content: center

Free space is split before and after the item group.

justify-content: space-between

Free space is placed between items, with no added space at the outer edges.

justify-content moves or separates the group only when the flex container has free space on its main axis.

If the items already use all available main-axis space, justify-content has no free space to distribute. A declaration can therefore be active without producing an obvious change.

align-items sets the default cross-axis alignment for the flex items on their current flex line.

Cross-axis comparison

align-items sets the default cross-axis alignment

These row examples use a vertical cross axis and the same fixed container height.

align-items: stretch

Items without a fixed cross size stretch across the container.

align-items: flex-start

Items keep their natural height and align at the cross-axis start.

align-items: center

Items keep their natural height and sit midway along the cross axis.

align-items sets the cross-axis position or stretch used by the items unless an item has its own alignment override.

The default stretch behavior only stretches an item when its cross size is automatic. A declared height in a row layout, for example, can prevent that item from stretching.

flex-wrap decides whether a flex container must keep one line or can create additional lines.

Available-width comparison

flex-wrap decides whether items can form new lines

Both examples have three items whose preferred widths do not fit on one line.

flex-wrap: nowrap

The default keeps one line, so the items shrink to share the available width.

flex-wrap: wrap

Items move to additional flex lines instead of forcing every item onto one line.

nowrap keeps one flex line and shrinks the items when allowed. wrap creates additional lines when the preferred item widths do not fit.

The default is nowrap. With wrap, a later item moves to another line when its preferred main size does not fit. The HTML order remains unchanged.

Add these rules after the existing page-width rule in styles.css:

html-profile/styles.css — flexible header and navigation
/* Header layout */
header {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 1rem;
align-items: center;
justify-content: space-between;
}
/* Navigation layout */
.nav-list {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin: 0;
padding: 0;
list-style: none;
}

The header’s direct children are the profile label and nav. justify-content: space-between places available main-axis space between them. align-items: center aligns them across the row. flex-wrap: wrap lets the navigation move below the label when the row cannot fit.

The nav-list declaration creates a separate flex container. Its direct li children become flex items. list-style: none removes the bullets, while the ul remains a list in the HTML.

  1. Save both files and reload the page.
  2. Confirm that the profile label and navigation share a row when enough width is available.
  3. Confirm that the navigation links form a row without bullets.
  4. Make the browser narrow and set browser zoom to 200%.
  5. Confirm that the header and navigation wrap, all links remain visible, and no content leaves the viewport.
  6. Select every navigation link and confirm that each link still reaches its matching section.
  • If the links stay in a vertical list, confirm that class="nav-list" is on the ul and that .nav-list includes display: flex.
  • If the complete page becomes a flex row, confirm that display: flex is on header or .nav-list, not on body.
  • If the header leaves the viewport, confirm that both flex containers include flex-wrap: wrap and that no child has a new fixed width.
  • If bullets remain, confirm that list-style: none is in the .nav-list rule.

Checkpoint: The header and navigation respond to available width

What now works
The header distributes its label and navigation in a flexible row. The navigation links use a consistent gap, and both containers wrap at narrow widths without hiding links.
Files changed
html-profile/index.html, html-profile/styles.css
What remains
Group the project entries in a flex container and control how each entry grows and shrinks.
Next action
In index.html, wrap the project articles in a div with class project-list.
If it does not work
Inspect the header and navigation ul separately. Confirm that each selected element has display: flex and that its intended direct children appear as flex items.

The Projects section contains a heading and several project articles. The heading should stay in normal flow above the entries, so the section itself should not become the flex container.

In index.html, add one div around the existing project articles. Do not move the section heading into the wrapper.

html-profile/index.html — Projects section structure
<section id="projects">
<h2>Projects</h2>
<div class="project-list">
<article class="project-entry">
<!-- Keep the existing project heading and paragraph here. -->
</article>
<article class="project-entry">
<!-- Keep the existing project heading and paragraph here. -->
</article>
</div>
</section>

This is a structure excerpt. Use the comments as placement markers. Keep your existing project headings and paragraphs instead of replacing them with the comments.

The div has no added semantic meaning. It provides one container for the related entries because no more specific HTML element is needed. The article elements keep the meaning of each independent project entry.

Set the preferred item size with flex-basis

Section titled “Set the preferred item size with flex-basis”

flex-basis sets a flex item’s preferred size on the main axis before the browser distributes free space. In a row, it supplies a preferred width. In a column, it supplies a preferred height.

Starting-size comparison

flex-basis sets an item’s preferred main size

The first item receives a larger starting size in the second panel.

All items: flex-basis: 4rem

Each item starts from the same main size.

Item 1: flex-basis: 8rem

Item 1 starts wider. Items 2 and 3 keep the 4rem basis.

flex-basis supplies the starting main size before Flexbox distributes positive or negative free space.

The final size can differ from the basis because growing or shrinking can adjust the item after this starting point.

Let items use positive free space with flex-grow

Section titled “Let items use positive free space with flex-grow”

flex-grow is a nonnegative factor. It controls how an item shares positive free space with other growing items on the same flex line.

Positive-space comparison

flex-grow distributes available positive free space

All items have the same 4rem basis. The second panel lets item 2 grow.

All items: flex-grow: 0

Unused main-axis space remains after the three items.

Item 2: flex-grow: 1

Item 2 receives the available positive free space.

A positive flex-grow value lets an item receive a share of remaining main-axis space. A value of 0 prevents growth.

A value of 0 prevents growth. If two items both use flex-grow: 1, they receive equal shares of the positive free space. This does not guarantee equal final widths when their bases or content constraints differ.

Let items reduce their size with flex-shrink

Section titled “Let items reduce their size with flex-shrink”

flex-shrink is also a nonnegative factor. It controls how an item participates when the combined item sizes exceed the available main-axis space.

Negative-space comparison

flex-shrink distributes a size reduction when items do not fit

Each item starts at 8rem, which is wider than the available row when combined.

All items: flex-shrink: 1

All three items become narrower so they can share the line.

Item 2: flex-shrink: 0

Item 2 keeps its 8rem basis. Items 1 and 3 take more of the size reduction.

A positive flex-shrink value lets an item become smaller when the line lacks space. An item with flex-shrink: 0 keeps its basis while the other items shrink more.

A value of 0 prevents an item from shrinking below its basis through the flex shrink calculation. Use that value only when the item must retain its main size and the container has another valid way to handle the missing space.

Add this container rule to styles.css:

html-profile/styles.css — flexible project list
/* Project list layout */
.project-list {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}

Then replace the existing .project-entry rule with this complete version:

html-profile/styles.css — flexible project items
/* Reusable project style and flex sizing */
.project-entry {
flex-basis: 16rem;
flex-grow: 1;
flex-shrink: 1;
margin: 0;
border: 2px solid #9aa9b8;
background-color: #ffffff;
padding: 1.25rem;
}

The 16rem basis gives each entry a readable preferred width. flex-grow: 1 lets entries share space that remains on a line. flex-shrink: 1 lets an entry become narrower when necessary. flex-wrap: wrap lets another entry move to a new line before all entries become too compressed.

The previous lesson used margin-block between project entries. This rule changes the margin to 0 because the container’s gap now owns that spacing.

  1. Save both files and reload the page.
  2. At a wide viewport, confirm that two project entries can share one row.
  3. Make the viewport narrower. Confirm that an entry moves to another line when both preferred widths no longer fit.
  4. At the narrowest useful width and 200% zoom, confirm that every project heading and paragraph remains visible.
  5. Confirm that the Projects heading stays above the project list instead of becoming one of its flex items.
  • If the Projects heading joins the row, move display: flex from #projects to .project-list and confirm that only the articles are inside the wrapper.
  • If the entries remain stacked at every width, inspect .project-list and confirm that it matches the new wrapper and has display: flex.
  • If no gap appears, confirm that .project-entry now has margin: 0 and .project-list has gap: 1rem.
  • If content becomes clipped, remove any fixed height or overflow: hidden declaration left from the preceding lesson’s temporary experiment.

Checkpoint: Project entries grow, shrink, and wrap

What now works
Project articles share a row when space permits, move to new lines when necessary, and keep all text visible. The Projects heading remains in normal flow above them.
Files changed
html-profile/index.html, html-profile/styles.css
What remains
Protect source order, inspect the final layout in DevTools, and complete the final verification.
Next action
Compare the HTML source order with the visible header, navigation, and project order.
If it does not work
Select div.project-list in DevTools. Confirm that it is the flex container and that each article.project-entry is a direct-child flex item.

Keep source order and visual order consistent

Section titled “Keep source order and visual order consistent”

Flexbox can change visual placement without changing the DOM. The order property demonstrates why this distinction matters.

Accessibility comparison

order can separate visual order from source order

The DOM order stays 1, 2, 3 in both examples. Only the second visual changes placement.

No order change

Visual order and DOM order both remain 1, 2, 3.

Item 3 uses order: -1

Visual order becomes 3, 1, 2, while DOM order remains 1, 2, 3.

Visual reordering does not rewrite the HTML reading or keyboard order. Keep meaningful content in the correct source order instead.

Browsers, assistive technologies, search tools, and keyboard navigation use the document structure. A visual order that conflicts with the source order can make the same page communicate different sequences to different users.

Do not add order to the required profile styles. Keep the profile label, navigation links, project entries, and contact content in a meaningful HTML order. Use wrapping to adapt placement without changing that sequence.

Current browser DevTools can identify flex containers and show a layout overlay. The exact button and panel name depend on the browser version.

  1. Save and reload the page.
  2. Right-click one project entry and select Inspect.
  3. Move to its parent div class="project-list" in the element tree.
  4. Confirm that the Styles or Rules panel shows display: flex as an active declaration.
  5. Select the flex, Flexbox, Layout, or equivalent badge or panel for that container.
  6. Turn on the layout overlay if the browser provides one.
  7. Identify the row main axis, the vertical cross axis, the gap, each item boundary, and any wrapped line.
  8. Make the browser wider and narrower. Observe when the articles share a line and when they wrap.
  9. Select one article class="project-entry" and inspect its active flex-basis, flex-grow, and flex-shrink values.
  10. Change flex-basis temporarily in DevTools. Observe the new layout, then reload the page to discard the temporary edit.
  • If DevTools does not show a Flexbox control, confirm that the selected element is div.project-list, not one of its child articles.
  • If display: flex is crossed out, inspect the competing rule and confirm that the selector and braces in styles.css are correct.
  • If a property edit remains after reload, confirm that you changed it only in DevTools and did not save the same temporary value in styles.css.

Checkpoint: The Flexbox inspector matches the rendered layout

What now works
DevTools identifies the project list as a flex container and the articles as flex items. You can point to the axes, gaps, wrapped lines, basis, grow factor, and shrink factor.
Files changed
html-profile/index.html, html-profile/styles.css
What remains
Complete the final checks and explain how available space produces the visible layout.
Next action
Complete the self-check against the saved source, the rendered page, and the DevTools evidence.
If it does not work
Start from div.project-list, verify its active display value, then inspect one container property or item property at a time.

Self-check

Complete these checks against the required result.

  1. Identify each flex container in the final page and name its direct-child flex items.
  2. Point to the main axis and cross axis of the row-based header and project-list containers.
  3. Explain what display: flex changes and what semantic information it does not change.
  4. Confirm that gap creates the visible space inside the navigation and project list without adding outer-edge spacing.
  5. Explain what free space justify-content distributes and why it can have no visible effect in a full container.
  6. Confirm that align-items centers the header items on the cross axis when they share a flex line.
  7. View the page at narrow and wide widths and explain when flex-wrap creates another line.
  8. Inspect one project entry and identify its flex-basis, flex-grow, and flex-shrink values.
  9. Confirm that the visible navigation and project order matches the HTML source order.
  10. Select every navigation link and confirm that all fragment destinations still work.
  11. View the page at 200% zoom and confirm that all text and links remain visible without unintended horizontal scrolling.
  12. Use the DevTools Flexbox overlay or layout panel to identify the project-list container, its item boundaries, gaps, and wrapped lines.

Practice the Flexbox properties with Flexbox Froggy

Section titled “Practice the Flexbox properties with Flexbox Froggy”

Flexbox Froggy is an external practice game. It asks you to use Flexbox declarations to place frogs on matching lily pads. It does not edit your html-profile files and does not replace the browser and DevTools checks in this lesson.

Recommended target: Complete levels 1–19. Those levels practice justify-content, align-items, flex-direction, order, and flex-wrap.

Levels 16 and 17 introduce align-self. Read the game’s explanation and solve the levels, but do not add align-self to the required profile page. Stop after level 19. Levels 20–24 introduce flex-flow and align-content, which this lesson does not require.

Use the game’s Colorblind Mode if the frog and lily-pad colors are difficult to distinguish. If you get stuck, identify the main axis and cross axis first, then use the game’s built-in description of the relevant property.

The browser first identifies the flex container and its direct-child flex items. flex-direction establishes the main axis. The browser uses each item’s basis and content constraints as a starting point, then accounts for gaps and available container space.

If positive free space remains, grow factors can distribute it. If the items need more space than the line provides, shrink factors can reduce their main sizes. Wrapping can create another flex line when the preferred sizes do not fit. The browser then applies main-axis distribution and cross-axis alignment to each line.

Every flex item still uses the box model. Its content, padding, border, and margin remain part of the size calculation. Flexbox changes how a container places its direct children; it does not replace the box model or the semantic HTML structure.

When to use Flexbox and when to keep normal flow

Section titled “When to use Flexbox and when to keep normal flow”

Use Flexbox when a group needs a shared one-dimensional relationship, such as alignment, distribution, flexible item sizing, or wrapping.

Keep normal flow when content only needs to follow source order from top to bottom. The About paragraph, section headings, Current focus note, Contact content, and footer text do not need display: flex in this project. Their existing block flow already matches the intended reading order.

After the required self-check passes, record the working state in the existing html-profile repository:

Commit and push the Flexbox layout
git status
git diff
git add index.html styles.css
git diff --staged
git commit -m "Arrange profile content with Flexbox"
git push

Reload the private GitHub repository and confirm that the commit contains the intended wrappers, classes, and Flexbox rules.

The required lesson is complete when the profile header, navigation, and project list respond to available space; the source and visual orders agree; all content remains available at narrow widths and 200% zoom; and you can explain the final layout with DevTools evidence.

Continue to Write reusable and predictable CSS to organize selectors, custom properties, and cascade behavior into a more maintainable stylesheet.

If you stop here, leave yourself this resume note: The Flexbox layout is working. Next, open the reusable CSS lesson and identify repeated values and selector relationships in styles.css.