Skip to content

Web Foundations and Front-End Roles

Step 3 of 45

Responsive Layouts and UI Frameworks

Introduction

This lesson closes the first chapter of the course and matches the responsive-layout and Bootstrap topics of course 1 of the Meta Front-End Developer pathway. Visitors reach Harbor Workshops on phones at 360px, tablets at 768px, laptops, and wide monitors. One fixed layout cannot serve them all. Responsive design means a single codebase that adapts its layout to the space and capabilities of the device.

You will learn the viewport, the mobile-first approach, media queries, fluid layout tools, reusable UI patterns, and the Bootstrap 5 grid and utility classes. You will also compare writing your own CSS with adopting a framework, and make that choice for a real scenario. The practice scenario is original: adapt the Harbor course catalog to phone and desktop widths.

Learning objectives

  1. Explain the role of the viewport meta tag and why it is required.
  2. Apply mobile-first thinking with min-width media queries.
  3. Build a fluid layout with relative units, max-width, and flexible images.
  4. Use the Bootstrap 5.3 container, row, column classes, breakpoints, and a few utility classes.
  5. Identify reusable UI patterns (card, navbar, form group).
  6. Weigh custom CSS against a framework for a given project.

1. The viewport

Mobile browsers have a concept of a layout viewport. When a page does not say otherwise, many phones assume a desktop-sized layout width (historically about 980px) and shrink the result to fit, making text tiny. Your media queries then compare against that fake width and never fire.

The fix is one line in <head>:

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

width=device-width makes the layout viewport equal the device's width in CSS pixels. initial-scale=1 sets the initial zoom to 100%. Do not add user-scalable=no or maximum-scale=1: they prevent pinch zoom, which low-vision users depend on, and are an accessibility failure.

A CSS pixel is not a physical pixel. A phone with a 1170-physical-pixel-wide screen may report a viewport of 390 CSS pixels (a device pixel ratio of 3). All breakpoints use CSS pixels.

2. Mobile first

Mobile first means writing the base CSS for the smallest screens, then adding rules for larger ones using min-width queries. Why this order?

  • Small screens have the tightest constraints, so you decide priorities first: what content really matters.
  • The base styles are simple (a single column), and larger screens layer on enhancements.
  • Phones download less CSS than if they had to override a desktop layout.

Media queries

.catalog {
  display: grid;
  gap: 1rem;
  grid-template-columns: 1fr;         /* phone: one column */
}

