ハコヅミを作る #23:カード表示を正方形にそろえ、キャンプを年ごとに区切る
前回(#22)に続いて、見た目の手直しです。今回は、#20で作ったカード表示を直します(#110・#112)。
前回は「サンプル画像を見て決めれば、あとからの直しが出ない」という話でした。今回は、サンプルを見て決めても、使ってみたら直したくなった話です。
「正方形にしたい」の、2通りの読み方
メモは、こうでした。
ギアとキャンプのカード表示のカード部分は正方形にしたい
今のカードは、画像の下に文字が続く形です。文字の行数がカードごとに違うので、高さがばらばらです。
「カード部分は正方形」には、2通りの読み方があります。
- 案A:カード全体(画像+文字)を正方形にする
- 案B:画像を正方形にして、文字は下に続ける(今に近い)
どちらか決めきれなかったので、両方のサンプルが出てきました。

ギアのカード(左から、今/案A:カード全体を正方形/案B:画像を正方形)

キャンプのカード(左から、今/案A/案B)
画像は、今の画面に見た目だけを仮に当てて撮ったサンプルです。ギアは#7で取り込んだ実際のもので、キャンプ場は記事用の架空のものです。画像付きのカードには、色だけの絵を付けています。
案A は、タイルがきれいにそろいます。その代わり、文字を入れた残りが画像になるので、スマホの幅では、画像が細い帯になります。キャンプは文字が多いので、特に細い。Claude Code のおすすめは「キャンプは案B」でした。
それでも、ギアもキャンプも 案A にしました。画像を大きく見ることより、一覧としてそろって見えるほうを取っています。おすすめと違うほうを選べるのも、両方の画像が並んでいるからです。
中身の並び
あわせて、カードの中身を整えました。
- ギア:画像・名称・メーカー・コンテナ・評価の順で、1つずつ行を分ける(メーカーとコンテナのバッジを、横に並べない)
- キャンプ:日付を、#22のリストと同じ書き方(曜日・泊数付き)にする。プランはバッジ、全部積んだら「積み込み済み」
- 名前は1行に収め、長いときは「…」で切る
キャンプの日付は、カードの幅では1行に入りません。「必ず2段になってしまうので、2段目だけ右寄せにして」と頼んで、開始日を左、終了日と泊数を右に寄せた2段にしました。
正方形の作り方
Tailwind では、カードに aspect-square(縦横を同じにする)と、縦に並べる flex-col を付けます。文字の部分は中身の高さのまま(flex-none)、画像は残りの高さいっぱい(flex-1)です。
<a class="flex aspect-square flex-col overflow-hidden rounded-box border border-line">
<img src={gear.imageUrl} alt="" loading="lazy" class="min-h-0 w-full flex-1 object-cover" />
<div class="flex-none space-y-0.5 p-2">
<p class="truncate text-sm font-semibold">{gear.productName}</p>
...
</div>
</a>
画像には min-h-0 を付けています。flex の中の要素は、そのままでは中身より小さくならないので、付けておかないと、画像が縮まずにカードが正方形からはみ出すことがあります。
使ってみたら、直したくなった
開発環境で実際に見てみると、3つ直したくなりました。
- キャンプの日付は、やはり1行に収めたい(曜日を外してでも)
- カードとカードの間を、もう少し詰めたい
- キャンプを、年ごとに区切りたい
1 は、自分で「2段にして右寄せ」と頼んだものです。サンプルでも見ていました。それでも、実際の画面で何枚も並ぶと、2段の日付が場所を取って、落ち着きません。サンプルは「選ぶ」ためには十分でも、「使った感じ」までは分からない、ということです。
ここでも、先にサンプルを頼みました。
年の区切り:見出しか、切り替えか
「年ごとにタブで区切る」も、2通りに読めます。

年の区切り(左から、今/案A:年ごとに見出しで区切る/案B:年を切り替えるタブ)
- 案A:年ごとの見出しで区切り、全部の年を1つの画面に続けて出す
- 案B:年を切り替えるタブにして、選んだ年だけを出す
キャンプは年に数回です。当面は1画面で見渡せるほうが使いやすいので、案A にしました。見出しは、ギアのカテゴリと同じタブの形(#22)なので、新しい見た目は増えていません。
このサンプルで、もう1つ分かったことがあります。右の案B は、日付に年を付けたままです。年を付けると、スマホの幅では1行に入りきらず、「…」で切れています。年ごとに区切るなら、年は見出しで分かるので、カードの日付からは外せます。
「曜日を入れても、1行に収まる?」
年を外すなら、その分、曜日を戻せるかもしれません。聞いてみました。
日付は確かに年を外してOK(その場合、曜日を入れても1行に収まる?収まるのであれば曜日は出したい)
返ってきたのは、「たぶん収まります」ではなく、実際に当てて測った結果でした。
| 画面の幅 | 曜日あり・文字 12px(今の大きさ) | 曜日あり・文字 11px | 曜日なし |
|---|---|---|---|
| 375px | 月も日も2桁の日付だけ、5px はみ出して「…」になる | 全部収まる | 全部収まる |
| 390px | 全部収まる(余裕は 2px) | 全部収まる | 全部収まる |
| 広い幅(3列) | 全部収まる | 全部収まる | 全部収まる |

幅 375px での見比べ(左から、曜日あり 12px/曜日あり 11px/曜日なし)。左の上の2枚だけ、末尾が切れている
はみ出すのは、「11/21(土)〜11/23(月)・2泊」のように、月も日も2桁の日付だけです。日付の行だけ文字を 1px 小さくすれば、全部収まります。曜日あり・11px に決めました。
測り方は単純で、日付の要素の「中身の幅」と「見えている幅」を比べるだけです。
// はみ出して「…」になっているか
el.scrollWidth > el.clientWidth
同じ確かめ方を、そのまま E2E にも入れました。幅 375px で、月も日も2桁の日付がはみ出さないことを確かめています。
ただし、#21で書いたとおり、E2E は Web フォントを読み込みません。フォントが違えば、文字の幅も違います。実際のフォントでは、手元でフォントを読み込んだ画面を撮って、同じ数字を測って確かめました。自動テストで確かめたことと、目と手で確かめたことは、PR に分けて書いています。
受入のあとの直しは、新しい Issue にする
進め方で迷ったのは、この直しをどの Issue でやるかです。
カードを正方形にした Issue(#110)は、PR がマージされて、受入確認を待っている状態でした。そこに直しを足すこともできます。ただ、年ごとの区切りは、リスト表示にも入る、#110 より大きい変更です。
- #110 は、今の状態で受入 OK にする
- 直しは、新しい Issue(#112)にする
と分けました。#110 の AC は「2段目が右寄せになっている」のままで、受入のコメントに「受入のあとで出た直しは、別の Issue で行う」と残しています。Issue の AC を書き換えて、なかったことにはしません。一度そう決めて作った、という経緯が残るほうが、あとで見返したときに分かりやすいためです。
できあがり

スマホの幅(左から、キャンプのカード表示/リスト表示/ギアのカード表示)

広い幅(左:カード表示、右:リスト表示)
- カードの間は、12px から 6px に
- 年の見出しの右に、その年の件数
- 年ごとの区切りは、リスト表示にも入れた(表示を切り替えても、同じ並びになる)
年ごとにまとめる処理は、ほかのロジックと同じく src/lib/camps.ts に切り出して、ユニットテストしています。年をまたぐキャンプ(12/31〜1/1)は、開始日の年に入れました。
おわりに
- 「正方形にしたい」のような一言にも、読み方が2つある。決めきれないときは、両方を並べてもらう
- カード全体を正方形にするなら、文字は中身の高さ、画像は残りの高さにする(画像には
min-h-0) - 「収まるか」は、聞くより測る。
scrollWidthとclientWidthを比べれば、はみ出しが分かる - サンプルで選んでも、使ってみて変えたくなることはある。そのときは、受入済みの Issue を書き換えずに、新しい Issue にする
フォント(#21)から今回までの見た目の手直しは、6つの Issue をまとめて本番にリリースしました。
手元の E2E の時間についても、後日談があります。#20で「10分を超えるようになった」と書きましたが、原因の1つは、手元の D1 にたまったテストのデータ(ギアが7,000件あまり)でした。消して作り直したら、全件で7分ほどに戻っています。
次は、キャンプの日付の出し方の見直しと、おすすめ度・感想の追加を予定しています。