Skip to content

Build responsive components

You will build one resource-card component that uses a stacked layout in a narrow container and places its content and action side by side when its own container has enough inline space.

The same component can appear in a full-width page, a narrow sidebar, a dashboard column, or a nested content region. A viewport media query cannot describe all of those contexts. A container query lets the component respond to the space its parent gives it.

What you will practice

  • Distinguish viewport-responsive page layout from container-responsive component layout.
  • Build a complete narrow-first component before adding a conditional arrangement.
  • Use CSS Grid, minmax, intrinsic sizing, and logical properties to prevent overflow.
  • Establish a named inline-size query container on a component host.
  • Use one content-based container breakpoint to change the component arrangement.
  • Preserve semantic order, keyboard order, visible focus, readable text, and target size across layouts.
  • Verify one component in narrow and wide containers without changing the viewport.
  • New: Component hosts, CSS Grid, intrinsic track sizing, named query containers, container-type, the container shorthand, @container, component-level breakpoints, and a narrow-first fallback for unsupported conditional rules.
  • Reused: Semantic HTML, classes, custom properties, the box model, Flexbox concepts, media-query reasoning, logical properties, relative units, source order, keyboard testing, visible focus, 320px checks, 200% zoom, DevTools, and Git checkpoints.

Starting point

Before you start

  • VS Code and a current version of Edge, Chrome, or Firefox.
  • The completed Level 1 CSS and responsive-layout learning goals.
  • Permission to create a new folder with index.html and styles.css.
  • No JavaScript, framework, package manager, or starter files are required.
Current state
You can make a complete page respond to viewport width, but you have not built a reusable component that responds to its containing block.
First action
Create a folder named responsive-components, open it in VS Code, and create index.html and styles.css.
First checkpoint
VS Code shows index.html and styles.css at the same level inside responsive-components.
Help trigger
Use the recovery note or ask for help if both component examples always use the same arrangement, the container query never appears in DevTools, long text creates horizontal scrolling, or keyboard focus follows a different order from the visible content.

You have completed the lesson when:

  • responsive-components contains index.html and styles.css;
  • index.html contains two examples of the same resource-card structure inside hosts with different available inline sizes;
  • each card uses an article, descriptive headings, a list for related facts, and a real link for navigation;
  • the base card uses a complete one-column Grid layout without a media or container query;
  • each .resource-card-host establishes the named resource-card inline-size container;
  • one @container resource-card (min-width: 30rem) rule changes the card to a content-and-action arrangement;
  • the host remains outside the conditional styles that change its descendant card;
  • the narrow example remains stacked while the wide example uses two columns at the same viewport width;
  • long text wraps, neither example creates horizontal overflow, and the action keeps a visible focus state and a minimum 2.75rem block size;
  • source, reading, visual, and keyboard order remain meaningful in both arrangements;
  • the page passes about 320px width and 200% browser zoom; and
  • the final HTML and CSS produce no browser Console error or invalid-rule warning.

Create the document and component examples

Section titled “Create the document and component examples”

Add this document to index.html:

responsive-components/index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Responsive resource cards</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<main>
<h1>Responsive resource cards</h1>
<p>
The same component can receive different amounts of space.
Its content and action must remain readable in both contexts.
</p>
<div class="demo-stack">
<section aria-labelledby="narrow-context-title">
<h2 id="narrow-context-title">Narrow context</h2>
<div class="resource-card-host resource-card-host--narrow">
<article class="resource-card">
<div class="resource-card__body">
<p class="resource-card__status">Required path</p>
<h3>Component test plan</h3>
<p class="resource-card__description">
Verify layout, text wrapping, zoom, and keyboard access.
</p>
<ul class="resource-card__facts">
<li><strong>Scope:</strong> one reusable card</li>
<li><strong>Evidence:</strong> one test matrix</li>
</ul>
</div>
<a class="resource-card__action" href="#verification">
Open verification steps
</a>
</article>
</div>
</section>
<section aria-labelledby="wide-context-title">
<h2 id="wide-context-title">Wide context</h2>
<div class="resource-card-host">
<article class="resource-card">
<div class="resource-card__body">
<p class="resource-card__status">Required path</p>
<h3>Component test plan</h3>
<p class="resource-card__description">
Verify layout, text wrapping, zoom, and keyboard access.
</p>
<ul class="resource-card__facts">
<li><strong>Scope:</strong> one reusable card</li>
<li><strong>Evidence:</strong> one test matrix</li>
</ul>
</div>
<a class="resource-card__action" href="#verification">
Open verification steps
</a>
</article>
</div>
</section>
</div>
<section id="verification">
<h2>Verification steps</h2>
<p>
Compare both card contexts, then test the complete page at a narrow
viewport and at 200% browser zoom.
</p>
</section>
</main>
</body>
</html>

