コメントスレッドComment thread
丸いアイコンと名前・日時の下に本文を置き、返信やいいねのボタンを添えたコメントを縦に並べる表示。返信は1段字下げして左の線で親につなぐ。
表示例
アイコン: Font Awesome 7 Free(fa-solid / fa-regular)
別名・英語表記
- コメント一覧
- 返信スレッド
- ディスカッション
- 入れ子のコメント
- ツリー型コメント
- スレッド表示
- Comment thread
実装例
<section class="x-comment-thread" aria-labelledby="x-comment-thread-h">
<h2 class="x-comment-thread__h" id="x-comment-thread-h">コメント 2件</h2>
<ol class="x-comment-thread__list" role="list">
<li>
<article class="x-comment-thread__c" aria-labelledby="x-comment-thread-a1">
<div class="x-comment-thread__meta">
<span class="x-comment-thread__av x-comment-thread__av--a" aria-hidden="true">佐</span>
<span class="x-comment-thread__name" id="x-comment-thread-a1">佐藤 花子</span>
<time class="x-comment-thread__time" datetime="2026-09-24T18:30">9月24日 18:30</time>
</div>
<p class="x-comment-thread__body">手順どおりに進めたら動きました。3番目の設定が分かりにくかったです。</p>
<div class="x-comment-thread__act">
<button class="x-comment-thread__b" type="button" aria-label="佐藤 花子に返信">
<i class="fa-solid fa-reply" aria-hidden="true"></i>返信
</button>
<button class="x-comment-thread__b" type="button" aria-pressed="true">
<i class="fa-solid fa-heart" aria-hidden="true"></i>いいね
</button>
<!-- 数は押すと変わるのでボタンの名前に入れない。状態は aria-pressed だけで伝える -->
<span class="x-comment-thread__cnt">3件</span>
</div>
</article>
<!-- 返信は親のコメントの li の中に入れ子の ol で置く。字下げと左の線で親子を見せる -->
<ol class="x-comment-thread__replies" role="list">
<li>
<article class="x-comment-thread__c" aria-labelledby="x-comment-thread-a2">
<div class="x-comment-thread__meta">
<span class="x-comment-thread__av x-comment-thread__av--b" aria-hidden="true">編</span>
<span class="x-comment-thread__name" id="x-comment-thread-a2">編集部</span>
<span class="x-comment-thread__tag">投稿者</span>
<time class="x-comment-thread__time" datetime="2026-09-25T09:10">9月25日 9:10</time>
</div>
<p class="x-comment-thread__body">ありがとうございます。3番目に図を足しました。</p>
<div class="x-comment-thread__act">
<button class="x-comment-thread__b" type="button" aria-label="編集部に返信">
<i class="fa-solid fa-reply" aria-hidden="true"></i>返信
</button>
<button class="x-comment-thread__b" type="button" aria-pressed="false">
<i class="fa-regular fa-heart" aria-hidden="true"></i>いいね
</button>
<span class="x-comment-thread__cnt">0件</span>
</div>
</article>
</li>
</ol>
</li>
</ol>
</section>
/* 貼った先のリセットに頼らないよう、この部品の中だけ border-box にする */
.x-comment-thread,
.x-comment-thread *:not([class*="fa-"]),
.x-comment-thread *:not([class*="fa-"])::before,
.x-comment-thread *:not([class*="fa-"])::after {
box-sizing: border-box;
/* 貼った先の既定の書体に引きずられないようにする。書体が変わると
文字の幅と高さが変わり、枠のサイズまでずれる */
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Hiragino Sans", "Noto Sans JP", "Yu Gothic UI", Meiryo, sans-serif;
}
.x-comment-thread {
width: 330px;
max-width: 100%;
padding: 12px;
border: 1px solid #e3e6ea;
border-radius: 10px;
background: #fff;
color: #1b2028;
}
.x-comment-thread__h {
margin: 0 0 8px;
font-size: 13px;
font-weight: 700;
}
.x-comment-thread__list,
.x-comment-thread__replies {
margin: 0;
padding: 0;
list-style: none;
}
/* 返信は字下げし、左の線で「どのコメントへの返信か」を見せる */
.x-comment-thread__replies {
margin: 8px 0 0 15px;
padding-left: 12px;
border-left: 2px solid #cfd4dc;
}
.x-comment-thread__meta {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 6px;
}
.x-comment-thread__av {
display: grid;
place-items: center;
flex: none;
width: 28px;
height: 28px;
border-radius: 50%;
font-size: 12px;
font-weight: 700;
}
.x-comment-thread__av--a {
background: #dbeafe;
color: #1e40af;
}
.x-comment-thread__av--b {
background: #dcfce7;
color: #166534;
}
.x-comment-thread__name {
font-size: 12px;
font-weight: 700;
}
.x-comment-thread__tag {
padding: 1px 6px;
border: 1px solid #7f8a99;
border-radius: 999px;
color: #3f4855;
font-size: 10px;
}
.x-comment-thread__time {
color: #5b6472;
font-size: 11px;
}
.x-comment-thread__body {
margin: 4px 0 0 34px;
font-size: 12.5px;
line-height: 1.6;
overflow-wrap: anywhere;
}
.x-comment-thread__act {
display: flex;
gap: 4px;
margin: 2px 0 0 28px;
}
.x-comment-thread__b {
display: inline-flex;
align-items: center;
gap: 4px;
min-height: 24px;
padding: 0 6px;
border: 0;
border-radius: 6px;
background: none;
color: #4b5563;
font-size: 11px;
cursor: pointer;
}
.x-comment-thread__b:hover {
background: #f1f3f6;
}
/* 押した状態はハートの塗りと色の両方で示す(押す前は線だけのハート) */
.x-comment-thread__b[aria-pressed="true"] {
color: #b91c1c;
font-weight: 700;
}
.x-comment-thread__cnt {
align-self: center;
color: #4b5563;
font-size: 11px;
}
.x-comment-thread__b:focus-visible {
outline: 2px solid #1d4ed8;
outline-offset: 1px;
}
AIへの指示文
「コメントスレッド(Comment thread)」を実装してください。
## これは何か
丸いアイコンと名前・日時の下に本文を置き、返信やいいねのボタンを添えたコメントを縦に並べる表示。返信は1段字下げして左の線で親につなぐ。
## 使いどころ
- 記事やお知らせの下に、読者のコメントと返信を並べたいとき
- 課題やレビューの指摘ごとに、やり取りを話題単位でまとめて残したいとき
## 実装の要件
- section(見出し「コメント n件」)> ol[role=list] > li > article(アバター・名前・time・本文・操作)+ ol(返信)
- 返信の ol は margin-left と border-left で字下げし、線で親につなぐ
- 操作のボタンは高さ 24px 以上。返信・いいね・通報のように、よく使うものから左に並べる
- 件数が多いときは「返信を n件表示」で折りたたみ、開いたら最初の返信へフォーカスを移す
- その場で会話を続けるチャットはチャットのメッセージ。こちらは記事や話題に付いたコメントを、話題ごとに後から読み返す表示で、新着を自動で読み上げない
- コメントを書き込む欄はテキストエリアで作る。一覧の上か下のどちらかに決めて置き、返信の欄は返信ボタンを押したコメントの直下に開く
- 入れ子は2段までにとどめる。深くすると字下げで本文の幅が潰れ、スマートフォンでは1行に数文字しか入らなくなる
- 日時の書き方をそろえる。「3時間前」のような相対表記にするなら、time 要素の datetime に正確な日時を持たせる
- いいねの数を押すたびに変わるボタンの名前に入れない。名前は「いいね」で固定し、数はボタンの外に置く
## アクセシビリティの要件
- コメント全体を ol に、1件を li > article にする。article は利用者が投稿したコメントの典型例として HTML の仕様に挙がっている
- 返信は親のコメントの li の中に入れ子の ol で置く。字下げと線だけでなく構造でも親子を伝える(WCAG 2.2 の 1.3.1 情報及び関係性)
- list-style: none にした ol には role="list" を付ける。Safari はそうしないとリストとして読まない
- 投稿日時は time 要素にして datetime 属性を付ける
- 同じ名前の「返信」ボタンが並ぶので、aria-label を「佐藤 花子に返信」のようにして、どのコメントへの返信かを名前に入れる(見えている「返信」を名前に含める)
- いいねは aria-pressed で押した状態を伝える。見た目は色だけでなく、塗りのハートと線のハートのように形でも変える
## 参考にする実装
この形を出発点にして、プロジェクトの命名規則・色・余白に合わせて調整してください。
```html
<section class="x-comment-thread" aria-labelledby="x-comment-thread-h">
<h2 class="x-comment-thread__h" id="x-comment-thread-h">コメント 2件</h2>
<ol class="x-comment-thread__list" role="list">
<li>
<article class="x-comment-thread__c" aria-labelledby="x-comment-thread-a1">
<div class="x-comment-thread__meta">
<span class="x-comment-thread__av x-comment-thread__av--a" aria-hidden="true">佐</span>
<span class="x-comment-thread__name" id="x-comment-thread-a1">佐藤 花子</span>
<time class="x-comment-thread__time" datetime="2026-09-24T18:30">9月24日 18:30</time>
</div>
<p class="x-comment-thread__body">手順どおりに進めたら動きました。3番目の設定が分かりにくかったです。</p>
<div class="x-comment-thread__act">
<button class="x-comment-thread__b" type="button" aria-label="佐藤 花子に返信">
<i class="fa-solid fa-reply" aria-hidden="true"></i>返信
</button>
<button class="x-comment-thread__b" type="button" aria-pressed="true">
<i class="fa-solid fa-heart" aria-hidden="true"></i>いいね
</button>
<!-- 数は押すと変わるのでボタンの名前に入れない。状態は aria-pressed だけで伝える -->
<span class="x-comment-thread__cnt">3件</span>
</div>
</article>
<!-- 返信は親のコメントの li の中に入れ子の ol で置く。字下げと左の線で親子を見せる -->
<ol class="x-comment-thread__replies" role="list">
<li>
<article class="x-comment-thread__c" aria-labelledby="x-comment-thread-a2">
<div class="x-comment-thread__meta">
<span class="x-comment-thread__av x-comment-thread__av--b" aria-hidden="true">編</span>
<span class="x-comment-thread__name" id="x-comment-thread-a2">編集部</span>
<span class="x-comment-thread__tag">投稿者</span>
<time class="x-comment-thread__time" datetime="2026-09-25T09:10">9月25日 9:10</time>
</div>
<p class="x-comment-thread__body">ありがとうございます。3番目に図を足しました。</p>
<div class="x-comment-thread__act">
<button class="x-comment-thread__b" type="button" aria-label="編集部に返信">
<i class="fa-solid fa-reply" aria-hidden="true"></i>返信
</button>
<button class="x-comment-thread__b" type="button" aria-pressed="false">
<i class="fa-regular fa-heart" aria-hidden="true"></i>いいね
</button>
<span class="x-comment-thread__cnt">0件</span>
</div>
</article>
</li>
</ol>
</li>
</ol>
</section>
```
```css
/* 貼った先のリセットに頼らないよう、この部品の中だけ border-box にする */
.x-comment-thread,
.x-comment-thread *:not([class*="fa-"]),
.x-comment-thread *:not([class*="fa-"])::before,
.x-comment-thread *:not([class*="fa-"])::after {
box-sizing: border-box;
/* 貼った先の既定の書体に引きずられないようにする。書体が変わると
文字の幅と高さが変わり、枠のサイズまでずれる */
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Hiragino Sans", "Noto Sans JP", "Yu Gothic UI", Meiryo, sans-serif;
}
.x-comment-thread {
width: 330px;
max-width: 100%;
padding: 12px;
border: 1px solid #e3e6ea;
border-radius: 10px;
background: #fff;
color: #1b2028;
}
.x-comment-thread__h {
margin: 0 0 8px;
font-size: 13px;
font-weight: 700;
}
.x-comment-thread__list,
.x-comment-thread__replies {
margin: 0;
padding: 0;
list-style: none;
}
/* 返信は字下げし、左の線で「どのコメントへの返信か」を見せる */
.x-comment-thread__replies {
margin: 8px 0 0 15px;
padding-left: 12px;
border-left: 2px solid #cfd4dc;
}
.x-comment-thread__meta {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 6px;
}
.x-comment-thread__av {
display: grid;
place-items: center;
flex: none;
width: 28px;
height: 28px;
border-radius: 50%;
font-size: 12px;
font-weight: 700;
}
.x-comment-thread__av--a {
background: #dbeafe;
color: #1e40af;
}
.x-comment-thread__av--b {
background: #dcfce7;
color: #166534;
}
.x-comment-thread__name {
font-size: 12px;
font-weight: 700;
}
.x-comment-thread__tag {
padding: 1px 6px;
border: 1px solid #7f8a99;
border-radius: 999px;
color: #3f4855;
font-size: 10px;
}
.x-comment-thread__time {
color: #5b6472;
font-size: 11px;
}
.x-comment-thread__body {
margin: 4px 0 0 34px;
font-size: 12.5px;
line-height: 1.6;
overflow-wrap: anywhere;
}
.x-comment-thread__act {
display: flex;
gap: 4px;
margin: 2px 0 0 28px;
}
.x-comment-thread__b {
display: inline-flex;
align-items: center;
gap: 4px;
min-height: 24px;
padding: 0 6px;
border: 0;
border-radius: 6px;
background: none;
color: #4b5563;
font-size: 11px;
cursor: pointer;
}
.x-comment-thread__b:hover {
background: #f1f3f6;
}
/* 押した状態はハートの塗りと色の両方で示す(押す前は線だけのハート) */
.x-comment-thread__b[aria-pressed="true"] {
color: #b91c1c;
font-weight: 700;
}
.x-comment-thread__cnt {
align-self: center;
color: #4b5563;
font-size: 11px;
}
.x-comment-thread__b:focus-visible {
outline: 2px solid #1d4ed8;
outline-offset: 1px;
}
```
## 出力の約束
- HTML と CSS を分けて出す。JavaScript が要るときは素の JavaScript で書き、UIフレームワークは使わない。
- 参考実装がアイコンに Font Awesome のクラスを使っている場合は、プロジェクトで使っているアイコンかインライン SVG に置き換えてよい。
- 上のアクセシビリティ要件を満たす。満たせない箇所があるときは、理由を添えて先に知らせる。
- 色・角丸・余白は既存のデザイントークンがあればそれを使う。無ければ CSS 変数として切り出す。
使いどころ
- 記事やお知らせの下に、読者のコメントと返信を並べたいとき
- 課題やレビューの指摘ごとに、やり取りを話題単位でまとめて残したいとき
注意点
- その場で会話を続けるチャットはチャットのメッセージ。こちらは記事や話題に付いたコメントを、話題ごとに後から読み返す表示で、新着を自動で読み上げない
- コメントを書き込む欄はテキストエリアで作る。一覧の上か下のどちらかに決めて置き、返信の欄は返信ボタンを押したコメントの直下に開く
- 入れ子は2段までにとどめる。深くすると字下げで本文の幅が潰れ、スマートフォンでは1行に数文字しか入らなくなる
- 日時の書き方をそろえる。「3時間前」のような相対表記にするなら、time 要素の datetime に正確な日時を持たせる
- いいねの数を押すたびに変わるボタンの名前に入れない。名前は「いいね」で固定し、数はボタンの外に置く
アクセシビリティの要点
- コメント全体を ol に、1件を li > article にする。article は利用者が投稿したコメントの典型例として HTML の仕様に挙がっている
- 返信は親のコメントの li の中に入れ子の ol で置く。字下げと線だけでなく構造でも親子を伝える(WCAG 2.2 の 1.3.1 情報及び関係性)
- list-style: none にした ol には role="list" を付ける。Safari はそうしないとリストとして読まない
- 投稿日時は time 要素にして datetime 属性を付ける
- 同じ名前の「返信」ボタンが並ぶので、aria-label を「佐藤 花子に返信」のようにして、どのコメントへの返信かを名前に入れる(見えている「返信」を名前に含める)
- いいねは aria-pressed で押した状態を伝える。見た目は色だけでなく、塗りのハートと線のハートのように形でも変える
コメント 2件
手順どおりに進めたら動きました。3番目の設定が分かりにくかったです。
ありがとうございます。3番目に図を足しました。