Week 07 / Phase 2: CSS(第3週) バイブコーディングの土台 — フロントエンドレッスン

FlexboxとCSS Gridを読む
AIが書いたレイアウトCSSの見方

Week 06 の最後で、カードを3つdisplay: inline-blockで横並びにしました。今の現場でこれを書く人はほとんどいません。AIに「カードを横並びにして」と頼めば、返ってくるのは display: flexdisplay: grid です。今週のゴールは、その出力を自分で書けるようになることではなく、読んで、崩れている箇所を1つ直せるようになることです。

読了時間: 約25分 公開日: 2026年7月30日 前提知識: Week 06 完了

今週のゴール

AIが出したレイアウトCSSを見て、どれが並べる親でどれが並ぶ子かを指させる。justify-contentalign-itemsがどちらの向きに効くかを読み分ける。flex: 1が何の省略形かを説明できる。grid-template-columnsの値を読んで列数を言い当てる。そのレイアウトに Flex と Grid のどちらが妥当かを判断できる。

AIの出力で最初に見る3行

「カードを3枚横並びにして」とAIに頼むと、返ってくるCSSはだいたい20〜40行あります。全部を上から読む必要はありません。レイアウトの構造は、たった3行を順番に見れば掴めます。

レイアウトCSSを読む3ステップ

① 親の display は何か(flex? grid? それ以外?)
② 主軸はどっち向きか(flex-directionが書かれていなければ横 = row)
③ 子に伸縮の指定があるか(flex: 1width が付いているか)

実際のAI出力で試す

これは「カードを3枚横並びに」と頼んだときにAIが返してくる、ごく典型的なコードです。まずHTML。

<div class="card-list">
    <div class="card">
        <h3>プランA</h3>
        <p>個人向けの入門プランです。</p>
    </div>
    <div class="card">
        <h3>プランB</h3>
        <p>チームでの利用に。</p>
    </div>
    <div class="card">
        <h3>プランC</h3>
        <p>大規模導入向け。</p>
    </div>
</div>

続いてCSS。ここに3ステップを当てていきます。

.card-list {
    display: flex;          /* ← ① 親の display */
    gap: 24px;
}

.card {
    flex: 1;                /* ← ③ 子の伸縮指定 */
    padding: 24px;
    border: 1px solid #e2e8f0;
    border-radius: 12px;
    background: #fff;
}

.card h3 {
    margin-top: 0;
    margin-bottom: 8px;
    font-size: 1.125rem;
}

.card p {
    margin: 0;
    color: #475569;
}

① 親の display: .card-listdisplay: flex があります。この時点で「.card-list が並べる親、その直接の子である .card が並ぶ子」と確定します。ここが一番大事で、並べる指定は必ず親側に書かれます。子の .card をいくら読んでも「なぜ横に並ぶのか」は書いてありません。

② 主軸の向き: flex-direction がどこにも書かれていません。書かれていない場合の初期値は row、つまり横方向です。だから3枚が横に並びます。この「書いていない = 横」を知らないと、「横並びにする指定はどこ?」と探し続けることになります。

③ 子の伸縮指定: .cardflex: 1 があります。詳しくは後のセクションで扱いますが、ここでは「3枚が等しい幅に伸びる」と読めれば十分です。もし flex: 1 が無ければ、カードは中身の文字数分の幅になり、3枚がバラバラの幅で左に寄ります。

3行を見終わった時点で、このCSSは「card-list が横方向に、3枚のカードを等幅で並べている。間隔は24px」と要約できます。残りの paddingborder-radius は見た目の装飾で、レイアウトの構造には関係ありません。装飾とレイアウトを切り分けて読むのが、AI出力を早く読むコツです。

display が flex でも grid でもないとき

①で display が見つからない、あるいは block のままなのに横に並んでいる場合、Week 06 で扱った inline-blockfloat が使われている可能性があります。AIに「モダンな書き方で」と伝えていないと、古い書き方が混ざることがあります。