The two card examples use the same element order and class names. Only the host context differs. resource-card-host--narrow will constrain one host so both component arrangements can be compared at the same viewport width.

The component keeps one primary action. The link navigates to an existing section. It is not a button because the action changes the current location to a fragment target.

Save the file and open it in a browser. Confirm that:

  • each context has an H2 heading;
  • each card’s H3 follows its context heading;
  • each card contains the status, title, explanation, facts, and link in that source order;
  • both links reach Verification steps; and
  • Tab reaches the narrow action before the wide action.

Correct the HTML before adding CSS. The stylesheet will change arrangement, not meaning or sequence.

Add these rules to styles.css:

responsive-components/styles.css
:root {
--color-page: #f3f7f5;
--color-surface: #ffffff;
--color-text: #1d2b26;
--color-muted: #45564f;
--color-border: #687b72;
--color-primary: #176b4d;
--color-primary-strong: #0d4f38;
--color-primary-container: #dcefe7;
--color-focus: #c2410c;
--space-1: 0.5rem;
--space-2: 0.75rem;
--space-3: 1rem;
--space-4: 1.5rem;
--space-5: 2rem;
--radius: 0.5rem;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
color: var(--color-text);
background: var(--color-page);
font-family: system-ui, sans-serif;
line-height: 1.6;
}
main {
inline-size: min(calc(100% - 2rem), 64rem);
margin-inline: auto;
padding-block: var(--space-5);
}
.demo-stack {
display: grid;
gap: var(--space-5);
margin-block: var(--space-5);
}
.resource-card-host--narrow {
max-inline-size: 22rem;
}
.resource-card {
display: grid;
gap: var(--space-4);
min-inline-size: 0;
border: 1px solid var(--color-border);
border-radius: var(--radius);
background: var(--color-surface);
padding: var(--space-4);
}
.resource-card__body {
min-inline-size: 0;
}
.resource-card__status {
inline-size: fit-content;
margin: 0 0 var(--space-2);
border-radius: 999px;
background: var(--color-primary-container);
padding: 0.25rem var(--space-2);
color: var(--color-primary-strong);
font-weight: 700;
}
.resource-card h3 {
margin-block: 0 var(--space-1);
line-height: 1.25;
}
.resource-card__description {
margin-block: 0;
color: var(--color-muted);
}
.resource-card__facts {
display: flex;
flex-wrap: wrap;
gap: var(--space-1) var(--space-3);
margin-block: var(--space-3) 0;
padding: 0;
list-style: none;
}
.resource-card__action {
display: inline-flex;
align-items: center;
justify-content: center;
min-block-size: 2.75rem;
max-inline-size: 100%;
border: 2px solid var(--color-primary);
border-radius: var(--radius);
padding: var(--space-1) var(--space-3);
color: var(--color-primary-strong);
font-weight: 700;
overflow-wrap: anywhere;
text-align: center;
text-decoration-thickness: 0.1em;
text-underline-offset: 0.2em;
}
.resource-card__action:hover {
background: var(--color-primary-container);
}
.resource-card__action:focus-visible {
outline: 3px solid var(--color-focus);
outline-offset: 3px;
}

The base .resource-card rule has one Grid column because no column track list is declared. Grid items fill rows in source order.

The base component already handles the constrained context:

  • min-inline-size: 0 lets Grid content become narrower than its automatic minimum when text can wrap.
  • The body repeats that rule at the nested content boundary.
  • Facts use Flexbox because they form one-dimensional metadata that can wrap.
  • The link’s block size gives its text and focus target room without a fixed width.
  • Logical properties follow the document’s writing mode.
  • Long action text can wrap instead of widening the component.

The narrow card is the fallback. If a browser ignores the later container rule, the content remains available in this stacked arrangement.

At this point, both examples must remain stacked.

  1. Resize the viewport from wide to about 320px.
  2. Confirm that the page edge space remains, both cards fit, and link text can wrap.
  3. Set browser zoom to 200% in the normal page view.
  4. Use Tab and Enter on both actions.
  5. Confirm visible focus and no horizontal scrolling.