@media (min-width: 40rem) {           /* about 640px */
  .catalog { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 64rem) {           /* about 1024px */
  .catalog { grid-template-columns: repeat(3, 1fr); }
}

Read it as: one column by default; two columns when the viewport is at least 40rem; three at 64rem. Because queries use min-width, the 64rem rule also applies above it, and the later rule wins in a tie due to source order. Keep queries in increasing order.

Choose breakpoints where the content breaks, not at specific device models. Resize your browser, and when a line of text becomes too long or cards get cramped, that is a breakpoint. Using rem or em in queries respects user font-size preferences.

Other useful media features: prefers-reduced-motion, prefers-color-scheme, and orientation.

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

3. Fluid building blocks

A responsive page rests on a few habits, before any framework.

Relative sizing

.container {
  width: min(100% - 2rem, 70rem);
  margin-inline: auto;
}
img {
  max-width: 100%;
  height: auto;
}

min(100% - 2rem, 70rem) takes the smaller value: on phones, the full width minus a 1rem margin each side; on big screens, a cap of 70rem. margin-inline: auto centers the box. max-width: 100% stops images from overflowing their container, and height: auto preserves proportions.

Flexible text

h1 { font-size: clamp(1.75rem, 1.2rem + 2vw, 3rem); }

clamp(min, preferred, max) scales the heading smoothly but never below 1.75rem or above 3rem.

Line length

Long lines hurt readability. Limit paragraphs to roughly 60 to 75 characters: max-width: 65ch; where ch is the width of the character "0".

Responsive images

<img
  src="/img/kiln-640.jpg"
  srcset="/img/kiln-640.jpg 640w, /img/kiln-1280.jpg 1280w"
  sizes="(min-width: 64rem) 33vw, 100vw"
  alt="A kiln glowing during a firing"
  width="640" height="427">

srcset offers candidates with their intrinsic widths; sizes tells the browser how wide the image will be displayed so it picks an appropriate file. Phones avoid downloading a 1280px image they will never fully show.

Layout modules in a nutshell

Flexbox is one-dimensional (a row or a column), good for navigation bars and aligning items. CSS Grid is two-dimensional (rows and columns together), good for page and catalog layouts. Lesson 4.2 covers both in depth; here we use them lightly.

.nav-list { display: flex; flex-wrap: wrap; gap: .5rem 1rem; }

flex-wrap: wrap lets items move to a new line instead of overflowing on narrow screens.

Worked example 1: a responsive catalog

Problem. Harbor has six workshop cards. Requirements: one column under 40rem, two columns from 40rem, three from 64rem. Cards must have equal gaps and never overflow at 320px.

Step 1: markup. Use a list inside a landmark.

<main>
  <h1>Workshops</h1>
  <ul class="catalog">
    <li class="card">
      <h2>Wheel basics</h2>
      <p>Mondays, 18:00. 6 seats.</p>
      <a href="/workshops/wheel-basics">Details</a>
    </li>
    <!-- five more cards -->
  </ul>
</main>

Step 2: base (mobile) styles.

.catalog { list-style: none; padding: 0; margin: 0; display: grid; gap: 1rem; }
.card { border: 1px solid #c8c8c8; border-radius: .5rem; padding: 1rem; }

With no grid-template-columns, the grid has one implicit column, so each card fills the width.

Step 3: enhancements.

@media (min-width: 40rem) { .catalog { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 64rem) { .catalog { grid-template-columns: repeat(3, 1fr); } }

Result. At 320px: six stacked cards. At 800px: three rows of two. At 1200px: two rows of three. No horizontal scrollbar, because grid items shrink to their column.

Why not the alternatives? Fixed width: 300px cards with float: left need manual clearing and break at odd widths. A desktop-first design with max-width queries works, but forces phones to parse rules they never use and tends to leave bugs in the smallest layouts. An auto-fit grid, repeat(auto-fit, minmax(16rem, 1fr)), is also a valid, query-free option; its trade-off is less explicit control over the exact column counts.

4. Reusable UI patterns

Interfaces repeat a small set of patterns. Recognizing them lets you build once and reuse.

Pattern Purpose Notes
Card Group an item's summary and action Used for workshops, tutors
Navigation bar Primary links, often collapsing on mobile Use <nav>
Form group Label plus control plus help or error text Always associate label with input
Alert / message Feedback after an action Use text, not color alone
Modal / dialog Focused task above the page Needs focus management; prefer <dialog>
Pagination / list Browse many items

A pattern is more than looks. A card that is entirely clickable needs one primary link, with accessible text; a collapsing menu needs a real <button> that reports whether it is expanded (aria-expanded).

5. Bootstrap 5.3

Bootstrap is a popular CSS framework (with optional JavaScript components) providing a grid, ready-made components, and utility classes. Version 5.3 does not require jQuery.

Adding Bootstrap

For a quick page you can use the CDN links from the official docs. In an npm project you install it:

npm install bootstrap

and import its stylesheet in your entry file, import "bootstrap/dist/css/bootstrap.min.css";. Always check the getting-started page of the Bootstrap version you use, and remember the viewport meta tag is a requirement of Bootstrap, too.

Breakpoints

Bootstrap 5.3 defines six default tiers, and its grid is mobile-first with min-width queries.

Breakpoint Class infix Viewport width
Extra small (none) under 576px
Small sm 576px and up
Medium md 768px and up
Large lg 992px and up
Extra large xl 1200px and up
Extra extra large xxl 1400px and up

Because the queries are min-width, a class like col-md-6 applies at md and every larger breakpoint.

Containers, rows, columns

The grid has a 12-column system. A container centers and constrains content, a row is a flex wrapper for columns, and columns take a number of the 12 slots.

<div class="container">
  <div class="row">
    <div class="col-12 col-md-6 col-lg-4">Card A</div>
    <div class="col-12 col-md-6 col-lg-4">Card B</div>
    <div class="col-12 col-md-6 col-lg-4">Card C</div>
  </div>
</div>

Reading col-12 col-md-6 col-lg-4: full width on phones (12 of 12), half (6 of 12) from 768px, a third (4 of 12) from 992px. Widths per column: 12/12 = 100%, 6/12 = 50%, 4/12 = 33.33%. If the sum on a row exceeds 12, the extra column wraps to the next line.

Shortcuts: row-cols-1 row-cols-md-2 row-cols-lg-3 on the row sets the number of columns per row without per-column classes. Gutters (spacing) use g-3 and similar classes, and .container-fluid spans the full width.

Utilities

Utility classes apply single rules directly.

Class Effect
mt-3, mb-4, p-2, px-3 Margin or padding on a spacing scale (0 to 5), per side or axis
d-none d-md-block Hidden below md, shown from md
d-flex justify-content-between align-items-center Flex row, space between, vertically centered
text-center, fw-bold, text-muted Text alignment, weight, color
w-100 Width 100%

A Bootstrap version of the catalog

<main class="container py-4">
  <h1 class="mb-4">Workshops</h1>
  <div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-3">
    <div class="col">
      <article class="card h-100">
        <div class="card-body">
          <h2 class="h5 card-title">Wheel basics</h2>
          <p class="card-text">Mondays, 18:00. 6 seats.</p>
          <a class="btn btn-primary" href="/workshops/wheel-basics">Details</a>
        </div>
      </article>
    </div>
    <!-- more columns -->
  </div>
</main>

h-100 makes cards in a row equally tall; h5 applies heading style to an <h2>, keeping the semantic level correct. This matches the earlier lesson: separate meaning from appearance.

Predicted output. One card per line under 768px, two per line from 768px, and three per line from 992px, with consistent gutters.

Framework or custom CSS?

Criterion Bootstrap Custom CSS
Speed to a working prototype Very fast Slower
Consistency across a team Built in, shared vocabulary Needs a style guide
Distinct brand identity Requires theming (Sass variables, CSS variables) Full control
File size Larger unless you import only what you use Only what you write
Learning curve Learn class names Learn CSS deeply
Accessibility Components are tested but you must still use correct semantics Entirely your responsibility
Risk Sites can look alike; overriding fights specificity More code to maintain

A framework does not replace understanding CSS. When a Bootstrap layout misbehaves, you debug with the same box model, cascade, and Elements panel. Choose by project: an internal admin tool benefits from Bootstrap, and a brand-heavy marketing page may justify custom CSS. Many teams combine them: the grid and form styles from the framework, a thin layer of custom brand styles on top.

Note also that React projects frequently use component libraries, such as React-Bootstrap, which wrap Bootstrap's styles as React components. This pathway later returns to the question inside React.

6. Pitfalls and exam tips

  • Forgetting the viewport meta tag, then wondering why media queries "do not work" on phones.
  • Mixing min-width and max-width queries in a confusing order.
  • Setting fixed widths in pixels on containers; use max-width.
  • Hiding content with d-none that screen-reader users still need; remember display: none also hides content from assistive technology.
  • Nesting a .row directly in a .container without columns, or putting content directly in a row (not in a column).
  • Testing only in one desktop window. Use the device toolbar in developer tools, then test a real phone.
  • Horizontal scrolling caused by one wide element; find it by temporarily outlining all elements (* { outline: 1px solid red; }).

Exam tip. In Bootstrap, class prefixes apply "this breakpoint and up". Read column classes from smallest to largest.

Exam tip. If a question says media queries never trigger on a phone, suspect the viewport meta tag.

Exam tip. The mobile-first rule: base styles are for small screens; add min-width queries as the screen grows.

Mini case study: the squeezed booking page

Situation. Harbor's booking page uses this HTML:

<div class="container">
  <div class="row">
    <div class="col-8">Workshop details</div>
    <div class="col-4">Booking summary</div>
  </div>
</div>

On phones the two columns are so narrow that the summary text wraps every two words. A tester also reports that at 300% zoom on a laptop, the page still shows two cramped columns.

Tasks.

  1. Explain the cause.
  2. Propose a responsive fix.
  3. Describe how you would verify it.

Solution.

  1. col-8 and col-4 have no breakpoint infix, so they apply at all widths, from the smallest. At 360px the summary column is 4/12 of about 330px, around 110px. Browser zoom at 300% shrinks the effective viewport to roughly a third (for example, 1280 CSS pixels become about 427), which triggers the same narrow situation.
  2. Make the layout stack by default and split only from a larger tier: col-12 col-lg-8 and col-12 col-lg-4. On a phone each takes the full row; from 992px they sit side by side. Because zoom reduces the effective viewport, zoomed users also receive the stacked layout, which is what WCAG reflow requirements expect.
  3. Verification: use the device toolbar to check 360px, 768px, and 1200px widths; zoom to 400% on a desktop and confirm there is no horizontal scrolling and no overlapping text; check Elements to see that col-lg-8 computes to about 66.67% only at 992px and above.

Why not use d-none on the summary for mobile? The summary contains the price and cancellation terms, which phone users need. Hiding it removes important content instead of reflowing it.

Key terms

  • Responsive design: one page adapting to many screen sizes.
  • Viewport: the visible area where the page is laid out.
  • Mobile first: base CSS for small screens, min-width queries for larger ones.
  • Media query: a conditional block of CSS.
  • Breakpoint: the width at which the layout changes.
  • Fluid layout: uses relative sizes so content adapts.
  • Grid (Bootstrap): 12-column system of container, row, and column.
  • Utility class: a class applying one style rule.
  • Gutter: space between columns.

Summary checklist

  • My pages include width=device-width, initial-scale=1 and allow zoom.
  • Base styles target small screens; larger screens use min-width queries.
  • Images use max-width: 100% and have dimensions.
  • I can read a Bootstrap class such as col-12 col-md-6 col-lg-4 and state its width at each tier.
  • I know Bootstrap's six tiers and that they are min-width based.
  • I can compare a framework with custom CSS for a given project.
  • I tested at phone, tablet, and desktop widths and at high zoom.

Official sources