Week 06 の DevTools でボックスモデルを読むで、デバッグ手順の4番目に「親要素の display(flex / grid なのか)も確認」を挙げました。今週はその1行を、確認したあとに何を読み取るかまで広げる回だと思ってください。

Flexbox — 主軸と交差軸だけ押さえる

Flexbox には十数個のプロパティがありますが、AI出力を読むために最初に必要なのは「2本の軸」の概念だけです。ここさえ押さえれば、残りのプロパティは必要になったときに調べれば足ります。

2本の軸とは

そして、この2本にはそれぞれ担当プロパティが1つずつ割り当てられています。

.container {
    display: flex;

    justify-content: center;   /* 主軸方向の配置 */
    align-items: center;       /* 交差軸方向の配置 */
}

justify-content は主軸、align-items は交差軸。この対応を覚えるのが今週の山場です。名前からは向きが読み取れないので、ここは丸暗記して構いません。

flex-direction を変えると、2つの意味が入れ替わる

ここが最大のハマりどころです。プロパティ名は同じままで、効く向きだけがひっくり返ります。

/* ケースA: 初期値(横並び) */
.row {
    display: flex;
    /* flex-direction: row; ← 書かなくてもこれ */
    justify-content: center;   /* 横方向に中央 */
    align-items: center;       /* 縦方向に中央 */
}

/* ケースB: 縦並びに変えた */
.column {
    display: flex;
    flex-direction: column;
    justify-content: center;   /* 縦方向に中央(!) */
    align-items: center;       /* 横方向に中央(!) */
}

ケースAとケースBで、書いてあるプロパティは同じなのに意味が逆になっています。flex-direction: column を足しただけで、justify-content の担当が横から縦へ、align-items の担当が縦から横へ移ります。

justify-content: center にしたのに横中央にならない」という相談の9割は、その親のどこかに flex-direction: column が書かれています。効かないと思ったら、まず flex-direction を探す。これが読み方の要点です。

Week 06 で書いた3行と同じもの

Week 06 の中央寄せの全パターンで、パターン4として次の3行を使いました。

.parent {
    display: flex;
    justify-content: center;
    align-items: center;
}

あのときは「中央寄せのレシピ」として丸ごと覚えてもらいました。あれと今週の flex は同じものです。子が1つだけなら中央寄せに見え、子が複数あれば横並びに見える。道具は1つで、使われ方が違うだけでした。今週で、レシピの中身が読めるようになります。

align-items を書かなかったときの計算値は normal

交差軸の指定を省略すると、子は交差軸いっぱいに伸びます。ただしDevTools の計算値は stretch ではなく normal と表示されます(Flex アイテムに対しては normal が stretch として振る舞う、という仕様のため)。見た目が同じでも表示が違うのは、むしろ都合が良い点です。計算値が normal ならそのCSSには指定が無いstretch なら誰かが明示的に書いたと見分けられます。後の「AIが間違えやすい点」で、この見分けをそのまま使います。

覚え方: justify-content = 並んでいる方向(主軸)の配置。align-items = それと直角の方向(交差軸)の配置。flex-direction: column が出てきたら、この2つの意味が入れ替わっていると考える。

flex: 1 の正体

AIが等幅のカラムやカードを作るとき、ほぼ確実に登場するのが flex: 1 です。短いので装飾のように見えますが、これは3つのプロパティをまとめた省略形で、レイアウトの挙動を決定づけています。

/* この1行は…… */
.card {
    flex: 1;
}

/* …… この3行と同じ意味 */
.card {
    flex-grow: 1;      /* 余った幅があれば、受け取って伸びる */
    flex-shrink: 1;    /* 幅が足りなければ、縮む */
    flex-basis: 0%;    /* 伸び縮みを計算する前の「基準の幅」をゼロにする */
}

3つのうち結果を左右しているのは flex-basis: 0% です。ここが理解の分かれ目なので、順を追います。

なぜ「文字数が違っても等幅」になるのか

flex-basis は「伸縮を始める前の基準サイズ」です。初期値は auto、つまり中身の分だけの幅flex: 1 はこれを 0% に上書きします。

/* 基準幅を0にしてから、余った幅を1:1:1で分ける */
.card { flex: 1; }