Checkpoint: The complete component works without a query

What now works
Both cards use the same stacked source order, long text can wrap, links meet the required block size, and the page remains usable at 320px and 200% zoom.
Files changed
responsive-components/index.html, responsive-components/styles.css
What remains
Establish the query containers and add one content-based wider arrangement.
Next action
Add the named container rule to every resource-card host, then confirm it in DevTools before writing @container.
If it does not work
Inspect the first box wider than the viewport. Keep the focus outline and correct the component width, minimum, padding, or unbroken text that causes the overflow.

A query container is an ancestor whose features a conditional @container rule can test. Add this rule before .resource-card-host--narrow:

Create a named inline-size query container
.resource-card-host {
container: resource-card / inline-size;
}

The shorthand sets two values:

  • resource-card is the container name; and
  • inline-size establishes this element for size queries on its inline axis.

The card is a descendant of the host. A size query can condition styles for descendants based on the host’s measured content-box size. The query does not use the physical screen or a device label.

Inspect each .resource-card-host in DevTools. Confirm that its computed container name is resource-card and its container type is inline-size.

Do not put container: resource-card / inline-size on .resource-card and then expect that card to change itself from its own query. Keep one host element as the measurement boundary and style the descendant component.

Choose a component breakpoint from content

Section titled “Choose a component breakpoint from content”

Use the working stacked card to find when a side-by-side arrangement becomes useful:

  1. Start with the narrow host at 22rem.
  2. In DevTools, increase its max-inline-size slowly.
  3. Find the range where the body text remains readable and the complete action fits beside it without crowding.
  4. Test the longest required title, fact, and action text near that range.
  5. Choose a nearby stable value in rem.

The reference content uses 30rem. This is a component breakpoint, not a phone, tablet, or desktop breakpoint. If your required content needs more space, record the evidence and choose a nearby value that passes the same tests.

Add this rule at the end of styles.css:

Use the wider card arrangement
@container resource-card (min-width: 30rem) {
.resource-card {
grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
}
.resource-card__action {
justify-self: end;
}
}

When a named host is at least 30rem wide, the descendant card receives two Grid columns:

  • minmax(0, 1fr) lets the body take remaining space and shrink below its automatic content minimum when wrapping is possible; and
  • auto sizes the action column from the link’s content and padding.

Only the arrangement changes. Source order, colors, spacing, semantics, and interaction states continue to come from the base rules.

Save and reload at a wide viewport. The narrow host stays below 30rem, so its card remains stacked. The unconstrained host has more space, so its card places the body and action in separate columns.

Prove that the container causes the change

Section titled “Prove that the container causes the change”

Do not resize the viewport during this check.

  1. Inspect the wide .resource-card-host and reduce its inline size below 30rem in DevTools. Its card becomes stacked.
  2. Increase that host above 30rem. Its card returns to two columns.
  3. Repeat with the narrow host by temporarily removing its maximum size. It receives the wider arrangement when its own size crosses the threshold.
  4. Reload the page to remove temporary DevTools edits.

The same viewport now contains components in different arrangements. That is the evidence that distinguishes the container query from a viewport media query.

Checkpoint: One component adapts to two container contexts

What now works
At one viewport width, the narrow host keeps the stacked card and the wide host triggers the two-column card; changing one host size in DevTools changes only its descendant component.
Files changed
responsive-components/styles.css
What remains
Stress-test content, source order, zoom, keyboard access, and the fallback boundary.
Next action
Replace the reference title and link text with the long test strings, then run the final matrix below.
If it does not work
Inspect the host's computed container name, type, and inline size. Then inspect whether the @container declarations appear active on its descendant card.

A reference card that works only with short English text is not a robust component.

Run these temporary content tests in both examples:

  • Replace the title with Document cross-platform component verification evidence.
  • Replace the action with Open responsive component verification requirements.
  • Add the unbroken reference RESPONSIVE-COMPONENT-VERIFICATION-2026 to one fact.
  • Increase the browser’s default text size if that setting is available.

Confirm that text wraps, the action stays inside the card, the two-column layout does not overlap, and no focus outline is clipped. Restore the required reference content after the test.

