シリーズ ゼロからWebサイトを作ろう! 第6回 / 全10回

CSS Gridとレスポンシブデザイン ―
どんな画面でも美しく

メニューカードやギャラリーをグリッドで美しく配置し、スマートフォンやタブレットでも崩れないレスポンシブ対応を実装します。ブラウザの幅を縮めた瞬間、レイアウトが滑らかに切り替わる — その体験を、今回手に入れましょう。

前回のおさらい

📖 前回のおさらい
  • Flexboxの基本を学び、親要素に display: flex を書くだけで子要素が横並びになることを体験した
  • justify-contentalign-itemsgapflex: 1 を使い、ヘッダー・コンセプト・アクセス・フッターを横並びレイアウトにした
  • ヒーローセクションの中央配置を position + transform からFlexboxに置き換え、コードをシンプルにした

ヘッダーにナビが横並びで収まり、コンセプトやアクセスも左右に画像とテキストが並ぶようになりました。かなりサイトらしくなりましたよね。

でも、メニューカードが4つ縦に並んでいたり、ギャラリーの画像もただ縦に積まれていたり。それに、スマートフォンで見たらどうなるでしょう? 横並びのコンセプトがギュッと圧縮されて読めなくなってしまいます。今回は、その2つの問題を一気に解決します。

今回のゴール

🎯 今回のゴール
  • CSS Gridの基本を理解し、メニューカードを2列・ギャラリーを大小レイアウトで配置する
  • レスポンシブデザインの考え方を学び、メディアクエリでタブレット・モバイル対応を実装する
  • ハンバーガーメニューのHTMLとCSSを追加し、モバイル時にナビゲーションを切り替えられる準備をする
👀 この回の完成イメージ
今回の内容を終えると、サイトはこのような状態になります。先に完成形を確認しておきたい方はこちらをご覧ください。
第6回の完成イメージを見る →

CSS Gridって何? — Flexboxとの使い分け

前回学んだFlexboxは「1方向に並べる」のが得意でした。横一列にナビリンクを並べたり、左右に画像とテキストを配置したり。基本的に「一列」の並びを制御する仕組みです。

それに対して CSS Grid(グリッド) は、縦と横の2方向を同時に制御できるレイアウト手法です。行と列を定義して、まるでExcelの表のようにセルの中にアイテムを配置できます。

使い分けの目安はこんなイメージです。横一列に並べるだけならFlexbox。行と列の両方を意識して「格子状」に配置したいならGrid。café SOELのメニューカードを2列×2行に並べたり、ギャラリーで大きい写真と小さい写真を組み合わせたりする場面は、Gridの出番です。

FlexboxとGridは敵同士じゃない。得意な場面が違うだけ。両方使えると、レイアウトの選択肢が一気に広がる。

Gridの基本の書き方

Flexboxと同じで、親要素に display: grid を書くところからスタートします。ただしGridの場合は、列数(カラム数)を明示的に指定する必要があります。

CSS Gridの基本構文
.menu-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 32px;
}

grid-template-columns が列の定義です。 repeat(2, 1fr) は「1fr を 2 回繰り返す」つまり「等幅の2列」という意味。 1frfr は "fraction"(分数)の略で、利用可能なスペースを等分するための単位です。 gap はFlexboxと同じく、アイテム間の余白を指定します。

▶ Live Demo — display: grid で2列配置
Card 1
Card 2
Card 3
Card 4

4つのアイテムが自動的に2列×2行に配置されました。Flexboxだと flex-wrap: wrap と幅の計算が必要になりますが、Gridなら列数を指定するだけ。直感的ですよね。

3列にしたければ repeat(3, 1fr) に変えるだけです。

▶ Live Demo — repeat(3, 1fr) で3列配置
1
2
3
4
5
6

grid-row — アイテムを複数行にまたがらせる

Gridの強力な機能のひとつが、特定のアイテムを複数行にまたがらせること。ギャラリーで「大きな写真1枚 + 小さな写真2枚」のレイアウトを作るときに使います。

grid-row で2行にまたがる
.gallery-item--large {
  grid-row: 1 / 3;  /* 1行目から3行目の手前まで = 2行分 */
}

grid-row: 1 / 3 は「グリッドの線の1番目から3番目まで」を占めるという意味。2列のグリッドで左のアイテムだけ2行分の高さにすると、右側に小さなアイテムが2つ積まれるレイアウトになります。

▶ Live Demo — grid-row: 1 / 3 で大小レイアウト

café SOELのギャラリーセクションで、まさにこのレイアウトを使います。

aspect-ratio — 画像の縦横比を固定する

メニューカードの画像は正方形(1:1)で統一したいですよね。 aspect-ratio プロパティを使えば、画像の縦横比を簡単に固定できます。

