模写初級 #004|ブログ一覧(記事カードを並べる)


ブログ一覧ページの模写課題とは、同じ形のカードを何枚も並べるレイアウトを、枚数が変わっても崩れない形で組む練習です。この004をやり切ると、記事カードの並びをCSS Gridの repeat() で書き、カードを1枚に減らしても10枚に増やしても破綻しない状態を自分で作れるようになります。

001から003までは「1つしかないもの」を作る課題でした。004で初めて「同じものが何個も並ぶ」が出てきます。実務のブログ一覧は記事が3件のことも12件のこともあり、その数はあとから変わります。だからこの課題の合否は、見た目の一致ではなく枚数を変えても壊れないかで判定します。

項目内容
難易度初級
所要時間目安2〜3時間(実装量から見積もった目安で、計測値ではありません)
使う技術HTML / CSS / CSS Grid / object-fit / メディアクエリ
模写対象Daily Notes(記事カードが並ぶブログ一覧ページ)

差分チェックツールで効率UPお手本コードと自分のコードを比較して、違いを一目で確認できます。練習前にブックマークしておくと便利です。
Diff Checkerを開く →

この課題で模写するページ

模写サンプルサイト  beginner004 / Daily Notes

ヘッダー(ロゴ+ナビ)/ヒーロー/記事一覧/カテゴリー一覧/フッターの5ブロックでできたブログのトップページです。記事一覧には同じ形のカードが6枚並び、1枚は「サムネイル・カテゴリーラベル・タイトル・抜粋文・投稿日」でできています。

サンプルのCSSを読むと、記事一覧の段組みはスマホ1列 →(600px以上)2列 →(900px以上)3列で切り替わっていました。ブレークポイントを自分で決めるときの目安にしてください。

ひとつだけサンプルが割り切っている箇所があります。グローバルナビは900px未満だと display: none で消えるだけで、代わりのメニューがありません。そのまま再現して構いませんが、ここを直すのが一番おいしい加点になります。


カードの枚数が変わっても崩れない並べ方

この課題の核はここです。以下の数値は、幅960pxの画面・コンテナ最大幅960px(左右padding 16px)・gap: 32px の条件で実際に描画して測りました(Chromium・2026-08-02実測)。

列数を直接書く方法(サンプルの方式)

サンプルは grid-template-columns: repeat(2, 1fr) のように列数をそのまま書き、メディアクエリで3回切り替えています。読みやすく、初級の答えとしては十分です。

注意点は、メディアクエリを1つでも書き忘れると即座に破綻することです。repeat(3, 1fr) だけを書いて切り替えを用意しなかった状態を320px幅で測ると、カード1枚の幅は74.7pxになりました。横スクロールは出ませんが、文字も画像も潰れて読めません。

列数をブラウザに決めさせる方法(auto-fill / auto-fit)

repeat() の第1引数には数値のほかに auto-fillauto-fit が書けます。MDNは auto-fill を「制約のある(最大サイズを持つ)コンテンツボックスをはみ出さない範囲で、最大の繰り返し回数に解決される」と定義し、auto-fit は「auto-fill と同じだが、グリッドアイテムを配置したあと、空の繰り返しトラックが折りたたまれる」と説明しています。

.posts-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 32px;
}

この4行だけで、320px幅では1列、960px幅では3列になりました。メディアクエリは1行も書いていませんminmax() は「min以上max以下のサイズ範囲を定義する関数」(MDN)なので、ここでは「1枚あたり最低280px、余りは等分」という意味になります。本題の「枚数を変えたとき」の実測結果が次の表です(960px幅)。

書き方カード6枚カード1枚
repeat(3, 1fr)288px×3列・2行288px(右3分の2が空く)
auto-fill, minmax(280px, 1fr)288px×3列・2行288px(右3分の2が空く)
auto-fit, minmax(280px, 1fr)288px×3列・2行928px(全幅に伸びる)

差が出るのは列が1本まるごと空になったときだけです。カード4枚(3列+2行目に1枚)では両者の結果が完全に一致しました。2行目に1枚だけ残る状態は、どの列も「どこかの行では使われている」ため折りたたみが起きません。1件のときに全幅へ伸ばしたくないなら auto-fill です。

minmax の最小値を大きくしすぎると横スクロールが出る

minmax() の最小値は「これ以上は縮まない」下限なので、画面がその値より狭いとカードがはみ出します。320px幅(内寸288px)での実測です。

