Build responsive components
Outcome
Section titled “Outcome”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.
Why this matters
Section titled “Why this matters”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.
What is new and what is reused
Section titled “What is new and what is reused”- New: Component hosts, CSS Grid, intrinsic track sizing, named query containers,
container-type, thecontainershorthand,@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,
320pxchecks,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.
Required result
Section titled “Required result”You have completed the lesson when:
responsive-componentscontainsindex.htmlandstyles.css;index.htmlcontains 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-hostestablishes the namedresource-cardinline-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.75remblock size; - source, reading, visual, and keyboard order remain meaningful in both arrangements;
- the page passes about
320pxwidth and200%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:
<!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.
Check the unstyled structure
Section titled “Check the unstyled structure”Save the file and open it in a browser. Confirm that:
- each context has an
H2heading; - each card’s
H3follows 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 the page and component foundation
Section titled “Add the page and component foundation”Add these rules to 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;}Read the narrow-first component
Section titled “Read the narrow-first component”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: 0lets 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.
Test before the conditional layout
Section titled “Test before the conditional layout”At this point, both examples must remain stacked.
- Resize the viewport from wide to about
320px. - Confirm that the page edge space remains, both cards fit, and link text can wrap.
- Set browser zoom to
200%in the normal page view. - Use Tab and Enter on both actions.
- 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.
Establish a component query boundary
Section titled “Establish a component query boundary”A query container is an ancestor whose features a conditional @container rule can test. Add this rule before .resource-card-host--narrow:
.resource-card-host { container: resource-card / inline-size;}The shorthand sets two values:
resource-cardis the container name; andinline-sizeestablishes 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:
- Start with the narrow host at
22rem. - In DevTools, increase its
max-inline-sizeslowly. - Find the range where the body text remains readable and the complete action fits beside it without crowding.
- Test the longest required title, fact, and action text near that range.
- 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 one container query
Section titled “Add one container query”Add this rule at the end of styles.css:
@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; andautosizes 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.
- Inspect the wide
.resource-card-hostand reduce its inline size below30remin DevTools. Its card becomes stacked. - Increase that host above
30rem. Its card returns to two columns. - Repeat with the narrow host by temporarily removing its maximum size. It receives the wider arrangement when its own size crosses the threshold.
- 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.
Stress-test the reusable component
Section titled “Stress-test the reusable component”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-2026to 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.
Verify order and interaction
Section titled “Verify order and interaction”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.
- Confirm that both examples use the same article, body, facts, and action class structure inside different resource-card hosts.
- Explain why the link is an anchor rather than a button and confirm that its fragment target exists.
- Disable the @container rule and confirm that the stacked base remains complete and usable.
- Inspect each host and confirm the resource-card container name and inline-size container type.
- At one wide viewport, confirm that the narrow card stacks while the wide card uses two columns.
- Change only one host width in DevTools and confirm that only its descendant card crosses the component breakpoint.
- Explain why minmax(0, 1fr) and min-inline-size: 0 help content shrink and wrap inside Grid.
- Compare source, visual, reading, and keyboard order in both arrangements and confirm that none conflicts.
- Run the long-title, long-action, and unbroken-reference tests and confirm no overlap or horizontal overflow.
- Test the page near 320px and at 200% zoom and confirm that both actions remain visible, focused, and operable.
- Search styles.css for fixed card heights, overflow-x: hidden, order, row-reverse, and column-reverse and confirm that none masks a layout problem.
- Reload the final page and confirm that DevTools reports no invalid CSS rule and the Console contains no page error.
Container query mental model
Section titled “Container query mental model”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 arrangementEach later layer changes less than the layer before it. This keeps the fallback complete and the conditional code focused.
Common responsive-component failures
Section titled “Common responsive-component failures”| 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 |
Optional reference
Section titled “Optional reference”- CSS Containment Module Level 3 defines query containers,
container-type, named containers,@container, and inline-size features. - CSS Grid Layout Module defines Grid tracks,
minmax, and repeat-to-fill behavior used in the optional collection route.
Record the component checkpoint
Section titled “Record the component checkpoint”After the required self-check passes, initialize or use the private responsive-components repository and record only the verified files:
git statusgit diffgit add index.html styles.cssgit diff --stagedgit commit -m "Build container-responsive resource card"git pushConfirm that the remote contains the complete stacked base and the single tested container query.
Next step or safe stopping point
Section titled “Next step or safe stopping point”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.