GODRICH
#cssアニメーション#ボタン#ローディング#トランジション#スクロール#ダッシュボード#実用ui#デザイントレンド

css カード デザイン 9種 — 街・チーム・ショートカット

css カード デザインとは、画面に内容を載せて並べるための最も基本的な容器を組む仕事です。この記事の原則は、クリックをカード全体ではなくリンクに任せることです。街紹介・チーム・ショートカット・自己紹介タブ・レビュー・スペック・イベント・イントロ・ブログ一覧まで、9つのカードをライブラリなしで組み、下のグリッドが今すぐ

自動再生 · タイルを押すとその項目へ · すべて1つのzip

並びは人気順ではなく、内容がカードになっていく順です。1枚が複数枚になり(01, 02)、カードが案内板の役を務め(03)、1枚の中で本文が分かれ(04, 05, 06)、カードに雰囲気が乗り(07)、カードがひとまとまりに組み上がり(08)、最後に多いカードが読む順番まで守ります(09)。この記事全体の軸は派手な効果ではなくレイアウトとリンクです — カード全体ではなくリンクにクリックを任せる原則は、9つすべてに通っています。同じ流れの隣としてCSS ボタン デザインとタブメニュー 作り方があります。

01街紹介グリッドカード

街の名前と一行紹介を載せたカードが grid-template-columns で並び、それぞれのラベル帯が自分の順番で上へせり上がる、旅行・地域サービスの紹介セクション向けカードです。

街紹介グリッドカード
新しいタブで開く

01 · 街紹介グリッドカード — 計測 累積面積 34.122% · 動いたフレーム 20/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

grid-template-columnsanimation-delayanimation-fill-mode

3つの枠はグリッドが決め、登場は順番が決める

.cg {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: $sp-2;
  box-sizing: border-box; width: min(100%, 440px);
  margin: 0; padding: 0; list-style: none;
}

カード1枚ずつに幅を与えるのではなく枠を先に3つ決めているので、4枚に増やすときは repeat(3, 1fr) を repeat(4, 1fr) に変えるだけで、カードの中身には触れずに済みます。

遅延の付いた登場には fill-mode を添える

// 遅延の間も上昇前の状態を見せる — なければ0フレーム目で完成形が一瞬光る
.cg.is-demo .cg__card { animation-fill-mode: backwards; }
.cg.is-demo .cg__card:nth-child(2) { animation-delay: .12s; }
.cg.is-demo .cg__card:nth-child(3) { animation-delay: .24s; }

animation-delay で時間差を与えたカードは、遅延の間も「上がる前の状態」を見せるのが自然で、backwards がなければ完成形がそのまま見えた後に消えて、また上がってきます。

02チームメンバーカード

名前と肩書きの下に隠れた連絡先の行が、カードにフォーカスが移ると clip-path で開き、リンクには本人の名前がそのまま入っている、会社紹介のチームセクション向けカードです。

チームメンバーカード
新しいタブで開く

02 · チームメンバーカード — 計測 累積面積 1.531% · 動いたフレーム 13/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

clip-pathfocus-withintransition

場所は最初から確保し、見える範囲だけを変える

.tm__contact {
  display: flex; align-items: center; gap: $sp-1;
  width: 100%; margin: 0; padding-top: $sp-1;
  border-top: 1px solid $edge;
  clip-path: inset(0 0 100% 0);
  transition: clip-path $duration $easing;
}
.tm__card:hover .tm__contact,
.tm__card:focus-within .tm__contact { clip-path: inset(0 0 0 0); }

連絡先の行は最初から高さを占めたまま clip-path で下側だけ隠しているので、開いても隣のカードは押されず、再び隠れるときもレイアウトが計算し直されることはありません。

開く条件はホバーとフォーカスの両方

:focus-within を併せて設定してあるので、マウスのないキーボード利用者がリンクに触れても同じ状態が開きます — ホバー用の操作には必ずフォーカス版を対にするのが、この記事の決まりです。

03ショートカットタイルカード

正方形のタイル4枚が aspect-ratio で形を保ち、矢印が角へ滑って行き先を示す、ホーム画面のクイックメニュー向けカードです。

ショートカットタイルカード
新しいタブで開く

03 · ショートカットタイルカード — 計測 累積面積 1.637% · 動いたフレーム 19/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

aspect-ratiotranslategrid

正方形は高さではなく比率で

.st__tile {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-rows: auto 1fr auto;
  aspect-ratio: 1 / 1;
  box-sizing: border-box;
  padding: $sp-2;
  border-radius: $r-card;
  background: $subject-cream;
}

高さを px でなく aspect-ratio: 1 / 1 で固定しているので、4列が狭い画面で2列に変わってもタイルは常に正方形のままで、伸びるのはラベルの折り返しだけです。