aspect-ratio の使い方
.menu-card-image {
  aspect-ratio: 1 / 1;  /* 正方形 */
  overflow: hidden;        /* はみ出た部分を隠す */
}

aspect-ratio: 1 / 1 で正方形に、 16 / 9 にすればワイドスクリーン比率に。 overflow: hidden と組み合わせて、画像の中央部分だけを見せることができます。

メニューセクションをGridで配置しよう

それでは実際にcafé SOELのCSSにGridを追加していきます。メニューセクションの4枚のカードを2列×2行で並べましょう。

css/style.css に以下を追加してください。

css/style.css — メニューグリッドを追加
/* ---------- Menu Grid ---------- */
.menu-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 32px;
  max-width: var(--container-width);
  margin: 0 auto;
}

.menu-card {
  background-color: var(--color-white);
  border-radius: 16px;
  overflow: hidden;
}

.menu-card-image {
  aspect-ratio: 1 / 1;
  overflow: hidden;
}

.menu-card-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.menu-griddisplay: gridgrid-template-columns: repeat(2, 1fr) を指定して2列に。 .menu-card には白背景と角丸、 overflow: hidden で角丸の外に画像がはみ出ないようにしています。

.menu-card-imageaspect-ratio: 1 / 1 で正方形に統一し、中の imgobject-fit: cover でトリミング。第4回のヒーロー画像で使った object-fit がここでも活躍します。

保存してブラウザを確認してみてください。メニューカードが2列に並び、正方形の画像が揃ってカフェらしいメニュー表示になっているはずです。

💡 KANONのワンポイント
aspect-ratio は比較的新しいプロパティですが、2021年以降のすべてのモダンブラウザで使えます。IE(Internet Explorer)はもうサポート終了しているので、安心して使ってください。私がWeb制作を始めた頃は padding-top のハックで縦横比を作っていたんですが、今は1行で済む。ありがたい時代です。

ギャラリーセクションをGridで配置しよう

続いてギャラリーセクション。3枚の写真を「左に大きな1枚、右に小さな2枚」のレイアウトで配置します。先ほど学んだ grid-row の出番です。

css/style.css — ギャラリーグリッドを追加
/* ---------- Gallery Grid ---------- */
.gallery-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: auto auto;
  gap: 16px;
  max-width: var(--container-width);
  margin: 0 auto;
}

.gallery-item {
  border-radius: 12px;
  overflow: hidden;
}

.gallery-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.gallery-item--large {
  grid-row: 1 / 3;
}

grid-template-columns: 1fr 1frrepeat(2, 1fr) と同じ意味。書き方の好みの差だけです。 grid-template-rows: auto auto で行の高さはコンテンツに合わせて自動調整にしています。

.gallery-item--largegrid-row: 1 / 3 を指定することで、HTMLの1番目の画像(窓際の席の写真)が2行分の高さで表示されます。右側には2番目と3番目の画像が1行ずつ収まります。

保存してブラウザを確認すると、ギャラリーが雑誌のような大小レイアウトに変わっているはずです。

📌 覚えておこう
grid-row: 1 / 3 の数字はアイテムの番号ではなく、グリッド線の番号です。2行のグリッドには、上から1番線・2番線・3番線の3本の水平線があります。「1番線から3番線まで」つまり2行分を占める、という指定です。最初は戸惑いやすいポイントですが、「線の番号」と覚えてしまえば大丈夫です。

レスポンシブデザインの考え方

メニューとギャラリーがGrid で整いました。PC画面ではきれいですね。でも、ブラウザの幅をグッと狭くしてみてください。……文字が潰れたり、カードが極端に小さくなったりしませんか?

Webサイトは、PC、タブレット、スマートフォンなど、さまざまな画面サイズで閲覧されます。どの画面サイズでも読みやすく、使いやすいレイアウトに対応すること。それが レスポンシブデザイン です。

第2回で <meta name="viewport"><head> に書いたのを覚えていますか? あのときは「おまじない」としてお伝えしましたが、実はこれが「画面幅に合わせてレイアウトを調整する」ための土台でした。あの1行がなければ、スマートフォンではPC用のレイアウトがそのまま縮小表示されてしまいます。

レスポンシブは「スマホ対応」じゃなく「どの画面でも最適な体験を届ける」こと。

メディアクエリ — 画面幅に応じてCSSを切り替える

レスポンシブデザインの中心的な仕組みが メディアクエリ@media)です。「画面幅が○○px以下のときは、このCSSを適用する」という条件分岐をCSSの中に書けます。

メディアクエリの基本構文
/* 通常のスタイル(PC画面用) */
.menu-grid {
  grid-template-columns: repeat(2, 1fr);
}

/* 画面幅が600px以下のとき */
@media (max-width: 600px) {
  .menu-grid {
    grid-template-columns: 1fr;  /* 1列に変更 */
  }
}

