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
- Explain the role of the viewport meta tag and why it is required.
- Apply mobile-first thinking with
min-widthmedia queries. - Build a fluid layout with relative units,
max-width, and flexible images. - Use the Bootstrap 5.3 container, row, column classes, breakpoints, and a few utility classes.
- Identify reusable UI patterns (card, navbar, form group).
- 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-widthandmax-widthqueries in a confusing order. - Setting fixed widths in pixels on containers; use
max-width. - Hiding content with
d-nonethat screen-reader users still need; rememberdisplay: nonealso hides content from assistive technology. - Nesting a
.rowdirectly in a.containerwithout 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.
- Explain the cause.
- Propose a responsive fix.
- Describe how you would verify it.
Solution.
col-8andcol-4have 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.- Make the layout stack by default and split only from a larger tier:
col-12 col-lg-8andcol-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. - 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-8computes 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-widthqueries 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=1and allow zoom. - Base styles target small screens; larger screens use
min-widthqueries. - Images use
max-width: 100%and have dimensions. - I can read a Bootstrap class such as
col-12 col-md-6 col-lg-4and state its width at each tier. - I know Bootstrap's six tiers and that they are
min-widthbased. - 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
- Meta Front-End Developer Professional Certificate (opens in a new tab)
- Course 1: Introduction to Front-End Development (opens in a new tab)
- Bootstrap 5.3: Breakpoints (opens in a new tab)
- Bootstrap 5.3: Grid system (opens in a new tab)
- MDN: Viewport meta element (opens in a new tab)
- MDN: Responsive design (opens in a new tab)
