CSS講座 / HTMLとCSSでレスポンシブなWebサイトを作る

HTMLとCSSの基礎を組み合わせると、意味のある文書構造、可変幅、Flexboxのナビゲーション、Gridのカードを持つ小さなサイトを作れます。まず狭い画面でも読んで操作できる形を作り、広い画面の調整と状態表示を追加します。

必要な内容と完成条件を決める

一つの主見出し、導入、三枚のカード、フッターを読む順番で置きます。狭い画面はメニューを折り返して一列、幅が増えれば二・三列にし、広い画面でも本文の最大幅を保ちます。

キーボードで本文ショートカットと各リンクへ移動でき、フォーカスが見えることも完成条件です。長文や空白のない文字列を入れても固定高さで切らず、動きを減らした設定でも内容と移動を維持します。

これは静的な画面で、カード自体にはリンクがありません。ナビと大きなリンクは同じ文書内の節へ移ります。実際の講座一覧へ拡張するなら、確認した投稿URLを見出しへ設定します。未使用フォームや無限アニメーションを足す必要はありません。

ファイルと意味のあるHTMLを用意する

空のフォルダーの同じ場所へindex.htmlとstyle.cssを作り、UTF-8で保存します。拡張子が隠れてindex.html.txtになっていないか確認します。インストールやビルドは不要です。

learning-site/
  index.html
  style.css

次のHTML全体をindex.htmlへ保存します。CSSが空でも内容の順番は読める構成です。headerは先頭、navは移動、mainは主内容、footerは末尾の領域です。独立した実習ファイルなのでh1を使い、その下にh2、カードのh3を置いています。WordPress本文へ直接貼る原稿の構造とは別です。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>A Small Learning Site</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <a class="skip-link" href="#main">Skip to main content</a>
  <header class="site-header">
    <div class="container header-inner">
      <p class="site-name">Learning Notes</p>
      <nav aria-label="Main navigation">
        <a href="#intro">About</a><a href="#lessons">Lessons</a>
      </nav>
    </div>
  </header>
  <main id="main" tabindex="-1">
    <section id="intro" class="hero" aria-labelledby="intro-title">
      <div class="container">
        <h1 id="intro-title">Learn to Build Webpages Step by Step</h1>
        <p>Structure content with HTML and adapt its appearance with CSS.</p>
        <a class="button" href="#lessons">Explore the lessons</a>
      </div>
    </section>
    <section id="lessons" class="lessons container" aria-labelledby="lessons-title">
      <h2 id="lessons-title">Learning topics</h2>
      <ul class="card-grid" role="list">
        <li><article class="card"><h3>HTML structure</h3><p>Organize content with headings and paragraphs.</p></article></li>
        <li><article class="card"><h3>CSS styles</h3><p>Adjust colors, fonts, and spacing.</p></article></li>
        <li><article class="card"><h3>Responsive layout</h3><p>Arrange cards to fit the available width.</p></article></li>
      </ul>
    </section>
  </main>
  <footer class="site-footer"><div class="container"><p>Small learning site exercise</p></div></footer>
</body>
</html>

本文ショートカットはmainのidへつながります。tabindex="-1"は通常のTab順へ加えずにmainがフォーカスを受けられるようにします。Enterで移動した後、本文から操作を続けられるか確認します。W3Cの本文スキップリンクを参照してください。

共通値と本文幅を設定する

style.cssを次の部分から始めます。色は役割で名付け、border-boxで余白と枠を指定幅へ含めます。固定寸法を増やすより、内容が増えても収まる基本形を作ります。

/* Shared values and base styles */
:root {
  --ink: #183044;
  --accent: #166b92;
  --surface: #eef6fa;
  --line: #c9dbe4;
  --focus: #164f73;
}
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body { margin: 0; color: var(--ink); font-family: sans-serif; line-height: 1.6; }
a { color: var(--accent); text-underline-offset: .15em; }
:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; }
.container { width: min(100% - 2rem, 68rem); margin-inline: auto; }
.container, .card { overflow-wrap: anywhere; }

containerの幅は、使える幅から左右合計2remを引いたものと68remの小さい方です。狭い画面の外側余白と広い画面の上限を両立し、margin-inline: autoで中央へ置きます。anywhereは長い識別子の途中で必要な折り返しを作ります。