この例では、画面幅が600px以下になったらメニューグリッドが1列に変わります。スマートフォンで見たときに、カードが1枚ずつ縦に並ぶ表示です。

▶ Live Demo — メディアクエリの動作確認
このボックスは画面幅600px以下で色が変わります。ブラウザの幅を縮めてみてください。

ブレイクポイント — どこで切り替えるか

メディアクエリで「何px以下のときに切り替えるか」の値を ブレイクポイント と呼びます。café SOELでは2つのブレイクポイントを使います。

900px — タブレットサイズ。ここで横並びレイアウトを縦積みに切り替え、ハンバーガーメニューを表示します。

600px — モバイルサイズ。ここでさらに余白やフォントサイズを調整し、メニューカードを1列にします。

💡 KANONのワンポイント
ブレイクポイントの数値に「絶対の正解」はありません。768pxを使う人もいれば、1024pxを使う人もいます。大事なのは、実際にブラウザの幅を動かしてみて「ここで崩れるな」と感じたポイントで切り替えること。デバイスのサイズに合わせるのではなく、コンテンツが崩れるポイントに合わせるのが現場の考え方です。

ハンバーガーメニューを追加しよう

モバイルサイズでは、ヘッダーのナビリンクを全部横並びで表示するスペースがありません。そこで登場するのが、3本線の「ハンバーガーメニュー」ボタン。タップするとナビゲーションが開く、スマートフォンサイトではおなじみのUIです。

まず、index.html のヘッダー部分にボタンのHTMLを追加します。

1

index.html にハンバーガーボタンを追加

<nav> タグの直前に、以下の <button> を追加してください。

index.html — header 内に追加
<header class="header" id="top">
  <div class="header-inner">
    <a href="#top" class="logo">
      <img src="img/logo.png" alt="café SOEL" width="160" height="40">
    </a>

    <!-- ↓ ここを追加 -->
    <button class="menu-toggle" type="button">
      <span class="menu-toggle-bar"></span>
      <span class="menu-toggle-bar"></span>
      <span class="menu-toggle-bar"></span>
    </button>

    <nav class="nav" aria-label="メインナビゲーション">
      <ul class="nav-list">
        <!-- ... 既存のナビリンク ... -->
      </ul>
    </nav>
  </div>
</header>

3つの <span class="menu-toggle-bar"> が3本の横線になります。画像ではなくHTMLとCSSで作るので、色やサイズの変更が簡単です。

もうひとつ、注目してほしいのが <nav> タグに class="nav" を追加したことです。第3回ではclassを付けていませんでしたが、レスポンシブ対応でnavの表示・非表示を切り替えるために、classが必要になります。<nav aria-label="メインナビゲーション"><nav class="nav" aria-label="メインナビゲーション"> に変更してください。

▶ Live Demo — ハンバーガーボタンの見た目
← これがハンバーガーボタン(PC表示では非表示になります)
2

css/style.css にハンバーガーのスタイルを追加

PC表示では非表示にし、モバイル表示で表示されるようにします。

css/style.css — ハンバーガーメニューのスタイル
/* ---------- Hamburger Menu ---------- */
.menu-toggle {
  display: none;           /* PC表示では非表示 */
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 32px;
  height: 32px;
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px;
}

.menu-toggle-bar {
  display: block;
  width: 100%;
  height: 2px;
  background-color: var(--color-black);
  border-radius: 1px;
}

display: none でPC表示では隠しておきます。タブレットサイズ以下のメディアクエリ内で display: flex に切り替えて表示する仕組みです。

⚠️ よくあるミス
ハンバーガーメニューに <div> ではなく <button> を使っているのには理由があります。 <button> はキーボード操作やスクリーンリーダーに対応した「操作できる要素」です。 <div> で見た目だけボタン風にしても、キーボードでフォーカスできず、アクセシビリティ上の問題があります。「クリックできるもの」には <button><a> を使う。これは覚えておいてくださいね。

タブレット対応 — 900pxのメディアクエリ

いよいよメディアクエリを書いていきます。まずは900px以下(タブレットサイズ)のスタイルから。 css/style.css の末尾に、以下を丸ごと追加してください。

css/style.css — タブレット対応(900px以下)
/* ========================================
   レスポンシブ:タブレット(900px以下)
   ======================================== */
