Container Queries 適応カード
container-type と @container で、置かれたコンテナの幅に応じて同じカードが縦積み↔横並びへ切り替わる。スライダーで挙動を体感できる。
ライブデモ
使用例(お題: カフェ MOON BREW)
この技法を「カフェ MOON BREW」というテーマのダミーサイトで実際に使った例です。
HTML
<!-- MOON BREW メニュー:コンテナ幅でメニューカードが縦積み↔横並びに切替 -->
<section class="mb-cq">
<header class="mb-cq__bar">
<h2 class="mb-cq__title">☕ 本日のメニュー</h2>
<label class="mb-cq__ctrl">
陳列幅
<input id="mbWidth" type="range" min="220" max="560" value="540">
</label>
</header>
<div class="mb-cq__stage">
<!-- このラッパが container。幅に応じて中のカードが変形する -->
<div class="mb-cq__container" id="mbContainer">
<article class="mb-cq__card">
<div class="mb-cq__photo" aria-hidden="true"></div>
<div class="mb-cq__body">
<div class="mb-cq__head">
<h3 class="mb-cq__name">琥珀ラテ</h3>
<span class="mb-cq__price">¥580</span>
</div>
<p class="mb-cq__desc">深煎り豆にキャラメルを一垂らし。ふんわりミルクで包んだ看板の一杯。</p>
<span class="mb-cq__tag">人気No.1</span>
</div>
</article>
</div>
</div>
<p class="mb-cq__hint" id="mbHint">幅を縮めると、写真の上に文字が乗る縦型カードに切り替わります。</p>
</section>
CSS
/* MOON BREW:container-type と @container でメニューカードを適応させる */
:root {
--cream: #f5ede1;
--brown: #2b1d12;
--amber: #c98a3b;
}
* { box-sizing: border-box; }
body {
margin: 0;
height: 400px;
display: grid;
place-items: center;
font-family: "Hiragino Kaku Gothic ProN", "Segoe UI", system-ui, sans-serif;
background: var(--cream);
color: var(--brown);
overflow: hidden;
}
.mb-cq {
width: 100%;
max-width: 600px;
padding: 18px;
}
.mb-cq__bar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 16px;
}
.mb-cq__title { margin: 0; font-size: 17px; font-weight: 800; }
.mb-cq__ctrl {
font-size: 11px;
color: #6b5640;
display: flex;
align-items: center;
gap: 8px;
}
.mb-cq__ctrl input { accent-color: var(--amber); cursor: pointer; }
/* ステージ:ここでカードの陳列幅を変える */
.mb-cq__stage {
display: flex;
justify-content: center;
padding: 14px;
border-radius: 14px;
background: #fff;
border: 1px dashed #e0cfb3;
}
/* container 化したラッパ。幅をクエリ対象にする */
.mb-cq__container {
container-type: inline-size;
width: 540px;
max-width: 100%;
}
.mb-cq__card {
display: flex;
gap: 0;
border-radius: 14px;
overflow: hidden;
background: #fffaf2;
border: 1px solid #ecdcc1;
box-shadow: 0 8px 20px rgba(43, 29, 18, 0.12);
}
.mb-cq__photo {
flex: 0 0 44%;
min-height: 150px;
background: url("https://picsum.photos/400/300?random=42") center/cover no-repeat;
filter: sepia(0.2) saturate(1.1);
}
.mb-cq__body { flex: 1; padding: 16px 18px; }
.mb-cq__head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 10px;
}
.mb-cq__name { margin: 0; font-size: 18px; font-weight: 800; }
.mb-cq__price { font-size: 17px; font-weight: 800; color: var(--amber); }
.mb-cq__desc { margin: 8px 0 12px; font-size: 12px; line-height: 1.7; color: #6b5640; }
.mb-cq__tag {
display: inline-block;
font-size: 10px;
font-weight: 700;
padding: 4px 10px;
border-radius: 999px;
background: var(--brown);
color: var(--cream);
letter-spacing: 0.08em;
}
/* コンテナが狭いとき:写真の上に文字を乗せた縦型カードへ */
@container (max-width: 360px) {
.mb-cq__card { flex-direction: column; }
.mb-cq__photo {
flex: none;
min-height: 120px;
}
.mb-cq__body { padding: 14px 16px 16px; }
.mb-cq__name { font-size: 16px; }
}
.mb-cq__hint {
margin: 14px 2px 0;
font-size: 11px;
color: #8a7459;
text-align: center;
line-height: 1.6;
}
JavaScript
// スライダーでコンテナ幅を変え、Container Queries の切替を体感させる
const slider = document.getElementById("mbWidth");
const container = document.getElementById("mbContainer");
const hint = document.getElementById("mbHint");
if (slider && container) {
const apply = () => {
const w = Number(slider.value);
container.style.width = w + "px";
// 360px を境にレイアウトが切り替わるので、状態を案内文に反映
if (hint) {
hint.textContent = w <= 360
? "現在は縦型カード(@container max-width:360px が適用中)。"
: "幅を縮めると、写真の上に文字が乗る縦型カードに切り替わります。";
}
};
slider.addEventListener("input", apply);
apply(); // 初期反映
}
コード
HTML
<!-- Container Queries 適応カード:親コンテナ幅で同じカードが縦↔横レイアウトに切替 -->
<div class="cq">
<div class="cq__bar">
<span class="cq__hint">スライダーでコンテナ幅を変える →</span>
<input id="cqRange" class="cq__range" type="range" min="200" max="640" value="560"
aria-label="コンテナ幅">
<span class="cq__val" id="cqVal">560px</span>
</div>
<!-- このラッパが container。幅に応じて中のカードが変化 -->
<div class="cq__wrap" id="cqWrap">
<article class="ccard">
<div class="ccard__media" aria-hidden="true">
<span class="ccard__emoji">🪴</span>
</div>
<div class="ccard__body">
<span class="ccard__tag">CONTAINER QUERY</span>
<h2 class="ccard__title">幅で姿を変えるカード</h2>
<p class="ccard__text">
コンテナが広いと横並び、狭いと縦積み。メディアクエリと違い
“置かれた場所”の幅で判断するのが強みです。
</p>
<button class="ccard__btn">詳しく見る</button>
</div>
</article>
</div>
</div>
CSS
/* 全体:暖色グラデのステージ */
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: "Hiragino Sans", system-ui, sans-serif;
background: linear-gradient(160deg, #fff7ed, #ffe4d6 60%, #ffd9c2);
color: #3a2418; min-height: 100vh; padding: 16px;
display: flex; flex-direction: column; align-items: center; gap: 14px;
}
/* 操作バー */
.cq { width: min(720px, 100%); display: flex; flex-direction: column; gap: 14px; align-items: center; }
.cq__bar {
display: flex; align-items: center; gap: 10px; flex-wrap: wrap; justify-content: center;
background: #fffaf6; border: 1px solid #f3d6c4; border-radius: 12px; padding: 10px 14px;
box-shadow: 0 10px 24px -18px rgba(120,60,20,.5);
}
.cq__hint { font-size: 12px; color: #9a6a4d; }
.cq__range { accent-color: #f97316; width: 180px; }
.cq__val { font-size: 12px; font-weight: 800; color: #ea580c; min-width: 56px; text-align: right; }
/* container 指定:このラッパがクエリ対象 */
.cq__wrap {
container-type: inline-size;
width: 560px; max-width: 100%;
transition: width .15s ease;
}
/* カード本体(デフォルト=狭い:縦積み) */
.ccard {
display: flex; flex-direction: column; overflow: hidden;
background: #ffffff; border: 1px solid #f3d6c4; border-radius: 18px;
box-shadow: 0 20px 44px -24px rgba(120,60,20,.55);
}
.ccard__media {
display: grid; place-items: center; min-height: 120px;
background: linear-gradient(135deg, #fb923c, #f97316 60%, #ef4444);
}
.ccard__emoji { font-size: 52px; filter: drop-shadow(0 6px 10px rgba(0,0,0,.25)); }
.ccard__body { padding: 18px; display: flex; flex-direction: column; gap: 9px; }
.ccard__tag {
align-self: flex-start; font-size: 10px; letter-spacing: .18em; font-weight: 800;
color: #c2410c; background: #ffedd5; padding: 4px 10px; border-radius: 20px;
}
.ccard__title { font-size: 18px; font-weight: 800; color: #431407; line-height: 1.3; }
.ccard__text { font-size: 13px; line-height: 1.7; color: #7c5440; }
.ccard__btn {
align-self: flex-start; margin-top: 4px;
font: inherit; font-size: 13px; font-weight: 700; cursor: pointer;
color: #fff; border: none; border-radius: 10px; padding: 9px 18px;
background: linear-gradient(90deg, #fb923c, #ef4444);
transition: transform .1s, filter .2s;
}
.ccard__btn:hover { filter: brightness(1.06); }
.ccard__btn:active { transform: scale(.97); }
/* コンテナが 420px 以上なら横並びレイアウトへ */
@container (min-width: 420px) {
.ccard { flex-direction: row; align-items: stretch; }
.ccard__media { min-height: auto; flex: 0 0 42%; }
.ccard__body { flex: 1; justify-content: center; }
.ccard__title { font-size: 21px; }
}
@media (prefers-reduced-motion: reduce) {
.cq__wrap, .ccard__btn { transition: none; }
}
JavaScript
// スライダーでコンテナ幅を変え、Container Query の切替を体感させる
(() => {
const range = document.getElementById('cqRange');
const wrap = document.getElementById('cqWrap');
const val = document.getElementById('cqVal');
if (!range || !wrap || !val) return;
const apply = () => {
const w = range.value;
wrap.style.width = w + 'px';
val.textContent = w + 'px';
};
range.addEventListener('input', apply);
apply(); // 初期反映
})();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「Container Queries 適応カード」の効果を追加してください。
# 追加してほしい効果
Container Queries 適応カード(レイアウト & グリッド)
container-type と @container で、置かれたコンテナの幅に応じて同じカードが縦積み↔横並びへ切り替わる。スライダーで挙動を体感できる。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- Container Queries 適応カード:親コンテナ幅で同じカードが縦↔横レイアウトに切替 -->
<div class="cq">
<div class="cq__bar">
<span class="cq__hint">スライダーでコンテナ幅を変える →</span>
<input id="cqRange" class="cq__range" type="range" min="200" max="640" value="560"
aria-label="コンテナ幅">
<span class="cq__val" id="cqVal">560px</span>
</div>
<!-- このラッパが container。幅に応じて中のカードが変化 -->
<div class="cq__wrap" id="cqWrap">
<article class="ccard">
<div class="ccard__media" aria-hidden="true">
<span class="ccard__emoji">🪴</span>
</div>
<div class="ccard__body">
<span class="ccard__tag">CONTAINER QUERY</span>
<h2 class="ccard__title">幅で姿を変えるカード</h2>
<p class="ccard__text">
コンテナが広いと横並び、狭いと縦積み。メディアクエリと違い
“置かれた場所”の幅で判断するのが強みです。
</p>
<button class="ccard__btn">詳しく見る</button>
</div>
</article>
</div>
</div>
【CSS】
/* 全体:暖色グラデのステージ */
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: "Hiragino Sans", system-ui, sans-serif;
background: linear-gradient(160deg, #fff7ed, #ffe4d6 60%, #ffd9c2);
color: #3a2418; min-height: 100vh; padding: 16px;
display: flex; flex-direction: column; align-items: center; gap: 14px;
}
/* 操作バー */
.cq { width: min(720px, 100%); display: flex; flex-direction: column; gap: 14px; align-items: center; }
.cq__bar {
display: flex; align-items: center; gap: 10px; flex-wrap: wrap; justify-content: center;
background: #fffaf6; border: 1px solid #f3d6c4; border-radius: 12px; padding: 10px 14px;
box-shadow: 0 10px 24px -18px rgba(120,60,20,.5);
}
.cq__hint { font-size: 12px; color: #9a6a4d; }
.cq__range { accent-color: #f97316; width: 180px; }
.cq__val { font-size: 12px; font-weight: 800; color: #ea580c; min-width: 56px; text-align: right; }
/* container 指定:このラッパがクエリ対象 */
.cq__wrap {
container-type: inline-size;
width: 560px; max-width: 100%;
transition: width .15s ease;
}
/* カード本体(デフォルト=狭い:縦積み) */
.ccard {
display: flex; flex-direction: column; overflow: hidden;
background: #ffffff; border: 1px solid #f3d6c4; border-radius: 18px;
box-shadow: 0 20px 44px -24px rgba(120,60,20,.55);
}
.ccard__media {
display: grid; place-items: center; min-height: 120px;
background: linear-gradient(135deg, #fb923c, #f97316 60%, #ef4444);
}
.ccard__emoji { font-size: 52px; filter: drop-shadow(0 6px 10px rgba(0,0,0,.25)); }
.ccard__body { padding: 18px; display: flex; flex-direction: column; gap: 9px; }
.ccard__tag {
align-self: flex-start; font-size: 10px; letter-spacing: .18em; font-weight: 800;
color: #c2410c; background: #ffedd5; padding: 4px 10px; border-radius: 20px;
}
.ccard__title { font-size: 18px; font-weight: 800; color: #431407; line-height: 1.3; }
.ccard__text { font-size: 13px; line-height: 1.7; color: #7c5440; }
.ccard__btn {
align-self: flex-start; margin-top: 4px;
font: inherit; font-size: 13px; font-weight: 700; cursor: pointer;
color: #fff; border: none; border-radius: 10px; padding: 9px 18px;
background: linear-gradient(90deg, #fb923c, #ef4444);
transition: transform .1s, filter .2s;
}
.ccard__btn:hover { filter: brightness(1.06); }
.ccard__btn:active { transform: scale(.97); }
/* コンテナが 420px 以上なら横並びレイアウトへ */
@container (min-width: 420px) {
.ccard { flex-direction: row; align-items: stretch; }
.ccard__media { min-height: auto; flex: 0 0 42%; }
.ccard__body { flex: 1; justify-content: center; }
.ccard__title { font-size: 21px; }
}
@media (prefers-reduced-motion: reduce) {
.cq__wrap, .ccard__btn { transition: none; }
}
【JavaScript】
// スライダーでコンテナ幅を変え、Container Query の切替を体感させる
(() => {
const range = document.getElementById('cqRange');
const wrap = document.getElementById('cqWrap');
const val = document.getElementById('cqVal');
if (!range || !wrap || !val) return;
const apply = () => {
const w = range.value;
wrap.style.width = w + 'px';
val.textContent = w + 'px';
};
range.addEventListener('input', apply);
apply(); // 初期反映
})();
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。