この段階では色、文字、本文幅が適用されますが、詳細な配置はまだありません。全く変わらなければ先へ足す前に接続パスを確認します。変数、可変幅を参照してください。

Flexboxナビとフォーカス経路を作る

共通ルールの下へ追加します。header-innerはサイト名とメニューの関係、navはリンク同士の間隔を担当します。間隔の責任を分けると、全体を変えたときにリンク間も不用意に変わることを減らせます。

/* Navigation and header */
.skip-link { position: absolute; left: 1rem; top: -6rem; padding: .5rem; background: white; }
.skip-link:focus { top: 1rem; z-index: 10; }
.site-header { border-bottom: 1px solid var(--line); }
.header-inner { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: .5rem 1.5rem; padding-block: 1rem; }
.site-name { margin: 0; font-weight: 700; }
nav { display: flex; flex-wrap: wrap; gap: 1rem; }

flex-wrapで幅が足りないとき次の行へ回せます。二行になること自体は失敗ではなく、切れ、重なり、操作不能がないかが基準です。意味のある順序をorderなどで見た目だけ逆転しません。

本文ショートカットは通常画面外、フォーカス時に見える設計です。display: noneだとTabでも入れないため同じ目的になりません。最初のTabで表示し、Enterで移動して、次のTabが導入の大きなリンクへ進むか見ます。Flexboxの基礎を参照してください。

導入の可変文字とリンクの状態を整える

続けて追加し、導入の背景、見出し、大きなリンクを整えます。clampは最小・推奨・最大を決めますが、実際の文字拡大で読めるかは別に確認します。

/* Introduction and navigation link */
.hero { padding-block: clamp(3rem, 8vw, 6rem); background: var(--surface); }
.hero h1 { max-width: 18ch; margin-block: 0 1rem;
  font-size: clamp(1.8rem, 1.2rem + 2vw, 3rem); line-height: 1.25; }
.hero p { max-width: 50ch; }
.button { display: inline-block; margin-top: .5rem; padding: .7rem 1rem;
  border-radius: .4rem; background: var(--accent); color: white; text-decoration: none;
  transition: background-color 180ms ease; }