@media (max-width: 900px) {

  :root {
    --section-padding: 80px;
  }

  /* --- ナビゲーション → フルスクリーン --- */
  .nav {
    display: none;
    position: fixed;
    inset: 0;
    background-color: rgba(255, 255, 255, 0.98);
    z-index: 99;
    align-items: center;
    justify-content: center;
  }

  .nav.is-open {
    display: flex;
  }

  .nav-list {
    flex-direction: column;
    align-items: center;
    gap: 32px;
  }

  .nav-link {
    font-size: 20px;
    letter-spacing: 3px;
  }

  /* --- ハンバーガーを表示 --- */
  .menu-toggle {
    display: flex;
    z-index: 101;
  }

  /* --- コンセプト・アクセス → 縦積み --- */
  .concept-inner {
    flex-direction: column;
    gap: 40px;
  }

  .access-inner {
    flex-direction: column;
    gap: 40px;
  }

  /* --- メニューグリッドの余白調整 --- */
  .menu-grid {
    gap: 20px;
  }

  /* --- ギャラリー → 1列に --- */
  .gallery-grid {
    grid-template-columns: 1fr;
  }

  .gallery-item--large {
    grid-row: auto;
  }
}

順番に見ていきましょう。

--section-padding: 80px で、タブレットでは各セクションの上下余白を少し縮めます。CSS変数の値をメディアクエリ内で上書きできるのも便利なポイントです。

.navdisplay: none で隠し、 .nav.is-open のときだけ display: flex で画面全体に表示する仕組みです。 position: fixedinset: 0 で画面全体を覆うフルスクリーンのナビゲーションにしています。ナビリンクは flex-direction: column で縦に並べ、フォントサイズも大きくして指で押しやすくしています。

.menu-toggledisplay: flex に変更して、ハンバーガーボタンを表示。 z-index: 101 でナビゲーション(z-index: 99)よりも手前に配置し、メニューが開いていてもボタンが押せるようにしています。

.concept-inner.access-inner には flex-direction: column を指定。前回Flexboxで横並びにしたレイアウトが、タブレットでは縦積みに戻ります。Flexboxは flex-direction を変えるだけで横並び↔縦並びを切り替えられる。この柔軟さがFlexboxの強みですね。

ギャラリーは grid-template-columns: 1fr(1列)に変更し、 .gallery-item--largegrid-rowauto にリセット。大小レイアウトを解除して、すべての写真を同じサイズで縦に並べます。

📌 覚えておこう
メディアクエリは必ず CSSファイルの末尾(通常のスタイルの後)に書いてください。CSSは「あとに書いたルールが優先される」ので、メディアクエリを先に書いてしまうと、通常のスタイルに上書きされてしまいます。

モバイル対応 — 600pxのメディアクエリ

続けて、600px以下(モバイルサイズ)のスタイルを追加します。先ほどの900pxのメディアクエリの直後に書いてください。

css/style.css — モバイル対応(600px以下)
/* ========================================
   レスポンシブ:モバイル(600px以下)
   ======================================== */
@media (max-width: 600px) {

  :root {
    --section-padding: 60px;
  }

  .hero {
    min-height: 500px;
  }

  .hero-title {
    font-size: 26px;
    letter-spacing: 0.04em;
  }

  .menu-grid {
    grid-template-columns: 1fr;
  }

  .info-item {
    flex-direction: column;
    gap: 4px;
  }

  .info-item dt {
    width: auto;
  }

  .footer-nav {
    flex-direction: column;
    align-items: center;
    gap: 16px;
  }
}

モバイルではセクション余白をさらに縮めて60pxに。ヒーローの最小高さを500pxに下げ、タイトルのフォントサイズも小さくして、小さな画面でも読みやすくしています。

メニューグリッドは grid-template-columns: 1fr で1列に。カードが縦に1枚ずつ並ぶので、小さな画面でも写真とテキストがしっかり見えます。

店舗情報の .info-itemflex-direction: column で項目名と内容を縦に積みます。前回 width: 100pxflex-shrink: 0 で固定していたdtの幅も auto に戻して、自然な幅にしています。

フッターのナビリンクも flex-direction: column で縦並びに。指で押しやすい間隔を保つために gap: 16px を設定しています。

ブラウザで確認しよう

すべてのコードを保存したら、ブラウザで確認してみましょう。ウィンドウの幅をゆっくりと狭くしていってください。

1

PC幅(900px以上)

メニューカードが2列のグリッドに並び、ギャラリーが大小のレイアウトに。コンセプトとアクセスは画像とテキストが横並び。ハンバーガーボタンは見えない状態です。

2

タブレット幅(600px〜900px)

ハンバーガーメニューが現れ、コンセプトとアクセスが縦積みに。ギャラリーは1列に。メニューカードはまだ2列を維持しています。

3

モバイル幅(600px以下)

メニューカードも1列に。ヒーローのタイトルが小さくなり、店舗情報が縦積みに。フッターナビも縦並びに変わります。

画面幅を動かしながら、レイアウトがスムーズに切り替わる瞬間を確認してみてください。900pxと600pxの境目で変化が起きるのがわかるはずです。