最小値の書き方カード幅横スクロール
minmax(280px, 1fr)288px出ない
minmax(320px, 1fr)320px出る(内容幅336px)
minmax(min(320px, 100%), 1fr)288px出ない

直し方は、最小値を下げるか min(320px, 100%)「使える幅を超えない」上限をかぶせるかです。後者なら、あとから最小値を大きくしても狭い画面で溢れません。

出典:MDN Web Docs「repeat()」MDN「minmax()」(いずれも英語版・2026-08-02取得)


カード1枚のHTMLを意味のある形にする

MDNは <article> を「自己完結した構成物で、単独で配信したり再利用したりできることを意図したもの」と定義し、例として「ブログの投稿」と「商品カード」を挙げています。記事カードはこれに当てはまります。

模写サンプルは <li> だけで組んでいて <article> は使っていません。間違いではありませんが、<li> の中に <article> を置けば「一覧の項目である」と「1件の記事である」を両方表せます。MDNは「それぞれの <article> は通常、子要素として見出しを含めることで識別されるべき」とも書いています。カード内のタイトルは必ず見出しタグにしてください。

<ul class="posts-list">
  <li>
    <article class="post-card">
      <figure class="post-card-thumbnail">...</figure>
      <div class="post-card-body">
        <span class="post-card-category">...</span>
        <h3><a href="...">記事タイトル</a></h3>
        <p class="post-card-excerpt">...</p>
        <time datetime="2024-01-15">2024.01.15</time>
      </div>
    </article>
  </li>
  <!-- 以下、同じ形を必要な枚数ぶん繰り返す -->
</ul>

日付は time の datetime に機械可読で書く

画面に出す文字は「2024.01.15」でも「2024年1月15日」でも構いません。ただし datetime 属性は決められた書式で書く必要があります。MDNも「記事の公開日時は <time> 要素の datetime 属性で記述できる」と明記しています。

表したいもの書式
日付YYYY-MM-DD2024-01-15
年月YYYY-MM2024-01
日付と時刻YYYY-MM-DDTHH:MM2024-01-15T11:12

気をつけたいのは、書式を間違えてもブラウザは何も教えてくれないことです。datetime="2024.01.12"datetime="2024/01/10" と書いたページで確かめたところ、エラーも警告も出ず、.dateTime の値も書いた文字列がそのまま返りました。完成条件でまとめて確認します。

サムネイルの alt には何を書くか

模写サンプルの6枚のサムネイルはすべて空です。手抜きに見えて、これが正しい書き方です。MDNは「この属性を空文字列にすると、その画像がコンテンツの重要な部分ではない(装飾やトラッキングピクセルである)ことを示す」と説明しています。判断はこの1点で決まります。

  • 画像を消しても意味が伝わる(隣のタイトルが同じことを言っている)→ 空にする
  • 画像を消すと意味が失われる(グラフ・図解・画像自体が主題)→ 何が写っているかを書く

やってはいけないのは「サムネイル」「画像」「photo-1517694712202.jpg」のように、読み上げても何の役にも立たない文字を入れることです。なおカードの高さを揃えているのは figureaspect-ratio: 16 / 10object-fit: cover で、MDNによれば cover は「縦横比を保ったまま要素のコンテンツボックス全体を埋め、比率が合わなければはみ出した部分が切り取られる」値です。

出典:MDN「<article>」MDN「<time>」MDN「<img>」MDN「object-fit」(いずれも英語版・2026-08-02取得)


カード全体をクリックできるようにする代償

模写サンプルはカード全体を1つの <a> で囲む方式です。押せる面積が広くて使いやすいのですが、代償があります。3つのやり方を同じカードで測りました(Chromium・2026-08-02実測)。

やり方本文をドラッグしたときリンクの読み上げ名
カード全体を <a> で囲む0文字しか選択できず、代わりにリンクが発火81文字(カテゴリ+タイトル+抜粋+日付)
タイトルの a::after をカード全面に広げる0文字しか選択できず、代わりにリンクが発火19文字(タイトルのみ)
タイトルだけリンクにする27文字を選択できる(リンクは発火しない)19文字(タイトルのみ)