.button:hover { background: #104d6a; }

18chはフォントの数字0の幅を基準にし、一行18文字を保証するものではありません。見出しのline-heightで複数行を調整し、導入文は50chへ制限します。外部フォントは取得せず、システムのサンセリフを使います。

大きなリンクは同一文書の講座節へ移るのでaです。移動せず状態を変える操作ならbuttonを検討します。ホバーは背景色だけの遷移にし、共通outlineでフォーカスも分かるようにします。

内容が増えても伸びるGridカード

次を追加してカードとフッターを整えます。箇条書きの点を消してもulとliの構造は残します。nav外のlist-style: noneはSafariでリストとして認識されない場合があるため、例はrole="list"も指定しています。MDNのリストのアクセシビリティを参照してください。

/* Cards and footer */
.lessons { padding-block: 3rem; }
.lessons h2 { margin-block: 0 1.5rem; }
.card-grid { display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  gap: 1rem; margin: 0; padding: 0; list-style: none; }
.card-grid > li { min-width: 0; }
.card { height: 100%; padding: 1.5rem; border: 1px solid var(--line); border-radius: .5rem; }
.card h3 { margin-block: 0 .75rem; }
.card p { margin-block: 0; }
.site-footer { padding-block: 2rem; border-top: 1px solid var(--line); }
.site-footer p { margin: 0; }

auto-fitが入る繰り返し列を作り、minmaxで最小と伸びる範囲を指定します。min(100%, 16rem)なら親が16remより狭くてもそこへ縮められます。三枚しかないので広い画面で四枚目が生成されるわけではありません。MDNのrepeat、minmaxを参照してください。

ルート16pxでページ幅とビューポートが等しい条件なら、カード最低256px、間隔16px、左右合計32pxで二列は560px、三列は832pxから入ります。親幅、文字、スクロールバーの条件で変わるため端末名では覚えません。

カードのheight: 100%は同じ行のliの高さを埋めるもので、固定ピクセル高ではありません。文章が増えれば行も伸びます。liのmin-width: 0と文字の折り返しで長い単語にも対応します。自動列のGridも参照してください。

広い画面と動きを減らす条件を加える

style.css末尾へ追加します。64rem以上で導入背景をグラデーションへ変えます。カードの列数はGridが独立して決め、このメディアクエリーでは指定していません。

/* Conditional adjustments */
@media (min-width: 64rem) {
  .hero { background: linear-gradient(100deg, var(--surface), #fff); }
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .button { transition: none; }
}

動きを減らす条件ではスムーズスクロールを瞬時の移動へ戻し、色の遷移も外します。リンクの目的と内容、フォーカスは維持します。条件付きCSS、表示と操作の点検を参照してください。

全体のスタイルシートで動かす

次は段階的に追加したルールをまとめたstyle.cssの全体です。順番の確認や、最初からやり直すときに使えます。同じフォルダーのindex.htmlを開き、保存後に再読み込みします。

/* Shared values and base styles */
:root {
  --ink: #183044;
  --accent: #166b92;
  --surface: #eef6fa;
  --line: #c9dbe4;
  --focus: #164f73;
}
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body { margin: 0; color: var(--ink); font-family: sans-serif; line-height: 1.6; }
a { color: var(--accent); text-underline-offset: .15em; }
:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; }
.container { width: min(100% - 2rem, 68rem); margin-inline: auto; }
.container, .card { overflow-wrap: anywhere; }

/* Navigation and header */
.skip-link { position: absolute; left: 1rem; top: -6rem; padding: .5rem; background: white; }
.skip-link:focus { top: 1rem; z-index: 10; }
.site-header { border-bottom: 1px solid var(--line); }
.header-inner { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: .5rem 1.5rem; padding-block: 1rem; }
.site-name { margin: 0; font-weight: 700; }
nav { display: flex; flex-wrap: wrap; gap: 1rem; }

/* Introduction and navigation link */
.hero { padding-block: clamp(3rem, 8vw, 6rem); background: var(--surface); }
.hero h1 { max-width: 18ch; margin-block: 0 1rem;
  font-size: clamp(1.8rem, 1.2rem + 2vw, 3rem); line-height: 1.25; }
.hero p { max-width: 50ch; }
.button { display: inline-block; margin-top: .5rem; padding: .7rem 1rem;
  border-radius: .4rem; background: var(--accent); color: white; text-decoration: none;
  transition: background-color 180ms ease; }
.button:hover { background: #104d6a; }

/* Cards and footer */
.lessons { padding-block: 3rem; }
.lessons h2 { margin-block: 0 1.5rem; }
.card-grid { display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  gap: 1rem; margin: 0; padding: 0; list-style: none; }
.card-grid > li { min-width: 0; }
.card { height: 100%; padding: 1.5rem; border: 1px solid var(--line); border-radius: .5rem; }
.card h3 { margin-block: 0 .75rem; }
.card p { margin-block: 0; }
.site-footer { padding-block: 2rem; border-top: 1px solid var(--line); }
.site-footer p { margin: 0; }

/* Conditional adjustments */
@media (min-width: 64rem) {
  .hero { background: linear-gradient(100deg, var(--surface), #fff); }
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .button { transition: none; }
}

値の変更と不具合の切り分け

  1. 390px程度から広げ、一列から二・三列へ変わるか確認します。先の計算と同じ条件なら559・560px、831・832pxも比べます。
  2. card-gridの16remだけを20remへ変え、列が増えるのに必要な幅を比べて16remへ戻します。
  3. containerの68remを50remへ変え、広い画面の本文上限を見て68remへ戻します。
  4. accentだけを別の十分暗い色へ変え、リンク背景と白い文字、フォーカスを確認して戻します。
  5. 説明を数文へ伸ばし、高さが増えても切れないか見ます。空白のない英字列も追加し、anywhereを外した差を見てルールと文章を復元します。
  6. TabとShift+Tab、各リンクのEnterで移動します。動きを減らす条件では移動は維持し、スムーズな効果だけがなくなるかを確認して再現設定を戻します。

ページ拡大と既定文字サイズの変更でも、ナビ、カード、文章、フォーカスを確認します。開発者ツールでhtmlの文字だけを大きくする実験とブラウザー拡大は別に記録します。一条件の成功だけで全条件を確認したとは扱いません。

全く装飾されないなら保存・拡張子・link、横スクロールなら長い単語・最小幅・親幅・border-box、フォーカスがないならoutlineの上書きや切り取りを調べます。行ごとの高さは同じである必要はありません。bodyのhiddenで隠す前に、原因の要素の寸法と内容を直し、元の失敗条件で再確認します。