⚠️ よくあるミス
ハンバーガーボタンをクリックしてもメニューが開かないのは正常です。メニューの開閉には .is-open クラスの付け替えが必要で、それはJavaScriptの役割。番外編のEX2で実装します。今の時点では「ボタンは表示されているが、まだ動かない」が正しい状態です。

FlexboxとGridの使い分け — まとめ

ここで、FlexboxとGridの使い分けを整理しておきましょう。

Flexboxは「1方向の並び」が得意です。ヘッダーのロゴとナビを左右に配置する、ナビリンクを横一列に並べる、フッターを縦一列に中央揃えする。こうした「一列」のレイアウトはFlexboxが最適です。

Gridは「2次元の配置」が得意です。メニューカードを2列×2行に並べる、ギャラリーで大小の写真を組み合わせる。行と列の両方を意識するレイアウトはGridの出番です。

迷ったときの目安はこうです。「横に並べるだけ?」ならFlexbox。「格子状に並べる? 行と列を同時に指定する?」ならGrid。もちろん、どちらで書いても実現できるケースは多いので、「こっちのほうが書きやすい」で選んでしまっても大丈夫です。

実は今回、メディアクエリの中では flex-direction: column を多用しました。Gridで作ったメニューは grid-template-columns: 1fr で1列に。Flexboxで作ったコンセプトは flex-direction: column で縦積みに。どちらも「レスポンシブで1列に戻す」操作が1行で書けるように設計されています。

レイアウトの引き出しが増えると、デザインの自由度がぐんと上がる。Flexbox と Grid の両方を使いこなせれば、たいていのレイアウトは怖くない。

ここまでの全コードを確認しよう

今回はHTMLとCSSの両方に変更がありました。うまくいかない場合は、以下の全コードをまるごとコピーして貼り付けてリカバリーしてください。

👀 この回の完成イメージ
あなたの画面でも同じように表示されていますか? 完成イメージと見比べてみてください。
第6回の完成イメージを見る →

index.html(第6回完成時点)

index.html(第6回完成時点)
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>café SOEL — そよぐ光と、ひと息つくひととき</title>
  <link rel="stylesheet" href="css/style.css">
