04 / 20
All 20 in 20 CSS Container Queries
- 01Container Queries Getting Started
- 02Container Query Units cqi cqw cqmin
- 03Named Containers with container-name
- 04Card Component Adapting to Container Width
- 05Sidebar Aware Main Content Reflow
- 06Container Queries vs Media Queries
- 07CSS Style Queries with container style
- 08Scroll State Container Queries
- 09Fluid Typography with Container Units
- 10Dashboard Widget Component Resizing
- 11Responsive Product Card Grid Reflow
- 12Media Object Layout Inline to Stacked
- 13Dynamic Form Layout Adaptation
- 14Navigation Component Collapse by Container
- 15Responsive Table to Card Layout Switch
- 16Progressive Enhancement and Fallback Strategy
- 17Nested Container Contexts
- 18Avoiding Infinite Layout Loops
- 19Combining Container Queries with CSS Grid
- 20Debugging Container Query Sizing Issues
Card Component Adapting to Container Width
One card component in three placements — a wide feature slot, a mid-width grid cell and a narrow rail — with layout, image ratio and type scale all changing from container width alone. The markup and class names are byte-for-byte identical in all three; nothing about the page layout is baked into the component, which is the point that makes the whole idea click.
Build UI like this from scratch: Flexbox, Grid, animations & responsive CSS
ZTM · The CSS Bootcamp · 30‑day money-back guarantee · Affiliate link
The code
<section class="cq-04" aria-labelledby="cq-04-h">
<fieldset class="cq-04__theme">
<legend class="cq-04__themelegend">Theme</legend>
<input class="cq-04__themeinput" type="radio" name="cq-04-theme" id="cq-04-theme-auto" checked>
<label class="cq-04__themeopt" for="cq-04-theme-auto">Auto</label>
<input class="cq-04__themeinput" type="radio" name="cq-04-theme" id="cq-04-theme-light">
<label class="cq-04__themeopt" for="cq-04-theme-light">Light</label>
<input class="cq-04__themeinput" type="radio" name="cq-04-theme" id="cq-04-theme-dark">
<label class="cq-04__themeopt" for="cq-04-theme-dark">Dark</label>
</fieldset>
<div class="cq-04__stage">
<header class="cq-04__head">
<p class="cq-04__kicker">Basalt Analytics · component library</p>
<h2 class="cq-04__h" id="cq-04-h">One card, three placements</h2>
</header>
<div class="cq-04__row">
<div class="cq-04__col cq-04__col--wide">
<p class="cq-04__label">Feature slot</p>
<div class="cq-04__slot">
<article class="cq-04__card">
<div class="cq-04__art" role="img" aria-label="Report cover artwork"></div>
<p class="cq-04__tag">Quarterly report</p>
<h3 class="cq-04__title">Where activation actually breaks</h3>
<p class="cq-04__text">Nine hundred onboarding sessions, replayed and grouped by the first step where intent drops off.</p>
<div class="cq-04__foot">
<span class="cq-04__who"><span class="cq-04__av" aria-hidden="true">AC</span>Alex Chen</span>
<span class="cq-04__read">14 min</span>
</div>
</article>
</div>
</div>
<div class="cq-04__col cq-04__col--mid">
<p class="cq-04__label">Grid cell</p>
<div class="cq-04__slot">
<article class="cq-04__card">
<div class="cq-04__art" role="img" aria-label="Report cover artwork"></div>
<p class="cq-04__tag">Quarterly report</p>
<h3 class="cq-04__title">Where activation actually breaks</h3>
<p class="cq-04__text">Nine hundred onboarding sessions, replayed and grouped by the first step where intent drops off.</p>
<div class="cq-04__foot">
<span class="cq-04__who"><span class="cq-04__av" aria-hidden="true">AC</span>Alex Chen</span>
<span class="cq-04__read">14 min</span>
</div>
</article>
</div>
</div>
<div class="cq-04__col cq-04__col--rail">
<p class="cq-04__label">Narrow rail</p>
<div class="cq-04__slot">
<article class="cq-04__card">
<div class="cq-04__art" role="img" aria-label="Report cover artwork"></div>
<p class="cq-04__tag">Quarterly report</p>
<h3 class="cq-04__title">Where activation actually breaks</h3>
<p class="cq-04__text">Nine hundred onboarding sessions, replayed and grouped by the first step where intent drops off.</p>
<div class="cq-04__foot">
<span class="cq-04__who"><span class="cq-04__av" aria-hidden="true">AC</span>Alex Chen</span>
<span class="cq-04__read">14 min</span>
</div>
</article>
</div>
</div>
</div>
</div>
</section><section class="cq-04" aria-labelledby="cq-04-h">
<fieldset class="cq-04__theme">
<legend class="cq-04__themelegend">Theme</legend>
<input class="cq-04__themeinput" type="radio" name="cq-04-theme" id="cq-04-theme-auto" checked>
<label class="cq-04__themeopt" for="cq-04-theme-auto">Auto</label>
<input class="cq-04__themeinput" type="radio" name="cq-04-theme" id="cq-04-theme-light">
<label class="cq-04__themeopt" for="cq-04-theme-light">Light</label>
<input class="cq-04__themeinput" type="radio" name="cq-04-theme" id="cq-04-theme-dark">
<label class="cq-04__themeopt" for="cq-04-theme-dark">Dark</label>
</fieldset>
<div class="cq-04__stage">
<header class="cq-04__head">
<p class="cq-04__kicker">Basalt Analytics · component library</p>
<h2 class="cq-04__h" id="cq-04-h">One card, three placements</h2>
</header>
<div class="cq-04__row">
<div class="cq-04__col cq-04__col--wide">
<p class="cq-04__label">Feature slot</p>
<div class="cq-04__slot">
<article class="cq-04__card">
<div class="cq-04__art" role="img" aria-label="Report cover artwork"></div>
<p class="cq-04__tag">Quarterly report</p>
<h3 class="cq-04__title">Where activation actually breaks</h3>
<p class="cq-04__text">Nine hundred onboarding sessions, replayed and grouped by the first step where intent drops off.</p>
<div class="cq-04__foot">
<span class="cq-04__who"><span class="cq-04__av" aria-hidden="true">AC</span>Alex Chen</span>
<span class="cq-04__read">14 min</span>
</div>
</article>
</div>
</div>
<div class="cq-04__col cq-04__col--mid">
<p class="cq-04__label">Grid cell</p>
<div class="cq-04__slot">
<article class="cq-04__card">
<div class="cq-04__art" role="img" aria-label="Report cover artwork"></div>
<p class="cq-04__tag">Quarterly report</p>
<h3 class="cq-04__title">Where activation actually breaks</h3>
<p class="cq-04__text">Nine hundred onboarding sessions, replayed and grouped by the first step where intent drops off.</p>
<div class="cq-04__foot">
<span class="cq-04__who"><span class="cq-04__av" aria-hidden="true">AC</span>Alex Chen</span>
<span class="cq-04__read">14 min</span>
</div>
</article>
</div>
</div>
<div class="cq-04__col cq-04__col--rail">
<p class="cq-04__label">Narrow rail</p>
<div class="cq-04__slot">
<article class="cq-04__card">
<div class="cq-04__art" role="img" aria-label="Report cover artwork"></div>
<p class="cq-04__tag">Quarterly report</p>
<h3 class="cq-04__title">Where activation actually breaks</h3>
<p class="cq-04__text">Nine hundred onboarding sessions, replayed and grouped by the first step where intent drops off.</p>
<div class="cq-04__foot">
<span class="cq-04__who"><span class="cq-04__av" aria-hidden="true">AC</span>Alex Chen</span>
<span class="cq-04__read">14 min</span>
</div>
</article>
</div>
</div>
</div>
</div>
</section>.cq-04,
.cq-04[data-theme="light"] {
--bg: #f7f9fc;
--surf: #ffffff;
--line: #dde4ee;
--ink: #0d1b2e;
--mut: #5a6a80;
--acc: #1665d8;
--acc2: #6aa5f5;
--sh: 0 1px 2px rgba(13, 27, 46, 0.06), 0 8px 24px rgba(13, 27, 46, 0.07);
}
.cq-04[data-theme="dark"] {
--bg: #0a1220;
--surf: #111c2e;
--line: #21304a;
--ink: #e9eff8;
--mut: #93a4bd;
--acc: #62a0ff;
--acc2: #9cc4ff;
--sh: 0 1px 2px rgba(0, 0, 0, 0.5), 0 10px 28px rgba(0, 0, 0, 0.4);
}
@media (prefers-color-scheme: dark) {
.cq-04:not([data-theme="light"]) {
--bg: #0a1220;
--surf: #111c2e;
--line: #21304a;
--ink: #e9eff8;
--mut: #93a4bd;
--acc: #62a0ff;
--acc2: #9cc4ff;
--sh: 0 1px 2px rgba(0, 0, 0, 0.5), 0 10px 28px rgba(0, 0, 0, 0.4);
}
}
@supports (color: oklch(0.5 0.1 250)) {
.cq-04,
.cq-04[data-theme="light"] {
--acc: oklch(0.53 0.17 255);
--acc2: oklch(0.72 0.12 255);
}
.cq-04[data-theme="dark"] {
--acc: oklch(0.72 0.14 255);
--acc2: oklch(0.84 0.09 255);
}
@media (prefers-color-scheme: dark) {
.cq-04:not([data-theme="light"]) {
--acc: oklch(0.72 0.14 255);
--acc2: oklch(0.84 0.09 255);
}
}
}
.cq-04 {
width: 100%;
min-height: 100vh;
display: block;
box-sizing: border-box;
background: var(--bg);
color: var(--ink);
font-family: system-ui, -apple-system, "Segoe UI", Helvetica, sans-serif;
line-height: 1.55;
}
.cq-04 *,
.cq-04 *::before,
.cq-04 *::after {
box-sizing: border-box;
}
.cq-04__stage {
max-width: 78rem;
margin-inline: auto;
padding: clamp(1.25rem, 3vw, 2.75rem);
display: grid;
gap: 1.75rem;
}
.cq-04__kicker {
margin: 0 0 0.4rem;
font-size: 0.75rem;
letter-spacing: 0.06em;
color: var(--acc);
font-weight: 600;
}
.cq-04__h {
margin: 0;
font-size: clamp(1.4rem, 1.1rem + 1.6vw, 2rem);
font-weight: 650;
letter-spacing: -0.02em;
}
.cq-04__row {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
align-items: start;
}
.cq-04__col {
min-inline-size: 0;
display: grid;
gap: 0.6rem;
}
.cq-04__col--wide {
flex: 1 1 32rem;
}
.cq-04__col--mid {
flex: 1 1 19rem;
}
.cq-04__col--rail {
flex: 0 1 14.5rem;
}
.cq-04__label {
margin: 0;
font-size: 0.7rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--mut);
font-weight: 600;
}
.cq-04__slot {
container: cq-04-slot / inline-size;
}
.cq-04__card {
display: grid;
grid-template-areas: "art" "tag" "title" "text" "foot";
gap: 0.55rem;
padding: 1.25rem;
background: var(--surf);
border: 1px solid var(--line);
border-radius: 14px;
box-shadow: var(--sh);
}
.cq-04__art {
grid-area: art;
aspect-ratio: 4 / 3;
border-radius: 10px;
background: linear-gradient(140deg, #1665d8, #6aa5f5 60%, #cfe0fb);
background: linear-gradient(140deg, var(--acc), var(--acc2) 60%, color-mix(in srgb, var(--acc2) 35%, white));
}
.cq-04__tag {
grid-area: tag;
margin: 0;
font-size: 0.68rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--acc);
font-weight: 700;
}
.cq-04__title {
grid-area: title;
margin: 0;
font-size: 1.05rem;
font-weight: 650;
line-height: 1.25;
letter-spacing: -0.01em;
text-wrap: balance;
}
.cq-04__text {
grid-area: text;
margin: 0;
font-size: 0.85rem;
color: var(--mut);
}
.cq-04__foot {
grid-area: foot;
display: flex;
flex-wrap: wrap;
gap: 0.5rem 0.75rem;
align-items: center;
justify-content: space-between;
padding-top: 0.6rem;
border-top: 1px solid var(--line);
font-size: 0.78rem;
color: var(--mut);
}
.cq-04__who {
display: flex;
align-items: center;
gap: 0.45rem;
min-inline-size: 0;
}
.cq-04__av {
inline-size: 1.6rem;
block-size: 1.6rem;
border-radius: 50%;
display: grid;
place-items: center;
background: color-mix(in srgb, var(--acc) 18%, transparent);
color: var(--acc);
font-size: 0.62rem;
font-weight: 700;
letter-spacing: 0.02em;
}
.cq-04__read {
font-variant-numeric: tabular-nums;
}
@container cq-04-slot (min-width: 17em) {
.cq-04__card {
grid-template-areas: "art tag" "art title" "art text" "art foot";
grid-template-columns: 7.5rem 1fr;
column-gap: 1rem;
align-content: start;
}
.cq-04__art {
aspect-ratio: auto;
block-size: 100%;
min-block-size: 6rem;
}
}
@container cq-04-slot (min-width: 30em) {
.cq-04__card {
grid-template-areas: "art art" "tag tag" "title title" "text text" "foot foot";
grid-template-columns: 1fr;
padding: 1.75rem;
gap: 0.7rem 1rem;
}
.cq-04__art {
aspect-ratio: 21 / 9;
block-size: auto;
}
.cq-04__title {
font-size: 1.65rem;
}
.cq-04__text {
font-size: 0.95rem;
max-width: 52ch;
}
}
/* Theme switch (added 2026-08-25). Pure CSS: three radios + :has(), zero
JavaScript. Auto inherits both the OS preference and the site's
[data-theme] stamp; Light and Dark pin the palette and beat both. The
pinned values are the demo's own [data-theme] declarations reused
verbatim, so a toggled demo can never drift from how the same demo
renders under the site theme. */
.cq-04 {
position: relative;
}
.cq-04__theme {
position: absolute;
inset-block-start: 0.875rem;
inset-inline-end: 0.875rem;
z-index: 9;
display: flex;
align-items: center;
gap: 0.125rem;
margin: 0;
padding: 0.1875rem;
border: 1px solid color-mix(in srgb, currentColor 18%, transparent);
border-radius: 999px;
background: color-mix(in srgb, currentColor 8%, transparent);
-webkit-backdrop-filter: blur(6px);
backdrop-filter: blur(6px);
font-family: system-ui, sans-serif;
}
.cq-04__themelegend {
position: absolute;
inline-size: 1px;
block-size: 1px;
padding: 0;
overflow: hidden;
clip-path: inset(50%);
}
.cq-04__themeinput {
position: absolute;
inline-size: 1px;
block-size: 1px;
margin: 0;
overflow: hidden;
clip-path: inset(50%);
}
.cq-04__themeopt {
display: inline-flex;
align-items: center;
justify-content: center;
min-block-size: 2.25rem;
min-inline-size: 2.75rem;
padding: 0 0.6875rem;
border-radius: 999px;
font-size: 0.6875rem;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: inherit;
opacity: 0.55;
cursor: pointer;
user-select: none;
white-space: nowrap;
}
.cq-04__themeopt:hover {
opacity: 0.85;
}
.cq-04__themeinput:checked + .cq-04__themeopt {
opacity: 1;
background: color-mix(in srgb, currentColor 15%, transparent);
}
.cq-04__themeinput:focus-visible + .cq-04__themeopt {
outline: 2px solid currentColor;
outline-offset: 2px;
}
.cq-04:has(#cq-04-theme-light:checked) {
--bg: #f7f9fc;
--surf: #ffffff;
--line: #dde4ee;
--ink: #0d1b2e;
--mut: #5a6a80;
--acc: #1665d8;
--acc2: #6aa5f5;
--sh: 0 1px 2px rgba(13, 27, 46, 0.06), 0 8px 24px rgba(13, 27, 46, 0.07);
}
.cq-04:has(#cq-04-theme-dark:checked) {
--bg: #0a1220;
--surf: #111c2e;
--line: #21304a;
--ink: #e9eff8;
--mut: #93a4bd;
--acc: #62a0ff;
--acc2: #9cc4ff;
--sh: 0 1px 2px rgba(0, 0, 0, 0.5), 0 10px 28px rgba(0, 0, 0, 0.4);
}.cq-04,
.cq-04[data-theme="light"] {
--bg: #f7f9fc;
--surf: #ffffff;
--line: #dde4ee;
--ink: #0d1b2e;
--mut: #5a6a80;
--acc: #1665d8;
--acc2: #6aa5f5;
--sh: 0 1px 2px rgba(13, 27, 46, 0.06), 0 8px 24px rgba(13, 27, 46, 0.07);
}
.cq-04[data-theme="dark"] {
--bg: #0a1220;
--surf: #111c2e;
--line: #21304a;
--ink: #e9eff8;
--mut: #93a4bd;
--acc: #62a0ff;
--acc2: #9cc4ff;
--sh: 0 1px 2px rgba(0, 0, 0, 0.5), 0 10px 28px rgba(0, 0, 0, 0.4);
}
@media (prefers-color-scheme: dark) {
.cq-04:not([data-theme="light"]) {
--bg: #0a1220;
--surf: #111c2e;
--line: #21304a;
--ink: #e9eff8;
--mut: #93a4bd;
--acc: #62a0ff;
--acc2: #9cc4ff;
--sh: 0 1px 2px rgba(0, 0, 0, 0.5), 0 10px 28px rgba(0, 0, 0, 0.4);
}
}
@supports (color: oklch(0.5 0.1 250)) {
.cq-04,
.cq-04[data-theme="light"] {
--acc: oklch(0.53 0.17 255);
--acc2: oklch(0.72 0.12 255);
}
.cq-04[data-theme="dark"] {
--acc: oklch(0.72 0.14 255);
--acc2: oklch(0.84 0.09 255);
}
@media (prefers-color-scheme: dark) {
.cq-04:not([data-theme="light"]) {
--acc: oklch(0.72 0.14 255);
--acc2: oklch(0.84 0.09 255);
}
}
}
.cq-04 {
width: 100%;
min-height: 100vh;
display: block;
box-sizing: border-box;
background: var(--bg);
color: var(--ink);
font-family: system-ui, -apple-system, "Segoe UI", Helvetica, sans-serif;
line-height: 1.55;
}
.cq-04 *,
.cq-04 *::before,
.cq-04 *::after {
box-sizing: border-box;
}
.cq-04__stage {
max-width: 78rem;
margin-inline: auto;
padding: clamp(1.25rem, 3vw, 2.75rem);
display: grid;
gap: 1.75rem;
}
.cq-04__kicker {
margin: 0 0 0.4rem;
font-size: 0.75rem;
letter-spacing: 0.06em;
color: var(--acc);
font-weight: 600;
}
.cq-04__h {
margin: 0;
font-size: clamp(1.4rem, 1.1rem + 1.6vw, 2rem);
font-weight: 650;
letter-spacing: -0.02em;
}
.cq-04__row {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
align-items: start;
}
.cq-04__col {
min-inline-size: 0;
display: grid;
gap: 0.6rem;
}
.cq-04__col--wide {
flex: 1 1 32rem;
}
.cq-04__col--mid {
flex: 1 1 19rem;
}
.cq-04__col--rail {
flex: 0 1 14.5rem;
}
.cq-04__label {
margin: 0;
font-size: 0.7rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--mut);
font-weight: 600;
}
.cq-04__slot {
container: cq-04-slot / inline-size;
}
.cq-04__card {
display: grid;
grid-template-areas: "art" "tag" "title" "text" "foot";
gap: 0.55rem;
padding: 1.25rem;
background: var(--surf);
border: 1px solid var(--line);
border-radius: 14px;
box-shadow: var(--sh);
}
.cq-04__art {
grid-area: art;
aspect-ratio: 4 / 3;
border-radius: 10px;
background: linear-gradient(140deg, #1665d8, #6aa5f5 60%, #cfe0fb);
background: linear-gradient(140deg, var(--acc), var(--acc2) 60%, color-mix(in srgb, var(--acc2) 35%, white));
}
.cq-04__tag {
grid-area: tag;
margin: 0;
font-size: 0.68rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--acc);
font-weight: 700;
}
.cq-04__title {
grid-area: title;
margin: 0;
font-size: 1.05rem;
font-weight: 650;
line-height: 1.25;
letter-spacing: -0.01em;
text-wrap: balance;
}
.cq-04__text {
grid-area: text;
margin: 0;
font-size: 0.85rem;
color: var(--mut);
}
.cq-04__foot {
grid-area: foot;
display: flex;
flex-wrap: wrap;
gap: 0.5rem 0.75rem;
align-items: center;
justify-content: space-between;
padding-top: 0.6rem;
border-top: 1px solid var(--line);
font-size: 0.78rem;
color: var(--mut);
}
.cq-04__who {
display: flex;
align-items: center;
gap: 0.45rem;
min-inline-size: 0;
}
.cq-04__av {
inline-size: 1.6rem;
block-size: 1.6rem;
border-radius: 50%;
display: grid;
place-items: center;
background: color-mix(in srgb, var(--acc) 18%, transparent);
color: var(--acc);
font-size: 0.62rem;
font-weight: 700;
letter-spacing: 0.02em;
}
.cq-04__read {
font-variant-numeric: tabular-nums;
}
@container cq-04-slot (min-width: 17em) {
.cq-04__card {
grid-template-areas: "art tag" "art title" "art text" "art foot";
grid-template-columns: 7.5rem 1fr;
column-gap: 1rem;
align-content: start;
}
.cq-04__art {
aspect-ratio: auto;
block-size: 100%;
min-block-size: 6rem;
}
}
@container cq-04-slot (min-width: 30em) {
.cq-04__card {
grid-template-areas: "art art" "tag tag" "title title" "text text" "foot foot";
grid-template-columns: 1fr;
padding: 1.75rem;
gap: 0.7rem 1rem;
}
.cq-04__art {
aspect-ratio: 21 / 9;
block-size: auto;
}
.cq-04__title {
font-size: 1.65rem;
}
.cq-04__text {
font-size: 0.95rem;
max-width: 52ch;
}
}
/* Theme switch (added 2026-08-25). Pure CSS: three radios + :has(), zero
JavaScript. Auto inherits both the OS preference and the site's
[data-theme] stamp; Light and Dark pin the palette and beat both. The
pinned values are the demo's own [data-theme] declarations reused
verbatim, so a toggled demo can never drift from how the same demo
renders under the site theme. */
.cq-04 {
position: relative;
}
.cq-04__theme {
position: absolute;
inset-block-start: 0.875rem;
inset-inline-end: 0.875rem;
z-index: 9;
display: flex;
align-items: center;
gap: 0.125rem;
margin: 0;
padding: 0.1875rem;
border: 1px solid color-mix(in srgb, currentColor 18%, transparent);
border-radius: 999px;
background: color-mix(in srgb, currentColor 8%, transparent);
-webkit-backdrop-filter: blur(6px);
backdrop-filter: blur(6px);
font-family: system-ui, sans-serif;
}
.cq-04__themelegend {
position: absolute;
inline-size: 1px;
block-size: 1px;
padding: 0;
overflow: hidden;
clip-path: inset(50%);
}
.cq-04__themeinput {
position: absolute;
inline-size: 1px;
block-size: 1px;
margin: 0;
overflow: hidden;
clip-path: inset(50%);
}
.cq-04__themeopt {
display: inline-flex;
align-items: center;
justify-content: center;
min-block-size: 2.25rem;
min-inline-size: 2.75rem;
padding: 0 0.6875rem;
border-radius: 999px;
font-size: 0.6875rem;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: inherit;
opacity: 0.55;
cursor: pointer;
user-select: none;
white-space: nowrap;
}
.cq-04__themeopt:hover {
opacity: 0.85;
}
.cq-04__themeinput:checked + .cq-04__themeopt {
opacity: 1;
background: color-mix(in srgb, currentColor 15%, transparent);
}
.cq-04__themeinput:focus-visible + .cq-04__themeopt {
outline: 2px solid currentColor;
outline-offset: 2px;
}
.cq-04:has(#cq-04-theme-light:checked) {
--bg: #f7f9fc;
--surf: #ffffff;
--line: #dde4ee;
--ink: #0d1b2e;
--mut: #5a6a80;
--acc: #1665d8;
--acc2: #6aa5f5;
--sh: 0 1px 2px rgba(13, 27, 46, 0.06), 0 8px 24px rgba(13, 27, 46, 0.07);
}
.cq-04:has(#cq-04-theme-dark:checked) {
--bg: #0a1220;
--surf: #111c2e;
--line: #21304a;
--ink: #e9eff8;
--mut: #93a4bd;
--acc: #62a0ff;
--acc2: #9cc4ff;
--sh: 0 1px 2px rgba(0, 0, 0, 0.5), 0 10px 28px rgba(0, 0, 0, 0.4);
}Here's a working CSS Container Querie from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Card Component Adapting to Container Width
Source: https://codefronts.com/layouts/css-container-queries/card-component-adapting-to-container-width/
One card component in three placements — a wide feature slot, a mid-width grid cell and a narrow rail — with layout, image ratio and type scale all changing from container width alone. The markup and class names are byte-for-byte identical in all three; nothing about the page layout is baked into the component, which is the point that makes the whole idea click.
## HTML
```html
<section class="cq-04" aria-labelledby="cq-04-h">
<fieldset class="cq-04__theme">
<legend class="cq-04__themelegend">Theme</legend>
<input class="cq-04__themeinput" type="radio" name="cq-04-theme" id="cq-04-theme-auto" checked>
<label class="cq-04__themeopt" for="cq-04-theme-auto">Auto</label>
<input class="cq-04__themeinput" type="radio" name="cq-04-theme" id="cq-04-theme-light">
<label class="cq-04__themeopt" for="cq-04-theme-light">Light</label>
<input class="cq-04__themeinput" type="radio" name="cq-04-theme" id="cq-04-theme-dark">
<label class="cq-04__themeopt" for="cq-04-theme-dark">Dark</label>
</fieldset>
<div class="cq-04__stage">
<header class="cq-04__head">
<p class="cq-04__kicker">Basalt Analytics · component library</p>
<h2 class="cq-04__h" id="cq-04-h">One card, three placements</h2>
</header>
<div class="cq-04__row">
<div class="cq-04__col cq-04__col--wide">
<p class="cq-04__label">Feature slot</p>
<div class="cq-04__slot">
<article class="cq-04__card">
<div class="cq-04__art" role="img" aria-label="Report cover artwork"></div>
<p class="cq-04__tag">Quarterly report</p>
<h3 class="cq-04__title">Where activation actually breaks</h3>
<p class="cq-04__text">Nine hundred onboarding sessions, replayed and grouped by the first step where intent drops off.</p>
<div class="cq-04__foot">
<span class="cq-04__who"><span class="cq-04__av" aria-hidden="true">AC</span>Alex Chen</span>
<span class="cq-04__read">14 min</span>
</div>
</article>
</div>
</div>
<div class="cq-04__col cq-04__col--mid">
<p class="cq-04__label">Grid cell</p>
<div class="cq-04__slot">
<article class="cq-04__card">
<div class="cq-04__art" role="img" aria-label="Report cover artwork"></div>
<p class="cq-04__tag">Quarterly report</p>
<h3 class="cq-04__title">Where activation actually breaks</h3>
<p class="cq-04__text">Nine hundred onboarding sessions, replayed and grouped by the first step where intent drops off.</p>
<div class="cq-04__foot">
<span class="cq-04__who"><span class="cq-04__av" aria-hidden="true">AC</span>Alex Chen</span>
<span class="cq-04__read">14 min</span>
</div>
</article>
</div>
</div>
<div class="cq-04__col cq-04__col--rail">
<p class="cq-04__label">Narrow rail</p>
<div class="cq-04__slot">
<article class="cq-04__card">
<div class="cq-04__art" role="img" aria-label="Report cover artwork"></div>
<p class="cq-04__tag">Quarterly report</p>
<h3 class="cq-04__title">Where activation actually breaks</h3>
<p class="cq-04__text">Nine hundred onboarding sessions, replayed and grouped by the first step where intent drops off.</p>
<div class="cq-04__foot">
<span class="cq-04__who"><span class="cq-04__av" aria-hidden="true">AC</span>Alex Chen</span>
<span class="cq-04__read">14 min</span>
</div>
</article>
</div>
</div>
</div>
</div>
</section>
```
## CSS
```css
.cq-04,
.cq-04[data-theme="light"] {
--bg: #f7f9fc;
--surf: #ffffff;
--line: #dde4ee;
--ink: #0d1b2e;
--mut: #5a6a80;
--acc: #1665d8;
--acc2: #6aa5f5;
--sh: 0 1px 2px rgba(13, 27, 46, 0.06), 0 8px 24px rgba(13, 27, 46, 0.07);
}
.cq-04[data-theme="dark"] {
--bg: #0a1220;
--surf: #111c2e;
--line: #21304a;
--ink: #e9eff8;
--mut: #93a4bd;
--acc: #62a0ff;
--acc2: #9cc4ff;
--sh: 0 1px 2px rgba(0, 0, 0, 0.5), 0 10px 28px rgba(0, 0, 0, 0.4);
}
@media (prefers-color-scheme: dark) {
.cq-04:not([data-theme="light"]) {
--bg: #0a1220;
--surf: #111c2e;
--line: #21304a;
--ink: #e9eff8;
--mut: #93a4bd;
--acc: #62a0ff;
--acc2: #9cc4ff;
--sh: 0 1px 2px rgba(0, 0, 0, 0.5), 0 10px 28px rgba(0, 0, 0, 0.4);
}
}
@supports (color: oklch(0.5 0.1 250)) {
.cq-04,
.cq-04[data-theme="light"] {
--acc: oklch(0.53 0.17 255);
--acc2: oklch(0.72 0.12 255);
}
.cq-04[data-theme="dark"] {
--acc: oklch(0.72 0.14 255);
--acc2: oklch(0.84 0.09 255);
}
@media (prefers-color-scheme: dark) {
.cq-04:not([data-theme="light"]) {
--acc: oklch(0.72 0.14 255);
--acc2: oklch(0.84 0.09 255);
}
}
}
.cq-04 {
width: 100%;
min-height: 100vh;
display: block;
box-sizing: border-box;
background: var(--bg);
color: var(--ink);
font-family: system-ui, -apple-system, "Segoe UI", Helvetica, sans-serif;
line-height: 1.55;
}
.cq-04 *,
.cq-04 *::before,
.cq-04 *::after {
box-sizing: border-box;
}
.cq-04__stage {
max-width: 78rem;
margin-inline: auto;
padding: clamp(1.25rem, 3vw, 2.75rem);
display: grid;
gap: 1.75rem;
}
.cq-04__kicker {
margin: 0 0 0.4rem;
font-size: 0.75rem;
letter-spacing: 0.06em;
color: var(--acc);
font-weight: 600;
}
.cq-04__h {
margin: 0;
font-size: clamp(1.4rem, 1.1rem + 1.6vw, 2rem);
font-weight: 650;
letter-spacing: -0.02em;
}
.cq-04__row {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
align-items: start;
}
.cq-04__col {
min-inline-size: 0;
display: grid;
gap: 0.6rem;
}
.cq-04__col--wide {
flex: 1 1 32rem;
}
.cq-04__col--mid {
flex: 1 1 19rem;
}
.cq-04__col--rail {
flex: 0 1 14.5rem;
}
.cq-04__label {
margin: 0;
font-size: 0.7rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--mut);
font-weight: 600;
}
.cq-04__slot {
container: cq-04-slot / inline-size;
}
.cq-04__card {
display: grid;
grid-template-areas: "art" "tag" "title" "text" "foot";
gap: 0.55rem;
padding: 1.25rem;
background: var(--surf);
border: 1px solid var(--line);
border-radius: 14px;
box-shadow: var(--sh);
}
.cq-04__art {
grid-area: art;
aspect-ratio: 4 / 3;
border-radius: 10px;
background: linear-gradient(140deg, #1665d8, #6aa5f5 60%, #cfe0fb);
background: linear-gradient(140deg, var(--acc), var(--acc2) 60%, color-mix(in srgb, var(--acc2) 35%, white));
}
.cq-04__tag {
grid-area: tag;
margin: 0;
font-size: 0.68rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--acc);
font-weight: 700;
}
.cq-04__title {
grid-area: title;
margin: 0;
font-size: 1.05rem;
font-weight: 650;
line-height: 1.25;
letter-spacing: -0.01em;
text-wrap: balance;
}
.cq-04__text {
grid-area: text;
margin: 0;
font-size: 0.85rem;
color: var(--mut);
}
.cq-04__foot {
grid-area: foot;
display: flex;
flex-wrap: wrap;
gap: 0.5rem 0.75rem;
align-items: center;
justify-content: space-between;
padding-top: 0.6rem;
border-top: 1px solid var(--line);
font-size: 0.78rem;
color: var(--mut);
}
.cq-04__who {
display: flex;
align-items: center;
gap: 0.45rem;
min-inline-size: 0;
}
.cq-04__av {
inline-size: 1.6rem;
block-size: 1.6rem;
border-radius: 50%;
display: grid;
place-items: center;
background: color-mix(in srgb, var(--acc) 18%, transparent);
color: var(--acc);
font-size: 0.62rem;
font-weight: 700;
letter-spacing: 0.02em;
}
.cq-04__read {
font-variant-numeric: tabular-nums;
}
@container cq-04-slot (min-width: 17em) {
.cq-04__card {
grid-template-areas: "art tag" "art title" "art text" "art foot";
grid-template-columns: 7.5rem 1fr;
column-gap: 1rem;
align-content: start;
}
.cq-04__art {
aspect-ratio: auto;
block-size: 100%;
min-block-size: 6rem;
}
}
@container cq-04-slot (min-width: 30em) {
.cq-04__card {
grid-template-areas: "art art" "tag tag" "title title" "text text" "foot foot";
grid-template-columns: 1fr;
padding: 1.75rem;
gap: 0.7rem 1rem;
}
.cq-04__art {
aspect-ratio: 21 / 9;
block-size: auto;
}
.cq-04__title {
font-size: 1.65rem;
}
.cq-04__text {
font-size: 0.95rem;
max-width: 52ch;
}
}
/* Theme switch (added 2026-08-25). Pure CSS: three radios + :has(), zero
JavaScript. Auto inherits both the OS preference and the site's
[data-theme] stamp; Light and Dark pin the palette and beat both. The
pinned values are the demo's own [data-theme] declarations reused
verbatim, so a toggled demo can never drift from how the same demo
renders under the site theme. */
.cq-04 {
position: relative;
}
.cq-04__theme {
position: absolute;
inset-block-start: 0.875rem;
inset-inline-end: 0.875rem;
z-index: 9;
display: flex;
align-items: center;
gap: 0.125rem;
margin: 0;
padding: 0.1875rem;
border: 1px solid color-mix(in srgb, currentColor 18%, transparent);
border-radius: 999px;
background: color-mix(in srgb, currentColor 8%, transparent);
-webkit-backdrop-filter: blur(6px);
backdrop-filter: blur(6px);
font-family: system-ui, sans-serif;
}
.cq-04__themelegend {
position: absolute;
inline-size: 1px;
block-size: 1px;
padding: 0;
overflow: hidden;
clip-path: inset(50%);
}
.cq-04__themeinput {
position: absolute;
inline-size: 1px;
block-size: 1px;
margin: 0;
overflow: hidden;
clip-path: inset(50%);
}
.cq-04__themeopt {
display: inline-flex;
align-items: center;
justify-content: center;
min-block-size: 2.25rem;
min-inline-size: 2.75rem;
padding: 0 0.6875rem;
border-radius: 999px;
font-size: 0.6875rem;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: inherit;
opacity: 0.55;
cursor: pointer;
user-select: none;
white-space: nowrap;
}
.cq-04__themeopt:hover {
opacity: 0.85;
}
.cq-04__themeinput:checked + .cq-04__themeopt {
opacity: 1;
background: color-mix(in srgb, currentColor 15%, transparent);
}
.cq-04__themeinput:focus-visible + .cq-04__themeopt {
outline: 2px solid currentColor;
outline-offset: 2px;
}
.cq-04:has(#cq-04-theme-light:checked) {
--bg: #f7f9fc;
--surf: #ffffff;
--line: #dde4ee;
--ink: #0d1b2e;
--mut: #5a6a80;
--acc: #1665d8;
--acc2: #6aa5f5;
--sh: 0 1px 2px rgba(13, 27, 46, 0.06), 0 8px 24px rgba(13, 27, 46, 0.07);
}
.cq-04:has(#cq-04-theme-dark:checked) {
--bg: #0a1220;
--surf: #111c2e;
--line: #21304a;
--ink: #e9eff8;
--mut: #93a4bd;
--acc: #62a0ff;
--acc2: #9cc4ff;
--sh: 0 1px 2px rgba(0, 0, 0, 0.5), 0 10px 28px rgba(0, 0, 0, 0.4);
}
```Here's a working CSS Container Querie from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Card Component Adapting to Container Width
Source: https://codefronts.com/layouts/css-container-queries/card-component-adapting-to-container-width/
One card component in three placements — a wide feature slot, a mid-width grid cell and a narrow rail — with layout, image ratio and type scale all changing from container width alone. The markup and class names are byte-for-byte identical in all three; nothing about the page layout is baked into the component, which is the point that makes the whole idea click.
## HTML
```html
<section class="cq-04" aria-labelledby="cq-04-h">
<fieldset class="cq-04__theme">
<legend class="cq-04__themelegend">Theme</legend>
<input class="cq-04__themeinput" type="radio" name="cq-04-theme" id="cq-04-theme-auto" checked>
<label class="cq-04__themeopt" for="cq-04-theme-auto">Auto</label>
<input class="cq-04__themeinput" type="radio" name="cq-04-theme" id="cq-04-theme-light">
<label class="cq-04__themeopt" for="cq-04-theme-light">Light</label>
<input class="cq-04__themeinput" type="radio" name="cq-04-theme" id="cq-04-theme-dark">
<label class="cq-04__themeopt" for="cq-04-theme-dark">Dark</label>
</fieldset>
<div class="cq-04__stage">
<header class="cq-04__head">
<p class="cq-04__kicker">Basalt Analytics · component library</p>
<h2 class="cq-04__h" id="cq-04-h">One card, three placements</h2>
</header>
<div class="cq-04__row">
<div class="cq-04__col cq-04__col--wide">
<p class="cq-04__label">Feature slot</p>
<div class="cq-04__slot">
<article class="cq-04__card">
<div class="cq-04__art" role="img" aria-label="Report cover artwork"></div>
<p class="cq-04__tag">Quarterly report</p>
<h3 class="cq-04__title">Where activation actually breaks</h3>
<p class="cq-04__text">Nine hundred onboarding sessions, replayed and grouped by the first step where intent drops off.</p>
<div class="cq-04__foot">
<span class="cq-04__who"><span class="cq-04__av" aria-hidden="true">AC</span>Alex Chen</span>
<span class="cq-04__read">14 min</span>
</div>
</article>
</div>
</div>
<div class="cq-04__col cq-04__col--mid">
<p class="cq-04__label">Grid cell</p>
<div class="cq-04__slot">
<article class="cq-04__card">
<div class="cq-04__art" role="img" aria-label="Report cover artwork"></div>
<p class="cq-04__tag">Quarterly report</p>
<h3 class="cq-04__title">Where activation actually breaks</h3>
<p class="cq-04__text">Nine hundred onboarding sessions, replayed and grouped by the first step where intent drops off.</p>
<div class="cq-04__foot">
<span class="cq-04__who"><span class="cq-04__av" aria-hidden="true">AC</span>Alex Chen</span>
<span class="cq-04__read">14 min</span>
</div>
</article>
</div>
</div>
<div class="cq-04__col cq-04__col--rail">
<p class="cq-04__label">Narrow rail</p>
<div class="cq-04__slot">
<article class="cq-04__card">
<div class="cq-04__art" role="img" aria-label="Report cover artwork"></div>
<p class="cq-04__tag">Quarterly report</p>
<h3 class="cq-04__title">Where activation actually breaks</h3>
<p class="cq-04__text">Nine hundred onboarding sessions, replayed and grouped by the first step where intent drops off.</p>
<div class="cq-04__foot">
<span class="cq-04__who"><span class="cq-04__av" aria-hidden="true">AC</span>Alex Chen</span>
<span class="cq-04__read">14 min</span>
</div>
</article>
</div>
</div>
</div>
</div>
</section>
```
## CSS
```css
.cq-04,
.cq-04[data-theme="light"] {
--bg: #f7f9fc;
--surf: #ffffff;
--line: #dde4ee;
--ink: #0d1b2e;
--mut: #5a6a80;
--acc: #1665d8;
--acc2: #6aa5f5;
--sh: 0 1px 2px rgba(13, 27, 46, 0.06), 0 8px 24px rgba(13, 27, 46, 0.07);
}
.cq-04[data-theme="dark"] {
--bg: #0a1220;
--surf: #111c2e;
--line: #21304a;
--ink: #e9eff8;
--mut: #93a4bd;
--acc: #62a0ff;
--acc2: #9cc4ff;
--sh: 0 1px 2px rgba(0, 0, 0, 0.5), 0 10px 28px rgba(0, 0, 0, 0.4);
}
@media (prefers-color-scheme: dark) {
.cq-04:not([data-theme="light"]) {
--bg: #0a1220;
--surf: #111c2e;
--line: #21304a;
--ink: #e9eff8;
--mut: #93a4bd;
--acc: #62a0ff;
--acc2: #9cc4ff;
--sh: 0 1px 2px rgba(0, 0, 0, 0.5), 0 10px 28px rgba(0, 0, 0, 0.4);
}
}
@supports (color: oklch(0.5 0.1 250)) {
.cq-04,
.cq-04[data-theme="light"] {
--acc: oklch(0.53 0.17 255);
--acc2: oklch(0.72 0.12 255);
}
.cq-04[data-theme="dark"] {
--acc: oklch(0.72 0.14 255);
--acc2: oklch(0.84 0.09 255);
}
@media (prefers-color-scheme: dark) {
.cq-04:not([data-theme="light"]) {
--acc: oklch(0.72 0.14 255);
--acc2: oklch(0.84 0.09 255);
}
}
}
.cq-04 {
width: 100%;
min-height: 100vh;
display: block;
box-sizing: border-box;
background: var(--bg);
color: var(--ink);
font-family: system-ui, -apple-system, "Segoe UI", Helvetica, sans-serif;
line-height: 1.55;
}
.cq-04 *,
.cq-04 *::before,
.cq-04 *::after {
box-sizing: border-box;
}
.cq-04__stage {
max-width: 78rem;
margin-inline: auto;
padding: clamp(1.25rem, 3vw, 2.75rem);
display: grid;
gap: 1.75rem;
}
.cq-04__kicker {
margin: 0 0 0.4rem;
font-size: 0.75rem;
letter-spacing: 0.06em;
color: var(--acc);
font-weight: 600;
}
.cq-04__h {
margin: 0;
font-size: clamp(1.4rem, 1.1rem + 1.6vw, 2rem);
font-weight: 650;
letter-spacing: -0.02em;
}
.cq-04__row {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
align-items: start;
}
.cq-04__col {
min-inline-size: 0;
display: grid;
gap: 0.6rem;
}
.cq-04__col--wide {
flex: 1 1 32rem;
}
.cq-04__col--mid {
flex: 1 1 19rem;
}
.cq-04__col--rail {
flex: 0 1 14.5rem;
}
.cq-04__label {
margin: 0;
font-size: 0.7rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--mut);
font-weight: 600;
}
.cq-04__slot {
container: cq-04-slot / inline-size;
}
.cq-04__card {
display: grid;
grid-template-areas: "art" "tag" "title" "text" "foot";
gap: 0.55rem;
padding: 1.25rem;
background: var(--surf);
border: 1px solid var(--line);
border-radius: 14px;
box-shadow: var(--sh);
}
.cq-04__art {
grid-area: art;
aspect-ratio: 4 / 3;
border-radius: 10px;
background: linear-gradient(140deg, #1665d8, #6aa5f5 60%, #cfe0fb);
background: linear-gradient(140deg, var(--acc), var(--acc2) 60%, color-mix(in srgb, var(--acc2) 35%, white));
}
.cq-04__tag {
grid-area: tag;
margin: 0;
font-size: 0.68rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--acc);
font-weight: 700;
}
.cq-04__title {
grid-area: title;
margin: 0;
font-size: 1.05rem;
font-weight: 650;
line-height: 1.25;
letter-spacing: -0.01em;
text-wrap: balance;
}
.cq-04__text {
grid-area: text;
margin: 0;
font-size: 0.85rem;
color: var(--mut);
}
.cq-04__foot {
grid-area: foot;
display: flex;
flex-wrap: wrap;
gap: 0.5rem 0.75rem;
align-items: center;
justify-content: space-between;
padding-top: 0.6rem;
border-top: 1px solid var(--line);
font-size: 0.78rem;
color: var(--mut);
}
.cq-04__who {
display: flex;
align-items: center;
gap: 0.45rem;
min-inline-size: 0;
}
.cq-04__av {
inline-size: 1.6rem;
block-size: 1.6rem;
border-radius: 50%;
display: grid;
place-items: center;
background: color-mix(in srgb, var(--acc) 18%, transparent);
color: var(--acc);
font-size: 0.62rem;
font-weight: 700;
letter-spacing: 0.02em;
}
.cq-04__read {
font-variant-numeric: tabular-nums;
}
@container cq-04-slot (min-width: 17em) {
.cq-04__card {
grid-template-areas: "art tag" "art title" "art text" "art foot";
grid-template-columns: 7.5rem 1fr;
column-gap: 1rem;
align-content: start;
}
.cq-04__art {
aspect-ratio: auto;
block-size: 100%;
min-block-size: 6rem;
}
}
@container cq-04-slot (min-width: 30em) {
.cq-04__card {
grid-template-areas: "art art" "tag tag" "title title" "text text" "foot foot";
grid-template-columns: 1fr;
padding: 1.75rem;
gap: 0.7rem 1rem;
}
.cq-04__art {
aspect-ratio: 21 / 9;
block-size: auto;
}
.cq-04__title {
font-size: 1.65rem;
}
.cq-04__text {
font-size: 0.95rem;
max-width: 52ch;
}
}
/* Theme switch (added 2026-08-25). Pure CSS: three radios + :has(), zero
JavaScript. Auto inherits both the OS preference and the site's
[data-theme] stamp; Light and Dark pin the palette and beat both. The
pinned values are the demo's own [data-theme] declarations reused
verbatim, so a toggled demo can never drift from how the same demo
renders under the site theme. */
.cq-04 {
position: relative;
}
.cq-04__theme {
position: absolute;
inset-block-start: 0.875rem;
inset-inline-end: 0.875rem;
z-index: 9;
display: flex;
align-items: center;
gap: 0.125rem;
margin: 0;
padding: 0.1875rem;
border: 1px solid color-mix(in srgb, currentColor 18%, transparent);
border-radius: 999px;
background: color-mix(in srgb, currentColor 8%, transparent);
-webkit-backdrop-filter: blur(6px);
backdrop-filter: blur(6px);
font-family: system-ui, sans-serif;
}
.cq-04__themelegend {
position: absolute;
inline-size: 1px;
block-size: 1px;
padding: 0;
overflow: hidden;
clip-path: inset(50%);
}
.cq-04__themeinput {
position: absolute;
inline-size: 1px;
block-size: 1px;
margin: 0;
overflow: hidden;
clip-path: inset(50%);
}
.cq-04__themeopt {
display: inline-flex;
align-items: center;
justify-content: center;
min-block-size: 2.25rem;
min-inline-size: 2.75rem;
padding: 0 0.6875rem;
border-radius: 999px;
font-size: 0.6875rem;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: inherit;
opacity: 0.55;
cursor: pointer;
user-select: none;
white-space: nowrap;
}
.cq-04__themeopt:hover {
opacity: 0.85;
}
.cq-04__themeinput:checked + .cq-04__themeopt {
opacity: 1;
background: color-mix(in srgb, currentColor 15%, transparent);
}
.cq-04__themeinput:focus-visible + .cq-04__themeopt {
outline: 2px solid currentColor;
outline-offset: 2px;
}
.cq-04:has(#cq-04-theme-light:checked) {
--bg: #f7f9fc;
--surf: #ffffff;
--line: #dde4ee;
--ink: #0d1b2e;
--mut: #5a6a80;
--acc: #1665d8;
--acc2: #6aa5f5;
--sh: 0 1px 2px rgba(13, 27, 46, 0.06), 0 8px 24px rgba(13, 27, 46, 0.07);
}
.cq-04:has(#cq-04-theme-dark:checked) {
--bg: #0a1220;
--surf: #111c2e;
--line: #21304a;
--ink: #e9eff8;
--mut: #93a4bd;
--acc: #62a0ff;
--acc2: #9cc4ff;
--sh: 0 1px 2px rgba(0, 0, 0, 0.5), 0 10px 28px rgba(0, 0, 0, 0.4);
}
```How this works
The component knows its container, not its page. Each of the three slots below sets container-type: inline-size and shares one name, so a single set of rules serves every placement:
.cq-04__slot { container: cq-04-slot / inline-size; }
@container cq-04-slot (min-width: 17em) { /* horizontal */ }
@container cq-04-slot (min-width: 30em) { /* feature */ }Stages, not breakpoints. Because both blocks are written min-width and appear in ascending source order, the wide stage layers on top of the horizontal one — the same cascade you already use for media queries, with none of the viewport coupling. Three placements, two rules, zero modifier classes.
Reflow the grid, do not reorder the DOM. The card is a grid with named areas; each stage reassigns grid-template-areas so the artwork moves from top to side without touching source order, which keeps tab and reading order stable for keyboard and screen reader users.
The trap: the container must be the slot, not the card. If container-type goes on the card itself, the query has no effect on that card — a query never styles its own container — and if it goes on the wrapper that holds all three slots, every card measures the same width and gives the same answer. One container per placement is the rule, and demo 11 shows the grid-item version of the same mistake.
Make it yours
- Add a fourth stage at
min-width: 42emfor a hero variant with the text overlaying the artwork; no markup changes are needed. - Change the aspect ratios per stage (
4 / 3,1 / 1,21 / 9) to control how much the artwork dominates each placement. - Move the byline into the footer row only in the widest stage by toggling its
grid-areainside the query. - Retune the two thresholds together — they are the only numbers that decide which placement gets which layout.
- Change the slot widths in the demo frame instead of the queries to prove the component is reading its container and nothing else.
- Recolour from
--accand--surf; the artwork gradients are built from the same two tokens.
Gotchas — read before shipping
- Putting
container-typeon the card means the card can never restyle itself — a container query styles descendants only. The slot around it is the container. - One container around all three slots gives all three cards the same measurement, which defeats the purpose. Each placement needs its own container.
container-type: inline-sizemakes the slot a containing block for absolutely positioned children, so a badge withposition: absolute; inset: 0now stops at the slot rather than escaping it.- Sizing the title in
cqialone makes it ignore user font-size settings; step it inremper stage, or clamp with aremfloor.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 114+ | 17.5+ | 121+ | 114+ |
Floor set by :has(), oklch(), color-mix(), backdrop-filter, @container, text-wrap as this demo is written. The core technique itself goes back further — Chrome 111+.
The whole demo rests on container-type: inline-size and @container, Baseline since February 2023 (Chrome 105 / Safari 16 / Firefox 110) — this is production-safe today. The quoted Chrome and Firefox floors come from oklch() in the accent ramp, which is gated by @supports with an sRGB hex first. Without container query support all three cards render the stacked vertical layout declared before the queries: the same component, one layout, still correct.