HTML & CSS / Maintainable presentation systems / Lesson 16
Progressive enhancement and container queries
Build a usable baseline, add supported presentation features, and adapt components to their own available space.
On this page
Lesson goals
- Start with a complete baseline
- Enhance a simple list layout
- Make compatibility assumptions reviewable
Start with a complete baseline
Progressive enhancement begins with content and interactions that already satisfy the essential task, then adds capabilities where they improve the experience. A failed stylesheet or unsupported declaration should not erase the only route to important information. This does not require every browser to look identical or support every enhancement. It requires an explicit support policy and a meaningful fallback. Native links, forms, headings, and ordinary flow provide a strong starting point. When JavaScript is needed for an advanced control, define what is available before initialization and what happens if the script never arrives or stops after partial work.
Detect features rather than brands
CSS @supports asks whether a declaration or selector syntax is supported under its defined feature-query rules. It does not prove that the resulting design is accessible, bug-free, or performant on every device. Write a safe base first and place only the enhancement inside the condition. User-agent sniffing often misidentifies browsers and ages poorly compared with capability checks. Even a supported feature can have implementation differences, so maintain a tested browser matrix for critical behavior. Use official compatibility data as planning evidence and verify the actual deployment targets rather than making a permanent claim that every modern browser supports a broad bundle of features.
Enhance a simple list layout
The first example begins with ordinary stacked articles and spacing, then enables a two-column Grid arrangement only when that declaration is supported and enough width is available. Content, headings, and order exist outside the feature query. The fallback is therefore a complete readable page rather than an empty component. Feature checks should not hide required labels or error messages. Avoid duplicating the entire content tree for the enhanced branch; presentation can usually change around one semantic structure. Test the fallback deliberately by disabling the enhancement, since a branch never exercised can drift out of alignment with the product’s actual needs.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Feature enhancement</title>
<style>
.collection article { margin-block: 1rem; padding: 1rem; border: 1px solid; } @supports (display: grid) { @media (min-width: 40rem) { .collection { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; } } }
</style>
</head>
<body>
<main><h1>Enhanced collection</h1><div class="collection"><article><h2>Structure</h2><p>Always readable.</p></article><article><h2>Presentation</h2><p>Columns are optional.</p></article></div></main>
</body>
</html>
A complete stacked fallback with an optional wider grid.
The base consists of ordinary blocks. Supporting browsers may enhance the collection into columns without changing content or semantics.
Query a component’s container
A viewport media query describes the environment, while a size container query can react to an eligible ancestor’s dimensions. Establish an inline-size query container on a wrapper, then style a descendant when the wrapper is wide enough. The queried element does not ordinarily use its own size container query to style itself based on itself; choose the ancestor boundary deliberately. Inline-size containment also affects intrinsic sizing, so applying container-type to an arbitrary shrink-to-fit element can change layout. The second example keeps a stacked baseline and adds two internal columns when its named container reaches the teaching threshold.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Container adaptation</title>
<style>
.wrapper { container-type: inline-size; container-name: card-space; max-inline-size: 50rem; } .card { padding: 1rem; border: 1px solid; } @container card-space (min-width: 30rem) { .card { display: grid; grid-template-columns: 1fr 2fr; gap: 1rem; } }
</style>
</head>
<body>
<main><h1>Container-aware card</h1><div class="wrapper"><article class="card"><h2>Workshop</h2><p>This card responds to its wrapper, not directly to the viewport.</p></article></div></main>
</body>
</html>
The card adapts at a 30rem container width with a stacked fallback.
The wrapper establishes the named size boundary. The descendant card gains columns only when that container is wide enough; otherwise normal block flow remains.
Make compatibility assumptions reviewable
Size container queries, cascade layers, and newer selectors have distinct support histories; do not infer one from another. This course reviews official platform guidance on its stated date and uses a current Chromium fixture for representative checks, while a product must verify its own supported engines and assistive-technology combinations. Keep enhancement failure local: a card can remain stacked without preventing the rest of the page from working. Test a narrow component inside a wide page, and a wide component inside a simple shell. These cases distinguish genuine container adaptation from a viewport rule that accidentally looked correct in one screenshot.
Common errors and fixes
An unsupported feature leaves no content
- Why it happens
- Essential markup or visibility depended on the enhancement.
- How to fix it
- Restore a meaningful baseline outside the condition.
- Prevent it next time
- Test the disabled-enhancement path.
A card responds to the whole page instead of its slot
- Why it happens
- A viewport query was used for a component-local need.
- How to fix it
- Establish and query the intended ancestor container.
- Prevent it next time
- Test narrow slots inside wide pages.
Adding container-type changes intrinsic layout
- Why it happens
- Containment was assumed to be a passive label.
- How to fix it
- Review the wrapper’s sizing contract.
- Prevent it next time
- Measure before and after establishing containment.
Check your understanding
1. Choose a media or container query for a reusable card appearing in both a sidebar and a main column.
Show worked solution
Use a size container query when the card’s internal arrangement should depend on its allocated slot. Establish a deliberate wrapper in each context and keep a stacked fallback. A viewport query cannot distinguish a narrow sidebar from a wide main column at the same window width.
2. Why does @supports (display: grid) not prove that a particular grid interface is usable?
Show worked solution
It checks support for the declaration under feature-query rules. It does not examine content order, contrast, focus, overflow, or the application task. Keep behavioral and accessibility testing separate from syntax support detection, and verify the complete design on supported environments.
Key takeaways
- Enhancements should improve an already usable baseline.
- Feature support is narrower than behavioral correctness.
- A container boundary has sizing consequences.