</head>
<body>

  <!-- ========== HEADER ========== -->
  <header class="header" id="top">
    <div class="header-inner">
      <a href="#top" class="logo">
        <img src="img/logo.png" alt="café SOEL" width="160" height="40">
      </a>
      <button class="menu-toggle" type="button">
        <span class="menu-toggle-bar"></span>
        <span class="menu-toggle-bar"></span>
        <span class="menu-toggle-bar"></span>
      </button>
      <nav class="nav" aria-label="メインナビゲーション">
        <ul class="nav-list">
          <li><a href="#concept" class="nav-link">Concept</a></li>
          <li><a href="#menu" class="nav-link">Menu</a></li>
          <li><a href="#gallery" class="nav-link">Gallery</a></li>
          <li><a href="#access" class="nav-link">Access</a></li>
        </ul>
      </nav>
    </div>
  </header>

  <main>

    <!-- ========== HERO ========== -->
    <section class="hero" id="hero">
      <div class="hero-image">
        <img src="img/hero.jpg" alt="café SOELの店内。大きな窓から自然光が差し込む開放的な空間" width="1920" height="1080">
      </div>
      <div class="hero-content">
        <p class="hero-sub">Natural Light &amp; Coffee</p>
        <h1 class="hero-title">そよぐ光と、<br>ひと息つくひととき。</h1>
        <a href="#concept" class="hero-cta">Scroll</a>
      </div>
    </section>

    <!-- ========== CONCEPT ========== -->
    <section class="concept section" id="concept">
      <div class="concept-inner">
        <div class="concept-image">
          <img src="img/concept.jpg" alt="木のテーブルの上に置かれたハンドドリップコーヒーと陶器のカップ" width="1200" height="800">
        </div>
        <div class="concept-text">
          <span class="section-label">Concept</span>
          <h2 class="section-heading">ひかりと、木と、<br>一杯のコーヒーと。</h2>
          <p>café SOEL(カフェ ソエル)は、「そよぐ光」をイメージした造語から名づけました。</p>
          <p>大きな窓から差し込む自然光、ナチュラルな木のぬくもり、丁寧に淹れた一杯のコーヒー。忙しい日常からすこしだけ離れて、穏やかな時間を過ごしていただける場所でありたいと思っています。</p>
          <p>素材の味を大切にしたフードとドリンクを、心を込めてお届けします。</p>
        </div>
      </div>
    </section>

    <!-- ========== MENU ========== -->
    <section class="menu section" id="menu">
      <div class="section-header">
        <span class="section-label">Menu</span>
        <h2 class="section-heading">こだわりのメニュー</h2>
        <p class="section-desc">素材を活かしたシンプルなおいしさを、丁寧にお届けします。</p>
      </div>
      <div class="menu-grid">
        <article class="menu-card">
          <div class="menu-card-image">
            <img src="img/menu-coffee.jpg" alt="ハンドドリップで淹れたコーヒー" width="800" height="800">
          </div>
          <div class="menu-card-body">
            <h3 class="menu-card-title">Hand Drip Coffee</h3>
            <p class="menu-card-desc">産地ごとに異なる豆の個性を引き出す、一杯ずつ丁寧なハンドドリップ。</p>
            <span class="menu-card-price">¥550</span>
          </div>
        </article>
        <article class="menu-card">
          <div class="menu-card-image">
            <img src="img/menu-latte.jpg" alt="シンプルなラテアートが施されたカフェラテ" width="800" height="800">
          </div>
          <div class="menu-card-body">
            <h3 class="menu-card-title">Café Latte</h3>
            <p class="menu-card-desc">まろやかなミルクとエスプレッソのバランス。シンプルなラテアートを添えて。</p>
            <span class="menu-card-price">¥600</span>
          </div>
        </article>
        <article class="menu-card">
          <div class="menu-card-image">
            <img src="img/menu-cake.jpg" alt="素朴で美しいケーキ" width="800" height="800">
          </div>
          <div class="menu-card-body">
            <h3 class="menu-card-title">Seasonal Cake</h3>
            <p class="menu-card-desc">季節のフルーツを使った、素朴だけど美しいケーキ。コーヒーとの相性も抜群です。</p>
            <span class="menu-card-price">¥650</span>
          </div>
        </article>
        <article class="menu-card">
          <div class="menu-card-image">
            <img src="img/menu-sandwich.jpg" alt="断面が美しいサンドイッチ" width="800" height="800">
          </div>
          <div class="menu-card-body">
            <h3 class="menu-card-title">SOEL Sandwich</h3>
            <p class="menu-card-desc">新鮮な野菜とハム、自家製ソースをはさんだボリュームのあるサンドイッチ。</p>
            <span class="menu-card-price">¥900</span>
          </div>
        </article>
      </div>
    </section>

    <!-- ========== GALLERY ========== -->
    <section class="gallery section" id="gallery">
      <div class="section-header">
        <span class="section-label">Gallery</span>
        <h2 class="section-heading">店内の様子</h2>
        <p class="section-desc">自然光と木のぬくもりに包まれた、穏やかな空間。</p>
      </div>
      <div class="gallery-grid">
        <div class="gallery-item gallery-item--large">
          <img src="img/interior-01.jpg" alt="大きな窓から光が差し込む窓際の席" width="1200" height="800">
        </div>
        <div class="gallery-item">
          <img src="img/interior-02.jpg" alt="木のカウンターとペンダントライト" width="1200" height="800">
        </div>
        <div class="gallery-item">
          <img src="img/exterior.jpg" alt="白い外壁と黒フレームの窓が特徴的な外観" width="1600" height="1000">
        </div>
      </div>
    </section>

    <!-- ========== ACCESS ========== -->
    <section class="access section" id="access">
      <div class="access-inner">
        <div class="access-image">
          <img src="img/exterior.jpg" alt="café SOELの外観" width="1600" height="1000">
        </div>
        <div class="access-info">
          <span class="section-label">Access</span>
          <h2 class="section-heading">店舗情報</h2>
          <dl class="info-list">
            <div class="info-item">
              <dt>住所</dt>
              <dd>〒156-0054 東京都世田谷区桜丘町3-12-8</dd>
            </div>
            <div class="info-item">
              <dt>営業時間</dt>
              <dd>
                平日 10:00 – 19:00<br>
                土日 9:00 – 19:00
              </dd>
            </div>
            <div class="info-item">
              <dt>定休日</dt>
              <dd>毎週水曜日</dd>
            </div>
            <div class="info-item">
              <dt>電話</dt>
              <dd><a href="tel:03-0000-0000">03-0000-0000</a></dd>
            </div>
            <div class="info-item">
              <dt>アクセス</dt>
              <dd>小田急線 千歳船橋駅より徒歩8分</dd>
            </div>
          </dl>
        </div>
      </div>
    </section>

  </main>

  <!-- ========== FOOTER ========== -->
  <footer class="footer">
    <div class="footer-inner">
      <a href="#top" class="footer-logo">
        <img src="img/logo.png" alt="café SOEL" width="120" height="30">
      </a>
      <ul class="footer-nav">
        <li><a href="#concept">Concept</a></li>
        <li><a href="#menu">Menu</a></li>
        <li><a href="#gallery">Gallery</a></li>
        <li><a href="#access">Access</a></li>
      </ul>
      <p class="footer-copy">&copy; 2025 café SOEL. All rights reserved.</p>
    </div>
  </footer>

</body>
</html>