/* → 3枚とも基準が0なので、gap を除いた残り幅を3等分した幅になる。
      中身が「A」でも200文字でも、幅は同じ */

全員の基準幅が 0 なら、残りは「余った幅をどう分けるか」だけの話になります。flex-grow: 1 が3枚に付いているので、余りは1:1:1で分配され、結果として3枚は完全に同じ幅になります。文字数は一切影響しません。

flex: 1 が無いとどうなるか

/* 子に flex の指定なし = flex-basis は auto のまま */
.card-list { display: flex; gap: 24px; }
.card      { /* flex 指定なし */ }

/* → 各カードは中身の文字数分の幅になる。
      3枚の幅はバラバラ、余った幅は右側に空白として残る */

「AIが作ったカードの幅がバラバラで揃わない」という症状は、たいていこれです。子に flex: 1 が付いているかどうかを見れば、等幅になる設計かどうかが読み分けられます。

読むときのチェックポイント

なお、解説記事によっては flex: 1 の展開を flex: 1 1 0 と書くことがあります。00% はここでは同じ意味なので、どちらの表記を見ても同じものだと思って構いません。

覚え方: flex: 1 は「基準幅をゼロにしてから、余りを山分けする」宣言。基準がゼロだから中身の量に左右されず、きれいに等分される。

CSS Grid — grid-template-columns を読む

Grid にもプロパティは大量にありますが、AI出力を読むうえで圧倒的に登場頻度が高いのは grid-template-columns 1つです。しかも実務で見る値はほぼ2パターンしかありません。この2つを読めれば、当面は困りません。

パターン1: repeat(3, 1fr) — 3列で固定

.card-list {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 24px;
}

repeat(3, 1fr) は「1fr という列を3回繰り返す」という意味です。省略せずに書くと 1fr 1fr 1fr とまったく同じで、値の個数がそのまま列数になります。

fr は Grid 専用の単位で、「余っている幅を何等分するか」の取り分を表します。1fr 1fr 1fr なら取り分が1:1:1なので3等分。2fr 1fr なら2:1で、左の列が右の2倍の幅になります。

/* 書き方の例(実際に使うのはこのうちどれか1つ) */
grid-template-columns: repeat(3, 1fr);   /* 3等分の3列 */
grid-template-columns: 1fr 1fr 1fr;      /* ↑と完全に同じ */
grid-template-columns: 2fr 1fr;          /* 2:1 の2列 */
grid-template-columns: 240px 1fr;        /* 左は240px固定、右が残り全部 */

読み方は単純です。値をスペースで区切って数えれば、それが列数。repeat(N, ...) が出てきたら N が列数です。ただし repeat()minmax()カッコの中にもスペースが入るので、機械的に空白で切ると数を間違えます。repeat(auto-fit, minmax(240px, 1fr)) にはスペースが2つありますが、列指定は1つです。カッコで囲まれた部分は、中身がいくつに見えても1つの塊として数えてください。

ただしこの書き方には弱点があります。repeat(3, 1fr)スマホでも3列のままです。列数が変わる仕組みがどこにも無いので、375px の画面では1列あたり100px程度まで潰れて読めなくなります。だからAIは、レスポンシブを意識すると次のパターン2を出してきます。

なお「必ず潰れる」わけではありません。1fr は正確には minmax(auto, 1fr) で、中身が縮められる限界を下回らない性質があります。日本語は好きな位置で折り返せるので今回の例では素直に潰れますが、長い英単語やURLが入っていると、そこで縮むのをやめて親から溢れます。同じ repeat(3, 1fr) でも、潰れるか溢れるかは中身次第です。

パターン2: repeat(auto-fit, minmax(240px, 1fr))

.card-list {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 24px;
}

見た目は複雑ですが、意味は一言です。「1列が240pxを下回らない範囲で、入るだけ入れる」。内側から分解します。

結果として、列数が画面幅で自動的に変わります。gap: 24px がある場合の目安はこうです。

/* repeat(auto-fit, minmax(240px, 1fr)) + gap: 24px のとき */