読み取れることは2つです。1つ目はクリック領域を広げると必ずテキスト選択を失うこと。擬似要素方式でも透明な板がカード全面をふさぐので結果は同じでした。2つ目はカード全体を <a> で囲むとリンクの名前がカードの文字すべてになることです。MDNはスクリーンリーダーのリンク一覧機能に触れたうえで「リンクの中身は、文脈から切り離しても、そのリンクがどこへ行くのかを示すべき」と書いています。

カードの下に「続きを読む」だけを置く形も見かけます。この文言単体では行き先が分かりませんが、達成基準2.4.4(レベルA)には適合しえます。W3Cの規定は「リンクの目的が、リンクテキストだけから、またはリンクテキストとプログラム的に判断できるリンクのコンテキストから判断できること」で、この「コンテキスト」にはHTMLでは「同じ段落・リスト項目・表のセルにあるテキスト」が含まれるからです。ただし、より厳しい基準2.4.9(レベルAAA)はリンクテキストだけでの判別を求め、失敗例F84は「『click here』や『more』のような具体性のないリンクを、具体的にする手段なしに使うこと」を失敗としています。記事タイトルそのものをリンクにするのが、どちらの基準でも安全です。

この課題では、まずサンプルどおりカード全体を <a> で囲んで再現してください。そのうえで擬似要素方式に書き換えると、同じ見た目・同じ操作感のまま読み上げ名だけが短くなることを確認できます。

出典:MDN「<a>」W3C「Understanding SC 2.4.4」W3C「F84」(いずれも英語版・2026-08-02取得)


進め方

カードを6枚いきなり書き始めると、直しが6倍になります。次の順番を守ってください。

ステップ1:カード1枚だけを完成させる

ヘッダーもヒーローも後回しにして、カード1枚だけを置きます。縦の並びと余白、枠線、角丸をここで決め切ってください。この1枚が完成形です。並べたあとに中身を直すと、同じ修正を6回することになります。

ステップ2:コピーして並べる

完成した1枚を丸ごとコピーして6枚に増やし、親の uldisplay: gridgrid-template-columnsgap を与えます。カード側には幅を書かないでください。幅は列が決めます。widthfloat を足すと、次のステップで必ず矛盾します。

ステップ3:ヘッダー・ヒーロー・フッターを足す

最後に前後のブロックを足します。ヘッダーはロゴとナビの横並び・上下中央揃え・項目間の gap の3点で決まり、003までと同じです。ヒーローとフッターは中央寄せの箱なので、上下 padding だけ合わせれば形になります。


完成条件(ここまでできたら合格)

スクリーンショットの一致では判定しません。ブラウザを触って、次の5つがすべて再現できたら合格です。外れたときに疑う場所も挙げてあります。

  1. カードを1枚だけに減らしても、3枚追加して9枚にしても、レイアウトが崩れない。HTMLを削る/貼り足すだけで確認する。1枚のとき画面いっぱいに伸びるなら auto-fit なので auto-fill に替える。9枚のときだけ形が変わるなら、カード側に width や高さの固定値が残っている
  2. ブラウザ幅を320pxまで縮めても横スクロールバーが出ない。出るなら、minmax() の最小値が画面の内寸より大きくないかをまず見る
  3. すべてのカードに、正しい書式の日付が入っている。開発者ツールのコンソールで [...document.querySelectorAll('time[datetime]')].filter(t => !/^\d{4}-\d{2}-\d{2}$/.test(t.dateTime)).length を実行して 0 が返ればOK。1以上なら、その数だけ datetime をハイフン区切りに直す
  4. リンクの名前だけを見て、どの記事へ行くか分かる。コンソールで [...document.querySelectorAll('.posts-list a')].map(a => a.textContent.trim().length) を実行し、数字が記事タイトルの長さ程度なら OK。極端に大きい数字が並ぶならカード全体を <a> で囲んだ状態なので、擬似要素方式へ替える
  5. サムネイルの alt が、空か、画像の内容を説明しているかのどちらかになっている。「サムネイル」のような中身のない文字列が1つでもあれば不合格。属性そのものが無い場合は、装飾なら空の alt を明示する

加点は2つです。メディアクエリを1行も書かずに repeat(auto-fill, minmax(...)) だけで段組みを成立させること、900px未満で消えるグローバルナビに代わりの導線を用意すること。どちらもサンプルより一歩進んだ実装になります。


つまずきポイント