css/style.css(第6回完成時点)

css/style.css(第6回完成時点)
/* ===================================
   café SOEL — Style Sheet
   第6回時点:基本CSS + Flexbox + Grid + レスポンシブ
   =================================== */

/* ---------- CSS Custom Properties ---------- */
:root {
  --color-bg: #ffffff;
  --color-bg-soft: #faf9f7;
  --color-bg-accent: #f5f0eb;
  --color-text: #2c2c2c;
  --color-text-light: #6b6b6b;
  --color-text-muted: #999999;
  --color-accent: #8b7355;
  --color-accent-light: #c4a882;
  --color-border: #e8e4df;
  --color-white: #ffffff;
  --color-black: #1a1a1a;

  --section-padding: 120px;
  --container-width: 1200px;
  --container-padding: 24px;
}

/* ---------- Reset & Base ---------- */
*,
*::before,
*::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  scroll-padding-top: 80px;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI',
               'Hiragino Kaku Gothic ProN', 'Noto Sans JP', sans-serif;
  font-weight: 400;
  font-size: 16px;
  line-height: 1.9;
  color: var(--color-text);
  background-color: var(--color-bg);
}

img {
  display: block;
  max-width: 100%;
  height: auto;
}

a {
  color: inherit;
  text-decoration: none;
}

ul, ol {
  list-style: none;
}

/* ---------- Utility ---------- */
.section {
  padding: var(--section-padding) var(--container-padding);
}

.section-label {
  display: inline-block;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 3px;
  text-transform: uppercase;
  color: var(--color-accent);
  margin-bottom: 16px;
}

.section-heading {
  font-size: clamp(24px, 4vw, 36px);
  font-weight: 700;
  line-height: 1.4;
  letter-spacing: 0.02em;
  color: var(--color-black);
  margin-bottom: 20px;
}

.section-header {
  text-align: center;
  max-width: 600px;
  margin: 0 auto 60px;
}

.section-desc {
  font-size: 15px;
  color: var(--color-text-light);
  line-height: 1.8;
}

/* ---------- Header ---------- */
.header {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 100;
  background-color: var(--color-white);
  border-bottom: 1px solid var(--color-border);
}

.header-inner {
  max-width: var(--container-width);
  margin: 0 auto;
  padding: 0 var(--container-padding);
  height: 72px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.logo img {
  height: 32px;
  width: auto;
}

.nav-list {
  display: flex;
  gap: 36px;
}

.nav-link {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--color-text);
}

/* ---------- Hamburger Menu ---------- */
.menu-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 32px;
  height: 32px;
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px;
}

.menu-toggle-bar {
  display: block;
  width: 100%;
  height: 2px;
  background-color: var(--color-black);
  border-radius: 1px;
}

/* ---------- Hero ---------- */
.hero {
  position: relative;
  height: 100vh;
  min-height: 600px;
  max-height: 1000px;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

.hero-image {
  position: absolute;
  inset: 0;
  z-index: 1;
}

.hero-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.hero-image::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0.15) 0%,
    rgba(0, 0, 0, 0.35) 100%
  );
}

.hero-content {
  position: relative;
  z-index: 2;
  text-align: center;
  color: var(--color-white);
  padding: 0 var(--container-padding);
}

.hero-sub {
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 4px;
  text-transform: uppercase;
  margin-bottom: 20px;
  opacity: 0.9;
}

.hero-title {
  font-size: clamp(28px, 5vw, 52px);
  font-weight: 700;
  line-height: 1.5;
  letter-spacing: 0.06em;
  margin-bottom: 40px;
  text-shadow: 0 2px 20px rgba(0, 0, 0, 0.2);
}

.hero-cta {
  display: inline-block;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 3px;
  text-transform: uppercase;
  color: var(--color-white);
  padding: 12px 36px;
}

/* ---------- Concept ---------- */
.concept {
  background-color: var(--color-bg);
}

.concept-inner {
  max-width: var(--container-width);
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: 60px;
}

.concept-image {
  border-radius: 12px;
  overflow: hidden;
  flex: 1;
}

.concept-text {
  flex: 1;
}

.concept-text p {
  font-size: 15px;
  line-height: 2;
  color: var(--color-text-light);
  margin-bottom: 16px;
}

.concept-text p:last-child {
  margin-bottom: 0;
}

/* ---------- Menu ---------- */
.menu {
  background-color: var(--color-bg-soft);
}

.menu-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 32px;
  max-width: var(--container-width);
  margin: 0 auto;
}

.menu-card {
  background-color: var(--color-white);
  border-radius: 16px;
  overflow: hidden;
}

.menu-card-image {
  aspect-ratio: 1 / 1;
  overflow: hidden;
}

.menu-card-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.menu-card-body {
  padding: 24px;
}

