repeated-component-alignment について
Repeated Component Alignmentは、カードや行、タイルなどを作る開発者向けのデザインスキルです。可変長のコンテンツでも整列を保つスロット設計と、切り詰めたテキストを再表示する方法を解説します。
主な機能
- 01反復要素の固定スロット設計
- 02各要素でアンカーを固定
- 03任意スロットのスペース確保
- 04テキストの省略と全文表示
- 05画像とローカライズへの対応
活用シーン
- 01長さの異なるテキストを含むカードの整列
- 02テーブル行とコントロールの安定化
- 03翻訳後のラベルによるレイアウト崩れの防止
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dembrandt/ dembrandt-skills --skill repeated-component-alignmentインストール後、Claude Code で/repeated-component-alignmentを入力して読み込まれたか確認します。
詳細
繰り返しコンポーネントの整列
カードグリッド、リスト、テーブル、ナビゲーションメニュー、KPI タイルの列、フィードなど、コンポーネントが何度もレンダリングされると、単なるボックスではなくパターンになります。パターンの価値はリズムにあります。目は一度レイアウトを覚えると、すべてのインスタンスで同じスロット(タイトル行、価格行、アクション行)を追うようになります。コンポーネントが可変長のコンテンツを吸収できるように作られていないと、コンテンツの長さの違いによってそのリズムが崩れます。
この原則は広く当てはまります。最も一般的な例はカードですが、同じルールはリスト行、テーブルセル、ナビゲーション項目、タイル、コメントエントリー、ダッシュボードウィジェット、検索結果など、繰り返されるあらゆるものに適用されます。それぞれを自由形式のコンテナではなく、固定スロットモデルとして扱います。
目標は、どんな長さのコンテンツでも、インスタンスの見た目を同じにすることです。これは、コンテンツ制作(目標の長さに合わせて書くこと)とレイアウト設計(ばらつきを許容するスロットを構築すること)の両方に関わります。このスキルでは、レイアウト面と、その両者が交わる部分を扱います。
スロットモデル
スロットに一度名前を付けたら、すべてのインスタンスが守る契約として扱います。具体例として、商品カードを見てみましょう。
┌──────────────────────┐
│ [media] │ ← fixed aspect ratio
│ │
├──────────────────────┤
│ ● Badge │ ← optional slot, space reserved
│ Title │ ← clamp to N lines
│ Subtitle / meta │
│ │
│ Description text… │ ← flexible slot, grows
│ │
├──────────────────────┤
│ €19.99 Read more│ ← anchor, pinned to bottom
└──────────────────────┘
同じ3つのルールが、リスト行(アバター · 名前 · メタデータ · 右端に固定されたステータス)、KPI タイル(ラベル · 大きな数値 · 下部に固定されたトレンド)、検索結果(タイトル · URL · 省略表示するスニペット)にも適用されます。
- すべてのスロットは固定位置にあります。インスタンスにコンテンツがあるかどうかは関係ありません。
- ばらつきを吸収するスロットは1つです(通常は説明文/スニペット)。それ以外はすべて固定するか、表示を省略します。
- 基準となる要素は固定します。主要なアクションや値(CTA、価格、ステータス、トレンド)は、上下や隣にどれだけコンテンツがあっても、すべてのインスタンスで同じ位置に配置します。
状態によって切り替わるコントロール
状態ごとに異なるコントロール(ボタン、入力欄、値、リンクなど)を表示するセルも、1つのスロットです。各位置に固定幅を与え、そこに配置するコントロールを幅いっぱいに広げます。同じ位置には、すべての行と状態で同じ役割のコントロールを配置します。コンテンツの幅に合わせるコントロールはラベルに応じて幅が変わるため、行の再配置が発生し、隣のコントロールがカーソルの下で移動し、列全体が1つのリストとして読めなくなります。
基準位置を揃える
最もよくある不具合は、テキストの長さが異なるために、基準となる要素(「続きを読む」リンク、価格、ステータスチップ)の位置がインスタンスごとにずれることです。可変スロットを伸長させ、基準要素を固定端に押し付けて解決します。
縦方向のレイアウト(カード、タイル) — フッターを下端に固定する:
.item {
display: flex;
flex-direction: column;
height: 100%; /* fill the grid/flex track */
}
.item__media {
aspect-ratio: 4 / 3; /* never let media height vary */
object-fit: cover;
}
.item__description {
flex: 1; /* the flexible slot absorbs the slack */
}
.item__footer {
margin-top: auto; /* pin the anchor (price + CTA) to the bottom */
}
インスタンス同士の高さを揃えるには、コンテナのトラックでインスタンスを伸長させる必要があります。CSS Grid と Flex では、これがデフォルトの動作です(align-items: stretch)。その後、height: 100% で各インスタンスをトラックいっぱいに広げ、margin-top: auto ですべてのフッターを揃えます。
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: var(--space-4);
/* align-items: stretch is the default — do not override it */
}
横方向のレイアウト(リスト行、テーブルセル) — 基準要素を右端に固定する:
.row {
display: flex;
align-items: center;
gap: var(--space-3);
}
.row__title {
flex: 1; /* title absorbs the width variance */
min-width: 0; /* REQUIRED for the title to be allowed to shrink/ellipsis */
}
.row__status {
margin-left: auto; /* status pinned to the right edge */
}
固定の height を指定してサイズを揃えるのは避けてください。最も高さのある自然なインスタンスがサイズを決め、その高さを超えるコンテンツは切り取られるか、はみ出します。トラックを伸長させ、基準要素を固定します。
オプションスロットのためのスペースを確保する
一部のインスタンスには表示され、別のインスタンスには表示されないスロット(バッジ、割引ラベル、「認証済み」のチェックマークなど)があると、それより後ろの要素が表示されているインスタンスでずれ、整列が崩れます。解決策は2つあります。
- スロットを確保する — コンテンツがない場合も空のまま、コンテナを常に固定サイズでレンダリングします。
.item__badge-row {
min-height: 24px; /* always occupies the row */
}
- スロットを重ねて配置する — 絶対位置に置き、通常のフローに影響しないようにします。
.item__badge {
position: absolute;
top: var(--space-2);
left: var(--space-2);
}
インラインのメタデータ要素にはスペースを確保し、メディア上のマーカー(Sale、New)には重ねて配置します。どちらの場合も、その後ろのスロットがすべてのインスタンスで同じ位置から始まるようにします。
はみ出すテキストを省略表示し、完全な値を取り出せるようにする
スロットのサイズを固定する必要がある一方で内容の長さが変わる場合は、表示する行数を制限し、省略記号で切れていることを示します。これにより、レイアウトの寸法が安定します。ただし、省略すると情報が隠れてしまうため、必ず完全な値を確認できるようにしてください。
複数行のテキスト — N 行に制限する:
.clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
1 行の値(名前、SKU、パスなど) — 省略記号を付ける:
.truncate {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 0; /* needed inside a flex row to allow shrinking */
}
完全な値を確認できるようにする。 省略表示された文字列の全文にアクセスできないと、ユーザビリティ上の問題になります。次の方法で表示してください。
- 通常のテキストにはネイティブのツールチップ:
<span title="Full value here">…</span>。追加コストはなく、どこでも使えますが、ホバー時にしか表示されず(タッチ操作には非対応)、スタイルも設定できません。 - タッチ操作への対応、スタイル設定、リッチコンテンツが必要な場合はカスタムツールチップ。
- 全文を見せることが目的の場合は、その場で表示する。 ユーザーに必要なテキストを常に隠すのではなく、「続きを読む」切り替えでその項目を展開するか、詳細ビューを開きます。
テキストが実際に省略されている場合にのみツールチップを設定してください。収まっている文字列に
titleを付けても、ホバー時に冗長な情報が表示されます。オーバーフロー(scrollWidth > clientWidth)を検出し、条件に応じてtitleを設定してください。
生成されるリストでは、この配慮が忘れられがちです。 ナビゲーションレール、目次、ツリービュー、サイドバーでは、データ由来のラベルを固定幅の列に表示するため、常に一部の行が途中で切れます。各行のラベル全体を title に設定してください(「条件に応じて設定する」の例外です。リストが長く、幅が固定されているため、行ごとのオーバーフロー検出には、冗長なホバー表示よりもコストがかかります)。その前にテキストをデコードしてください。レンダリング済み HTML から作った title では、& や " がそのまま表示され、切れたテキストよりも読みづらくなります。
長さが変わる場合の対処の優先順位:
- 文字数に合わせて書く — 最もすっきりした解決策です。コンテンツ作成者に目安(例:タイトルは 60 文字以下、スニペットは 120 文字以下)を示し、ほとんどの値が省略不要になるようにします。レイアウト上の工夫は安全策であり、第一の方針ではありません。
- 省略表示して、全文を確認できるようにする — 作成されるコンテンツの長さを保証できない場合(ユーザー生成コンテンツ、サードパーティのフィード、i18n による文字数の増加など)に使います。
- 1 つのスロットを伸ばす — 長さが変わることを許容するスロットを 1 つだけ設ける場合は、省略せずに flex で差を吸収し、その後に続く要素はすべて固定します。
スロット内の画像
画像のサイズを制約するのは、幅か高さのどちらか一方だけにしてください。幅と height: auto を指定するか、固定の aspect-ratio と object-fit を使います。両方を制約すると画像が歪みます。
Grid では画像だけを置くと列いっぱいに引き伸ばされ、flex の列方向レイアウトでは交差軸いっぱいに引き伸ばされます。画像をラップするか、align-self/justify-self を指定してください。あるカードではロゴがつぶれて見え、隣のカードでは正常に見える場合、その原因はたいていここにあります。
位置がずれたロゴの原因を CSS のせいにする前に、画像アセットを確認してください。ファイルに含まれる余白によって図柄がボックスの中心からずれている場合、どのようなレイアウト規則を使っても解決できません。
ラベルと値のリスト
ラベルと値が繰り返されるリストでは、ラベル列の幅を固定します。先頭のトラックを固定幅にした Grid か、テーブルを使います。すると、すべての値の開始位置がそろいます。間隔と区切り記号(矢印、ダッシュ、パイプ)を使った flex 行では位置がそろわず、区切り記号は各行で読み飛ばす必要のある要素を増やすだけです。
国際化に関する注意
テキストは翻訳すると長くなります。ドイツ語やフィンランド語では、英語より一般に 30~40%長くなります。英語ではきれいにそろうコンポーネントでも、別のロケールでは崩れることがあります。長い場合に備えてスロットを設計してください。テキストを省略表示し、任意のスロット用にスペースを確保し、flex 行には min-width: 0 を設定します。また、ソース言語でラベルが 1 行に収まるからといって、他の言語でも収まると考えないでください。
状態が変わってもコントロール領域の高さを一定に保つ
キャンバスとコントロールが垂直方向のスペースを取り合う場合は、現在の状態で使えるコントロールだけを表示し、ほかの状態でも同じスロットを使うようにします。行を表示したり隠したりすると、状態が変わるたびにキャンバスのサイズが変わります。そのため、ちらつきのように感じられ、ユーザーが見ている対象も動いてしまいます。ある状態で無効になるコントロールは、まずスロットを譲る対象にしてください。
レビューチェックリスト
- 繰り返し表示されるコンポーネントは定義済みのスロットモデルですか? 各インスタンスが同じ順序で同じスロットを埋めていますか?
- 兄弟インスタンスは、固定の高さではなく、引き伸ばされたグリッド/フレックスのトラックによって同じサイズになっていますか?
- アンカー(CTA/価格/ステータス/値)は固定されていますか? インスタンス間で揃うよう、列では
margin-top: auto、行ではmargin-left: autoを使用していますか? - 長さのばらつきを吸収するスロットは1つだけで、残りは固定またはクランプされていますか?
- オプションのスロット(バッジ、ラベル)は領域を確保するかオーバーレイされ、後続のスロットをずらさないようになっていますか?
- メディアには固定の
aspect-ratioが設定され、サイズが変わらないようになっていますか? - 各画像は1つの寸法が制限され、グリッドまたはフレックスによる引き伸ばしから保護されていますか?
- ラベルと値のリストでは、区切り記号を含むフレックス行ではなく、幅が固定されたラベル列を使用していますか?
- 複数行のスロットは行数を制限し、1行の値は省略記号で省略していますか?
- 省略表示されるフレックス行に
min-width: 0を指定し、テキストが縮小できるようにしていますか? - テキストを省略した場合、タイトル/ツールチップ/展開によって値全体を確認できますか?
- ツールチップはテキストが実際にはみ出す場合にのみ表示されますか?
- コンテンツ作成者には目標の文字数が示され、省略表示は通常ではなく安全策として機能していますか?
- スロットは原文の言語だけでなく、翻訳後に最も長くなるロケールでも確認されていますか?
- セル内のコントロールが状態に応じて切り替わる場合、どの状態でも同じ幅の固定スロットを埋めていますか?
- すべての状態でコントロール領域の高さは一定に保たれ、状態ごとのコントロールは共有スロットを使用していますか?