矢印は translate プロパティで押す

.st__go {
  translate: 0 0;
  transition: translate $duration $easing;
}
.st__tile:hover .st__go,
.st__tile:focus-within .st__go { translate: $reach (-$reach); }

transform の一括指定ではなく独立プロパティの translate を使っているので、後から別の効果が scale や rotate を足しても上書きし合わず、transition も1つのプロパティだけを正確に見ます。

04タブで切り替える自己紹介カード

紹介・経歴・連絡の3つのタブを押すと本文が1フレームで入れ替わり、下線はラジオ入力だけでそのタブへ滑る、個人の自己紹介ページ向けカードです。

タブで切り替える自己紹介カード
新しいタブで開く

04 · タブで切り替える自己紹介カード — 計測 累積面積 5.933% · 動いたフレーム 8/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

steps(1, end)hastranslate

状態はラジオが持ち、カードが :has() で読む

.ab__ink {
  position: absolute; left: 0; bottom: -1px;
  width: $tab-w; height: 3px; border-radius: $r-pill;
  background: $color;
  translate: 0 0;
  transition: translate $duration $easing;
}
.ab:has(#abT2:checked) .ab__ink { translate: $step 0; }
.ab:has(#abT3:checked) .ab__ink { translate: $step * 2 0; }

ラジオが状態を保存し、カードが :has() でその状態を読んで下線を動かし本文を替える、JS なしで両者が同じ値に従う仕組みです。

下線の幅は実測した枠の幅と同じ

下線は固定 px で動くので各タブは固定幅の枠(flex: 0 0 auto, 72px)で、幅は getBoundingClientRect で実測していて、言語ごとにラベルが長くなっても位置はずれません。

05ピンボード型レビューカード

長さがまちまちのレビューカードが column-count で流し込まれ、1枚ずつ収まって隙間のない壁を作る、レビュー一覧ページ向けカードです。

ピンボード型レビューカード
新しいタブで開く

05 · ピンボード型レビューカード — 計測 累積面積 28.106% · 動いたフレーム 12/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

column-countbreak-insideanimation-delay

段割りでカードが切れない1行

.pin {
  box-sizing: border-box; width: 100%;
  column-count: 3;
  column-gap: $sp-3;
}
.pin__card {
  // カードが段の境界で切れないように — 段組みの要になる1行
  break-inside: avoid;
  box-sizing: border-box;
  margin-bottom: $sp-3;
  padding: $sp-2;
  border-left: 3px solid $color;
  background: $card-bg;
}

段組みは行を段から段へ流す組み方なので、何もしなければレビューカードが2つの段にまたがって下端で半分に切れますが、break-inside: avoid の1行で「この箱は割るな」と固定すると、カードごと次の段へ丸ごと移ります。

隙間なしは段の中の話

隙間のない壁とは段の中でカードの間に隙間がないという意味で、段の下側は最後のカードの長さに応じて自然に余ります — 段組みで段の下端まで無理に埋めるとカードが伸びたり切れたりするので、端まで埋めたい場面には 09 のグリッド配置が正しい道具です。

06スペックが伸びる商品カード

商品カードが grid-template-rows の 0fr から 1fr へ開き、隠れていたスペック表が下りてきて、隣のカードは動かない、商品比較リスト向けカードです。

スペックが伸びる商品カード
新しいタブで開く

06 · スペックが伸びる商品カード — 計測 累積面積 5.152% · 動いたフレーム 9/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

grid-template-rowsminmaxoverflow

max-height の代わりに枠そのものを伸ばす

.sg__grow {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows $duration $easing;
}
// 0fr の枠を実際に折りたたむ2行 — これがないと表がそのまま見える
.sg__inner { overflow: hidden; min-height: 0; }
.sg__card.is-open .sg__grow { grid-template-rows: 1fr; }

max-height で開閉すると毎回「十分に大きな値」を推測しなければならず、内容が長くなると途中で切れますが、0fr → 1fr の遷移は内容の実際の高さまでしか伸びないので値の推測が要りません。

折りたたむ2行と、隣が動かない理由

0fr の枠の子要素は自分の大きさを持ったままなので、overflow: hidden + min-height: 0 の2行がなければ折りたたんでも表ははみ出したままです — さらに外側のグリッドが align-items: start でないと、1枚が伸びた瞬間に同じ行の残りのカードまで一緒に高くなります。

07雰囲気を先に見せるイベントカード

日付の数字がカードの片側を大きく占め、タイトル行が下からせり上がり、色帯がゆっくり傾いて催しの雰囲気を作る、公演・展示の日程一覧向けカードです。

雰囲気を先に見せるイベントカード
新しいタブで開く

07 · 雰囲気を先に見せるイベントカード — 計測 累積面積 7.618% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

rotatemix-blend-modeanimation-delay

帯は画像ではなく掛け算された色

.em__card {
  position: relative;
  // カードが自分の合成グループになる — これがないと帯はカード面ではなく橙の舞台と掛け合わされる
  isolation: isolate;
  overflow: hidden;
}
.em__mood {
  mix-blend-mode: multiply;
  rotate: -#{$tilt};
}

色帯は mix-blend-mode: multiply でカード面と掛け算された色なので、カードの背景色を変えれば帯の色も変わります — ただし isolation: isolate がないと掛け算がカードを突き抜けて後ろの橙の舞台まで混ざりに行きます。

帯が通っても文字のコントラストは守る

帯が通ると面の色はクリームから掛け算された色へ変わりますが、最も暗くなる組み合わせ(クリーム × 橙 = #ff4b1c)でもインクの文字は 5.50:1 を保ち AA を満たすので、文字はすべてインク一色にして、薄い文字(アルファ)の代わりに太さと大きさだけで階層を付けています。

08イントロ用グリッドセクションカード

大きなカード1枚と小さなカード4枚が grid-area の定位置に置かれ、大きな見出しが先に出て、小さなカードが時間差で続く、サービス紹介の冒頭向けカードです。

イントロ用グリッドセクションカード
新しいタブで開く

08 · イントロ用グリッドセクションカード — 計測 累積面積 18.81% · 動いたフレーム 18/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

grid-areagrid-template-areasanimation-delay

図面を先に描き、カードに名札を付ける

.ig {
  display: grid;
  grid-template-columns: 1.25fr 1fr 1fr;
  grid-template-areas:
    "hero a b"
    "hero c d";
  grid-template-rows: repeat(2, 1fr);
  gap: $sp-2;
}
// 名札 — カードは HTML のどこにあっても自分の枠へ行く
.ig__hero { grid-area: hero; }
.ig__cell--a { grid-area: a; }

grid-template-areas が図面で grid-area が「このカードはあの枠」という名札なので、狭い画面の図面を行ごとに描き直すだけで配置が丸ごと変わり、カードの順序(HTML)もクラスも触りません。

大きなカードが先、小さなカードが時間差で

.ig.is-demo .ig__title { animation-name: igTitle; animation-fill-mode: backwards; }
.ig.is-demo .ig__cell--a { animation-delay: .14s; }
.ig.is-demo .ig__cell--b { animation-delay: .24s; }
.ig.is-demo .ig__cell--c { animation-delay: .34s; }
.ig.is-demo .ig__cell--d { animation-delay: .44s; }

視線が行く場所(大きなカードの見出し)が先に出て、付属の情報が時間差で続きます — 時間差を与えた要素すべてに animation-fill-mode: backwards が要るので、遅延の間に完成形が光ることはありません。

09読む順番を守るウォーターフォール

高さの違うカードを grid-auto-flow の dense で詰めて隙間を消しつつ番号順を守るので、見た目の順番とソースの順番がずれない、ブログ一覧向けカードです。

読む順番を守るウォーターフォール
新しいタブで開く

09 · 読む順番を守るウォーターフォール — 計測 累積面積 39.506% · 動いたフレーム 11/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

grid-auto-flowgrid-roworder

3つのプロパティがそれぞれ別の仕事をする

.wf {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-auto-rows: 46px;
  // 手前に残った枠を遡って埋める — 下の grid-row による明示配置と対で順番がずれない
  grid-auto-flow: row dense;
  gap: $sp-2;
}
.wf__card--s2 { grid-row: 1 / span 2; grid-column: 2; }
.wf__card--tall { grid-row: span 2; }
.wf__card--pin { order: -1; }

dense は自動配置が手前に残った枠を遡って埋めるので隙間は残らないものの、その枠を埋めるのは常に後ろのカードなので画面の読む順番が番号とずれます — 背の高いカードの行を grid-row で明示配置すると、自動で流れるカードたちが残りの枠に番号順のまま収まり、そもそも遡る隙間が生じません。

固定した投稿は order だけで前に

固定した投稿(1番)は HTML の一番最後に置いてあり、order: -1 の1行で配置の順番だけを先頭へ送るので、マークアップはそのままに画面の順番だけが変わります。

css カード デザイン おしゃれに見せるには何から決めるか?

カードを作るときの決める順番は決まっています。まず枠の数と幅(grid-template-columns)、次にカードの面(背景・角丸・枠線)、その次にリンクの持ち主(カード全体か、タイトルリンクか)です。順番が逆だと前の決定が後ろで崩れます — 先にカード全体をクリック可能にすると、後から2つ目のリンクを入れた瞬間にクリック領域が重なります。

この9つはすべて枠が先でした。01 は repeat(3, 1fr)、03 は正方形の aspect-ratio、08 は grid-template-areas の図面、09 は dense の配置 — 配置が先に立つから、カードの中の効果(ラベル帯・連絡先の行・色帯)は揺れない枠の中だけで動けます。css カード デザイン コピペや css カードレイアウトで探した人にも答えは同じです — 効果はレイアウトの上に乗るもので、レイアウトの代わりにはなれません。

リンクの持ち主の原則もこの記事全体に通っています。カード全体ではなくリンクにクリックを任せると、リンクの文字だけで行き先が分かります(02 の「ソヨンにメール」、03 の「注文履歴」、08 の「サービスを見てみる」)。スクリーンリーダーはカードのかたまりを一行で読まずリンクをひとつずつ巡るので、リンクが自分の行き先を言うことがアクセシビリティの半分です。

どこで壊れるか — dense が順番を変え、0fr が内容をはみ出す

dense だけでは読む順番が壊れる

09 を作りながら実測した落とし穴です。grid-auto-flow: row dense だけを入れて背の高いカードの行を固定しないと、2番のカードが2行分を占めるために次の行へ押され、その空きを後ろの 3番が遡って埋めます。目は番号を追うのに、画面の並びは 1, 3, 4, 2 になっていました。明示配置(grid-row: 1 / span 2)を対で入れて、遡るべき隙間自体をなくす必要があります。自動配置の規則の原文はMDN の grid-auto-flowにあります。

0fr の枠は自分では折りたたまれない

06 のスペック表が、折りたたまれているはずの最初の状態でそのまま見えていました。grid-template-rows: 0fr は枠の高さを0にするだけで子要素の大きさは変えません。子に overflow: hidden と min-height: 0 を足して初めて表が枠の中に収まりました。この2行のない 0fr は折りたたまれているのではなく、内容がはみ出したまま積まれている状態です。

カードが中央揃えだと開くたびに丸ごと跳ねる

06 でもう一度ありました。舞台が place-items: center なので、ボードが伸びた瞬間に3枚のカードがそろって上へ跳ねました — 「隣のカードは動かない」というこのカードの主張が、画面では嘘になる瞬間です。ボードを舞台の高さまで伸ばして(align-self: stretch)行を上から積み(align-content: start)、伸びる枠を中央の1つだけにしました。実測では、開閉の前後でボード位置の変化は 0px に収まりました。

9つのデモとこの原文一式をパスワードで固めました。カード9種の HTML・SCSS・React 版と記事全文の圧縮パスワードは 75rkpw3y で、グリッドで見たものをそのままファイルとして受け取って貼れます。上の落とし穴3つは zip のソースでそのまま再現できます — 09 の grid-row の行を消せば順番が崩れ、06 の overflow: hidden の2行を消せば表がはみ出します。

アクセシビリティ — 動きを除いたときに残るもの

9つすべて prefers-reduced-motion: reduce ではアニメーションが切れて、カードは定位置にあります。01 のラベル帯は上がり切った状態で見え、02 の連絡先の行は clip-path の遷移だけが切れてホバーすればそのまま開き、04 は下線がカットで移るだけで本文の入れ替えは元々カットなので違いはありません。05 の時間差の登場も消えるだけで、レビューは最初から全部見えます。動きが情報を説明する唯一の手段にならないように、どのカードも静止画だけで誰がどこへ行くかが読めるように組んでいます。フォーカスリングは9つすべて :focus-visible で別に用意し、色帯の通る 07 の文字のコントラスト(5.50:1)も上で計算済みです。

FAQ

css カード デザイン コピペでそのまま使えますか?

はい。上のコードはすべてそのまま貼って使える書き方です。ただし03のタイルのラベルや07のイベント名など画面の文字は韓国語のままなので、自分の文言に差し替えて、チップのアイコンはお使いのアイコンセットに置き換えてください。

カードの高さがばらばらですが css カードレイアウトではどう揃えますか?

揃えるのではなくばらばらのままうまく扱う方法を先に見てください。05 は段組み(column-count)でばらばらの高さをそのまま使い、09 は dense の配置でばらばらのカードが隙間を埋めます。きっちり揃える必要があるなら、08 のように grid-template-rows で行の高さを固定するか、01 のように 1fr の枠で均等割りにするのが答えです。

grid-template-areas は古いブラウザでも動きますか?

grid-template-areas と grid-area は、2017年以降グリッドに対応しているすべてのブラウザで動きます。この記事で比較的新しいのは、04 の :has()(2023年から主要ブラウザすべて)と 03 の aspect-ratio(2021年)なので、この2つは対象ブラウザを確認してから使ってください。

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。