.menu-card-title {
  font-size: 20px;
  font-weight: 700;
  letter-spacing: 1px;
  margin-bottom: 8px;
  color: var(--color-black);
}

.menu-card-desc {
  font-size: 14px;
  line-height: 1.8;
  color: var(--color-text-light);
  margin-bottom: 12px;
}

.menu-card-price {
  font-size: 18px;
  font-weight: 600;
  color: var(--color-accent);
  letter-spacing: 1px;
}

/* ---------- Gallery ---------- */
.gallery {
  background-color: var(--color-bg);
}

.gallery-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: auto auto;
  gap: 16px;
  max-width: var(--container-width);
  margin: 0 auto;
}

.gallery-item {
  border-radius: 12px;
  overflow: hidden;
}

.gallery-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.gallery-item--large {
  grid-row: 1 / 3;
}

/* ---------- Access ---------- */
.access {
  background-color: var(--color-bg-soft);
}

.access-inner {
  max-width: var(--container-width);
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: 60px;
}

.access-image {
  border-radius: 12px;
  overflow: hidden;
  flex: 1;
}

.access-info {
  flex: 1;
}

.info-list {
  margin-top: 12px;
}

.info-item {
  padding: 16px 0;
  border-bottom: 1px solid var(--color-border);
  display: flex;
  gap: 24px;
}

.info-item:last-child {
  border-bottom: none;
}

.info-item dt {
  font-size: 13px;
  font-weight: 500;
  color: var(--color-text-muted);
  letter-spacing: 0.5px;
  margin-bottom: 4px;
  width: 100px;
  flex-shrink: 0;
}

.info-item dd {
  font-size: 15px;
  line-height: 1.8;
  color: var(--color-text);
}

.info-item dd a {
  color: var(--color-accent);
}

/* ---------- Footer ---------- */
.footer {
  background-color: var(--color-black);
  color: var(--color-white);
  padding: 60px var(--container-padding);
}

.footer-inner {
  max-width: var(--container-width);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
}

.footer-logo img {
  height: 24px;
  width: auto;
  filter: brightness(0) invert(1);
  opacity: 0.8;
}

.footer-nav {
  display: flex;
  gap: 28px;
}

.footer-nav a {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.5);
}

.footer-copy {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.35);
  letter-spacing: 0.5px;
}

/* ========================================
   レスポンシブ:タブレット(900px以下)
   ======================================== */
@media (max-width: 900px) {

  :root {
    --section-padding: 80px;
  }

  .nav {
    display: none;
    position: fixed;
    inset: 0;
    background-color: rgba(255, 255, 255, 0.98);
    z-index: 99;
    align-items: center;
    justify-content: center;
  }

  .nav.is-open {
    display: flex;
  }

  .nav-list {
    flex-direction: column;
    align-items: center;
    gap: 32px;
  }

  .nav-link {
    font-size: 20px;
    letter-spacing: 3px;
  }

  .menu-toggle {
    display: flex;
    z-index: 101;
  }

  .concept-inner {
    flex-direction: column;
    gap: 40px;
  }

  .menu-grid {
    gap: 20px;
  }

  .gallery-grid {
    grid-template-columns: 1fr;
  }

  .gallery-item--large {
    grid-row: auto;
  }

  .access-inner {
    flex-direction: column;
    gap: 40px;
  }
}

/* ========================================
   レスポンシブ:モバイル(600px以下)
   ======================================== */
@media (max-width: 600px) {

  :root {
    --section-padding: 60px;
  }

  .hero {
    min-height: 500px;
  }

  .hero-title {
    font-size: 26px;
    letter-spacing: 0.04em;
  }

  .menu-grid {
    grid-template-columns: 1fr;
  }

  .info-item {
    flex-direction: column;
    gap: 4px;
  }

  .info-item dt {
    width: auto;
  }

  .footer-nav {
    flex-direction: column;
    align-items: center;
    gap: 16px;
  }
}

今回のまとめ

  • CSS Gridは display: gridgrid-template-columns で行と列の2次元レイアウトを組める
  • repeat(2, 1fr) で等幅2列、grid-row: 1 / 3 でアイテムを複数行にまたがらせることができる
  • aspect-ratio: 1 / 1 で画像の縦横比を正方形に固定し、object-fit: cover でトリミングする
  • Flexboxは1方向の並び、Gridは2次元の配置。両方を場面に応じて使い分けるのがポイント
  • メディアクエリ @media (max-width: ...) で画面幅に応じてCSSを切り替える。これがレスポンシブデザインの仕組み
  • ブレイクポイントは900px(タブレット)と600px(モバイル)の2段階で、横並びレイアウトを縦積みに切り替えた
  • ハンバーガーメニューのHTMLとCSSを追加した。開閉のJavaScriptは番外編EX2で実装する