コンテナ幅 1000px → 3列(240×3 + 24×2 = 768px なので3列入る)
コンテナ幅  700px → 2列(3列だと 768px 必要で足りない)
コンテナ幅  400px → 1列(2列だと 504px 必要で足りない)

メディアクエリを1行も書かずにレスポンシブになるのがこの書き方の利点で、だからAIはカード一覧でこれを多用します。「なぜ画面を狭めると勝手に2列になるのか」の答えは、ここに書いてあります。

読むときは minmax() の第1引数(この例なら240px)だけ見れば十分です。それが「1列の最低幅」であり、列数が切り替わる境目を決めている数字です。カードが窮屈だと感じたらこの数字を大きく、もっと詰めたければ小さくします。

gap — 間隔は margin ではなく gap で取る

上のコードに毎回出てくる gap は、子要素どうしの間隔を指定するプロパティです。Flex コンテナと Grid コンテナの両方で使えます。

gap の良いところは、間だけに効いて、外側の端には付かないことです。子に margin で間隔を付けると、両端にも余白が出て「最初のカードだけ左が空く」といったズレが起きます。gap はそれが起きません。

重要なのは、gap は親のプロパティだという点です。子に書いても効きません。効かないときの確認方法は、後の「AIが間違えやすい点」で扱います。

覚え方: repeat(3, 1fr) は「3列で固定」、repeat(auto-fit, minmax(240px, 1fr)) は「240pxを切らない範囲で入るだけ」。列数を知りたければ、前者は数字を、後者は minmax() の1つ目を見る。

FlexかGridか — AIの選択を評価する

ここまでで、Flex の出力も Grid の出力も読めるようになりました。最後に「AIがそちらを選んだのは妥当か」を判断する軸を持ちます。ここが読める人と読めない人の差が一番出るところです。

判断の基準は1つだけ

1方向に並べるだけなら Flex。行と列の両方を揃える必要があるなら Grid。

「揃える」がポイントです。カードが折り返して2行になったとき、2行目のカードの左端が1行目の左端と縦に揃っていてほしいかを考えます。揃っていてほしいなら Grid の仕事です。Flex は行ごとに独立して配置を決めるので、行をまたいだ縦の整列は保証されません。

ケース1: 妥当な選択

/* ヘッダー: ロゴとナビを左右に離す = 1方向 → Flex が妥当 */
.header {
    display: flex;
    justify-content: space-between;
    align-items: center;
}

/* カード一覧: 折り返して格子になる = 2方向 → Grid が妥当 */
.card-list {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 24px;
}

ケース2: 理由なく Grid が使われている

/* ボタンを2つ横に並べたいだけなのに Grid */
.button-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
}

動きますが、ボタンの数が変わった瞬間に破綻します1fr 1fr は「2列」と決め打ちなので、ボタンを3つに増やすと3つ目が2行目に落ちます。また、ボタンの文字数が違っても強制的に等幅になるため、「キャンセル」と「この内容で申し込む」が同じ幅になって不自然に見えます。

/* 素直に Flex にすれば、数が増えても文字数が違っても崩れない */
.button-row {
    display: flex;
    gap: 8px;
}

指摘の仕方: 「ボタンは1方向に並べるだけなので Flex にしてください。数が増えたときに崩れないようにしたいです」とAIに返せば直ります。

ケース3: カード一覧を Flex + width: 33% で組んでいる

/* AIがたまに出してくる、崩れやすい書き方 */
.card-list {
    display: flex;
    flex-wrap: wrap;   /* 1行に収まらない子を次の行へ折り返す */
    gap: 24px;
}

.card {
    width: 33%;      /* ← ここが問題 */
}

先に flex-wrap: wrap について触れておきます。Flex は既定では折り返しません(nowrap)。1行に収まらないときは子を縮めて無理に押し込みます。flex-wrap: wrap を付けると、収まらない子が次の行へ送られるようになります。この1行があるかどうかで、幅が足りないときの挙動が「潰れる」か「折り返す」かに分かれます。