症状原因直し方
スマホでカードが極端に細くなる切り替えを書かず repeat(3, 1fr) のまま(320px幅で1枚74.7pxを実測)メディアクエリで1列に落とすか auto-fill に替える
320pxで横スクロールが出るminmax() の最小値が画面の内寸より大きい最小値を下げるか min(320px, 100%) をかぶせる
カード1枚のとき画面いっぱいに伸びるauto-fit が空のトラックを折りたたんでいる伸ばしたくないなら auto-fill にする
カードの高さがバラバラになる抜粋文の行数が記事ごとに違うサムネイルに aspect-ratio、抜粋に行数の上限を与える
本文をドラッグしてコピーできないクリック領域をカード全体に広げている(実測で選択0文字)仕様として受け入れるか、リンクをタイトルだけに戻す
カード同士の間隔が場所によって違うカード側の margingap が混ざっている間隔は gap だけで作り、カードの margin を消す

次の課題と関連記事

1つ前の課題は 模写初級 #003(LPレイアウト) です。セクションを縦に積む段階でつまずいた人は、先にこちらを終わらせてください。

クリアしたら、次は 模写初級 #005(アンカーリンク) へ進みます。004で身につけた「同じ部品を繰り返す」感覚が、ナビと本文セクションを対応づける作業にそのまま効いてきます。

他のレベルの課題も見たい場合は 模写コーディング課題の一覧 からどうぞ。初級から上級、Figma模写までまとめてあります。

カードの見た目を作り込みたくなったら、影・角丸・ホバーの動きなどをまとめた CSSテクニック集 が参考になります。この課題で作った骨格に後から足していくのがおすすめです。


よくある質問(FAQ)

Q. カードの枚数が変わってもレイアウトを崩さないには、どう書けばよいですか?

親要素に display: gridgrid-template-columns: repeat(auto-fill, minmax(280px, 1fr)) を書き、カード側には幅を書かないことです。列数はブラウザが画面幅から決めるので、HTMLを貼り足す/削るだけで済みます。960px幅の実測では、カード6枚でも1枚でも幅は288pxで一定でした。

Q. auto-fill と auto-fit はどちらを使えばよいですか?

ブログ一覧なら auto-fill が無難です。MDNによると auto-fit は「グリッドアイテムを配置したあと、空の繰り返しトラックを折りたたむ」点だけが違います。実測では960px幅でカード6枚なら両者とも288px×3列で同じ、カード1枚のときだけ auto-fit は928pxまで伸び、auto-fill は288pxのままでした。

Q. datetime の書式を間違えたら、ブラウザが教えてくれますか?

教えてくれません。datetime="2024.01.12"datetime="2024/01/10" と書いたページを実際に開いたところ、エラーも警告も出ず、.dateTime の値も書いた文字列がそのまま返りました。表示も変わらないため目視では気づけません。日付は YYYY-MM-DD で書き、完成条件のコンソール確認で洗い出してください。

Q. カード全体をクリックできるようにしても大丈夫ですか?

問題ありませんが、2つの代償があります。実測では、カード全体を <a> で囲んだ場合も擬似要素で領域を広げた場合も、本文をドラッグすると1文字も選択できずリンクが発火しました。さらにカード全体を <a> で囲むと、リンクの読み上げ名がカードの文字すべて(実測81文字)になります。名前を短く保ちたいなら、リンクはタイトルに置いて a::after で領域だけ広げます。

Q. リンクを「続きを読む」だけにするのは避けるべきですか?

避けたほうが安全です。達成基準2.4.4(レベルA)は「リンクテキストだけから、またはリンクテキストとプログラム的に判断できるリンクのコンテキストから」目的が分かればよいとしており、同じリスト項目内のタイトルが文脈になるため、カード内の「続きを読む」はレベルAには適合しえます。ただし基準2.4.9(レベルAAA)はリンクテキストだけでの判別を求め、失敗例F84は「click here」や「more」を具体化する手段なしに使うことを失敗としています。記事タイトル自体をリンクにするのが確実です。

Q. サムネイル画像の alt には何を書けばよいですか?

記事タイトルがすぐ隣にあるカードのサムネイルなら、空にするのが正解です。MDNは、この属性を空文字列にすることは「その画像がコンテンツの重要な部分ではない(装飾やトラッキングピクセルである)ことを示す」と説明しており、模写サンプルの6枚もすべて空です。逆にグラフや図解のように画像自体が情報を持つ場合は内容を書き、「サムネイル」のような中身のない文字列だけは入れないでください。