Do not repair content pressure with a fixed card height, hidden overflow, smaller body text, or an earlier device-named breakpoint. Give content room to wrap, then adjust the component threshold only when the side-by-side arrangement lacks enough space.

The two-column Grid keeps the body before the action in the DOM. Do not use order, named grid placement, or a reversed flow to create a visual sequence that conflicts with source order.

Use this final matrix:

Condition Expected result
Narrow host below 30rem Body and action stack in source order
Wide host at or above 30rem Body and action use two columns without overlap
Both hosts in one wide viewport Each card follows its own host size
Page near 320px Both cards fit without horizontal scrolling
Normal page view at 200% zoom Content reflows and both actions remain visible and operable
Long-content stress test Text wraps without changing meaning or hiding content
Keyboard forward and backward Focus follows source order and remains visible
Fragment activation Each action reaches Verification steps
Container rule disabled in DevTools Both cards return to the complete stacked fallback

Use the Grid and Layout panels to inspect active tracks and container sizes. Correct the first failed condition before adding another rule.

Self-check

Complete these checks against the required result.

  1. Confirm that both examples use the same article, body, facts, and action class structure inside different resource-card hosts.
  2. Explain why the link is an anchor rather than a button and confirm that its fragment target exists.
  3. Disable the @container rule and confirm that the stacked base remains complete and usable.
  4. Inspect each host and confirm the resource-card container name and inline-size container type.
  5. At one wide viewport, confirm that the narrow card stacks while the wide card uses two columns.
  6. Change only one host width in DevTools and confirm that only its descendant card crosses the component breakpoint.
  7. Explain why minmax(0, 1fr) and min-inline-size: 0 help content shrink and wrap inside Grid.
  8. Compare source, visual, reading, and keyboard order in both arrangements and confirm that none conflicts.
  9. Run the long-title, long-action, and unbroken-reference tests and confirm no overlap or horizontal overflow.
  10. Test the page near 320px and at 200% zoom and confirm that both actions remain visible, focused, and operable.
  11. Search styles.css for fixed card heights, overflow-x: hidden, order, row-reverse, and column-reverse and confirm that none masks a layout problem.
  12. Reload the final page and confirm that DevTools reports no invalid CSS rule and the Console contains no page error.

Use the right conditional tool for the relationship:

Question Tool
Does the complete page respond to viewport width or user preferences? Media query
Does a component respond to the size of the region that contains it? Container query
Can the layout adapt continuously without a condition? Intrinsic sizing, wrapping, Flexbox, or Grid

Start with intrinsic layout. Add a media or container query only when the content needs a discrete change in arrangement.

The required component uses three layers:

semantic source order
-> complete stacked Grid and wrapping
-> one container condition for the wider arrangement

Each later layer changes less than the layer before it. This keeps the fallback complete and the conditional code focused.

Symptom Likely cause Focused check
The query never matches The host has no query type, the name differs, or its measured size stays below the threshold Inspect computed container values and content-box inline size
Both cards change together A viewport media query or shared fixed width controls them Disable the condition and compare each host size at one viewport
The host collapses or sizes unexpectedly Inline-size containment has no independent size from its layout context Inspect the host’s parent sizing and remove unnecessary intrinsic dependence
Long text widens the card A Grid item keeps an automatic minimum or text cannot break Confirm minmax(0, 1fr), min-inline-size: 0, and a focused wrapping rule
Action overlaps content The two-column threshold is too small for required content Return to stacked layout sooner based on observed content pressure
Keyboard order feels reversed Visual placement conflicts with the DOM Remove reordering and keep meaningful source order
Focus outline is clipped The host or ancestor hides overflow or has no edge space Preserve the outline and correct overflow or spacing at its source

After the required self-check passes, initialize or use the private responsive-components repository and record only the verified files:

Commit and push the responsive component
git status
git diff
git add index.html styles.css
git diff --staged
git commit -m "Build container-responsive resource card"
git push

Confirm that the remote contains the complete stacked base and the single tested container query.

The required lesson is complete when one semantic card works in narrow and wide hosts, its container—not the viewport—controls the arrangement, and the complete fallback passes long-content, keyboard, width, and zoom checks.

Continue to Create accessible web animations to add motion only where it communicates a useful state change and to provide a reduced-motion result.

If you stop here, leave yourself this resume note: The resource card has a complete stacked fallback and one container-based wider arrangement. Next, open the animation lesson and define the state change before adding motion.