そのうえで、この書き方はほぼ確実に崩れます33% が3つで99%、そこに gap: 24px が2つ分(48px)加わるので、合計がコンテナ幅を超えます。flex-wrap: wrap があるため3枚目は縮まずに2行目へ落ちます。「なぜか2列しか並ばない」の典型パターンです。

さらに悪いことに、カード1枚の幅は画面をどれだけ狭めてもコンテナの33%のままです。パーセント指定は「画面幅が変わっても割合が変わらない」ので、スマホでは1枚あたり100px前後まで一緒に細くなります。列数が切り替わる仕組みがどこにも無いのが、この書き方の根本的な弱点です。

直し方は2つあります。カードが格子状に並ぶなら Grid の auto-fit に置き換えるのが素直で、Flex のままでいくなら width をやめて flex: 1 1 240px(基準240pxで伸縮)にします。どちらにせよ「パーセント固定幅 + gap」の組み合わせを見たら、崩れる前提で疑ってよいです。

評価するときのチェックリスト

なお Week 06 で「中央寄せは迷ったら Flexbox」と書きました。あれは1つの要素を中央に置く話で、今週の「複数の要素をどう並べるか」とは別の判断です。中央寄せの場面では、今も迷わず Flex で構いません。

各プロパティの網羅的な一覧と細かい挙動は、それぞれの解説ページにまとめてあります。今週の「読み方」で当たりを付けてから、必要な箇所だけ引くのが速いです。

今週の課題

Week 07 課題

Week 06 で display: inline-block を使って横並びにした3枚のカードを、(1) Flexbox版 と (2) Grid版 の2通りに書き換えるのが今週のゴールです。同じ見た目を2つの道具で作り、ウィンドウ幅を狭めたときの崩れ方の違いを自分の目で確認します。

必須要件

  1. Week 06 のカード3枚のHTMLはそのまま使い、CSSだけを書き換える(HTMLを変えずに済むことを体感するのが目的)
  2. Flexbox版: 親に display: flex、子に flex: 1 を付けて等幅で横並びにする
  3. Grid版: 親に display: gridgrid-template-columns: repeat(3, 1fr) を付けて3列にする
  4. どちらの版も、カード同士の間隔は margin ではなく gap: 24px で取る
  5. Week 06 で付けたカードの上下マージンと、inline-block 由来のタグ間の改行による横の隙間が、gap と二重になっていないか確認する(横並びの隙間は margin ではなくインライン空白が作っていたので、flex / grid にすると消えます)
  6. ウィンドウ幅を1200px → 400px までゆっくり狭め、Flex版とGrid版で崩れ方がどう違うかを観察する(どちらも折り返さず潰れていくはず)
  7. Grid版の repeat(3, 1fr)repeat(auto-fit, minmax(240px, 1fr)) に差し替え、もう一度幅を狭めて列数が3→2→1と変わることを確認する
  8. DevTools で親要素を選択し、Computed タブで displaygap計算値を確認する(自分が書いた値が本当に効いているかを見る)
  9. Flex版で flex: 1 を一時的に消し、カードの幅が中身の文字数分に変わることを確認してから戻す
  10. Flex版の親に flex-direction: column を一時的に足し、justify-contentalign-items の効く向きが入れ替わることを確認してから戻す

ボーナス課題

自己チェック項目(提出前に確認)

自己診断テスト

  1. justify-content はどちらの軸に効きますか。flex-direction: column にすると答えは変わりますか?
  2. flex: 1 を展開すると3つのプロパティになります。何ですか。またそのうち「文字数に関係なく等幅になる」ことを決めているのはどれですか?
  3. repeat(auto-fit, minmax(240px, 1fr)) は何列になりますか。列数を決めているのは何ですか?
  4. gap が効かないとき、最初に確認する場所はどこですか?
  5. AI出力のレイアウトCSSを読むとき、最初に見る3行は何と何と何ですか?
  6. flex-direction が書かれていないFlexコンテナは、子が縦横どちらに並びますか?
  7. カード一覧を display: flex + width: 33% + gap: 24px で組むと何が起きますか。理由も説明できますか?