CSS · 02 of 05
Responsive, wherever it lives
A component responds to its own space. The compact layout is the usable base.
- Experience kept
- The same compact and expanded card layouts
- Machinery removed
- Viewport coupling and parent-specific overrides
- Decision
- Let the component respond to its container
Live demoSlide to change the container width.
THE REDUCTION
Same outcome. Less to coordinate.
A viewport breakpoint answers the wrong question for a component that moves between a page and a sidebar.
CURRENT PROJECT · CSS
@media (min-width: 640px) {
.resource-card {
grid-template-columns: 96px 1fr;
}
}
WITH FEWER PARTS · CSS
.card-slot {
container-type: inline-size;
}
@container (min-width: 320px) {
.resource-card {
grid-template-columns: 96px 1fr;
}
}
The single-column card remains the base layout, so the component is still right in a slot that never reaches the threshold.
ABOUT THE PATTERN
Why this works
The card is portable because its layout depends on the space it receives, not the viewport around it.
Decisions that matter
- Use inline-size containment on a wrapper, query descendants, and keep the single-column base usable.
- The slider only demonstrates changing width.
- Style/scroll-state queries need separate support checks.
Read from the skill's own reference files at build time.
COPY, ADAPT, SHIP
The dependency-free implementation
HTML, CSS, and the JavaScript the interaction actually needs
STANDALONE HTML
<!doctype html>
<html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Container-aware card</title>
<style id="recipe">
.card-container { container-type: inline-size; }
.card { display: grid; gap: 1rem; }
@container (min-width: 320px) {
.card { grid-template-columns: 4rem 1fr; align-items: center; }
}
</style>
<style>
*{box-sizing:border-box}body{margin:0;padding:28px;font:14px/1.5 system-ui;color:#182334;background:#f2f5f9}label{display:block;margin-bottom:10px}input{width:100%;accent-color:#3866e8}.card-container{max-width:100%;margin:24px auto 0}.card{background:white;border:1px solid #dce2eb;border-radius:14px;padding:18px;box-shadow:0 10px 25px #1e32500a}.icon{display:grid;place-items:center;width:56px;height:56px;background:#e4edff;border-radius:12px;color:#2f5ccc;font-size:24px}.card h2{font-size:16px;margin:0}.card p{margin:4px 0 0;color:#647186}input:focus-visible{outline:3px solid #3866e8;outline-offset:4px}
</style>
<label for="width">Container width <output id="value">100%</output></label>
<input id="width" type="range" min="55" max="100" value="100">
<div class="card-container"><article class="card"><span class="icon" aria-hidden="true">↗</span><div><h2>Room to adapt</h2><p>Same component.<br>A different amount of space.</p></div></article></div>
<script>
const slider = document.querySelector('#width');
slider.addEventListener('input', () => {
document.querySelector('.card-container').style.width = slider.value + '%';
document.querySelector('#value').value = slider.value + '%';
});
</script></html>
Using Tailwind CSS v4 already? View the adapted example+
Optional integration · adapt utilities to your project tokens
TAILWIND HTML
<label for="width" class="block text-sm font-medium text-slate-900">
Container width <output id="value">100%</output>
</label>
<input
id="width"
type="range"
min="55"
max="100"
value="100"
class="mt-2 w-full accent-blue-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
>
<div data-demo-container class="@container mt-6 max-w-full">
<article class="grid min-w-0 gap-4 rounded-xl border border-slate-200 bg-white p-4 shadow-sm @[320px]:grid-cols-[4rem_minmax(0,1fr)] @[320px]:items-center">
<span aria-hidden="true" class="grid size-14 place-items-center rounded-xl bg-blue-50 text-2xl text-blue-700">↗</span>
<div class="min-w-0">
<h2 class="font-semibold text-slate-900">Room to adapt</h2>
<p class="mt-1 text-sm text-slate-600">Same component. A different amount of space.</p>
</div>
</article>
</div>
<script>
const slider = document.querySelector('#width');
const container = document.querySelector('[data-demo-container]');
const output = document.querySelector('#value');
slider.addEventListener('input', () => {
container.style.width = `${slider.value}%`;
output.value = `${slider.value}%`;
});
</script>