Webパーツ辞典

チャットのメッセージChat message

会話を角の丸い枠で1通ずつ積み上げる表示。自分の発言は右に色つきで、相手は左にアイコンつきで並び、時刻や「入力中」の動く点が添う。

表示例

サポート

ご用件をお聞かせください。

あなた

注文した品がまだ届きません

サポートが入力中

アイコン: Font Awesome 7 Free(fa-solid / fa-regular)

別名・英語表記

  • メッセージバブル
  • チャットバブル
  • 会話表示
  • チャットログ
  • トーク画面
  • Chat message

実装例

<div class="x-chat-message">
  <!-- role="log" は aria-live="polite" を暗黙に持つ。新しいメッセージを末尾に足すと、その分だけ読み上げる -->
  <div class="x-chat-message__log" role="log" aria-label="サポートとの会話">
    <div class="x-chat-message__row">
      <span class="x-chat-message__av" aria-hidden="true"><i class="fa-solid fa-robot"></i></span>
      <div class="x-chat-message__body">
        <p class="x-chat-message__meta">サポート <time datetime="2026-09-25T10:02">10:02</time></p>
        <p class="x-chat-message__bubble">ご用件をお聞かせください。</p>
      </div>
    </div>
    <div class="x-chat-message__row x-chat-message__row--me">
      <div class="x-chat-message__body">
        <!-- 自分の名前は画面に出さないが、読み上げでは誰の発言かを伝える -->
        <p class="x-chat-message__meta"><span class="x-chat-message__sr">あなた</span> <time datetime="2026-09-25T10:03">10:03</time></p>
        <p class="x-chat-message__bubble">注文した品がまだ届きません</p>
      </div>
    </div>
  </div>
  <!-- 入力中の表示はログの外に置く。ログの中で出し入れすると、そのたびに読み上げられる -->
  <p class="x-chat-message__typing">
    <span class="x-chat-message__dots" aria-hidden="true"><span></span><span></span><span></span></span>
    サポートが入力中
  </p>
</div>

AIへの指示文

「チャットのメッセージ(Chat message)」を実装してください。

## これは何か
会話を角の丸い枠で1通ずつ積み上げる表示。自分の発言は右に色つきで、相手は左にアイコンつきで並び、時刻や「入力中」の動く点が添う。

## 使いどころ
- 問い合わせ窓口やサポートのチャット、AIとの対話画面
- 2人以上のやり取りを、発言の順に追えるように見せたいとき

## 実装の要件
- div[role=log][aria-label] の中に、1通ずつ「名前と時刻 + 本文の枠」を積む。自分の発言は右寄せにする
- 枠の最大幅は親の8割ほどに留め、左右どちらの発言かが形でも分かるようにする
- 新着は末尾に足す(log は足された分を読み上げる)
- 誰の発言かを色だけで分けない。左右の位置・アイコン・名前のどれかを併せる
- 新しいメッセージが来るたびに一番下へ強制スクロールしない。過去を読み返している最中なら位置を保ち、「新しいメッセージ」の案内を出す
- 長いURLや英数字の続く文字列で枠が横にはみ出す。overflow-wrap: anywhere で折り返す
- 送信に失敗したメッセージを黙って消さない。枠に失敗の印と再送の操作を付けて残す

## アクセシビリティの要件
- メッセージを並べる領域に role="log" と aria-label を付ける。log は aria-live="polite" を暗黙に持ち、末尾に足した分を読み上げる
- 各メッセージに発言者の名前を入れる。画面に出さない自分の名前も、視覚的に隠した文字で読み上げに渡す
- 時刻は time 要素にして、datetime に機械で読める日時を書く
- 「入力中」の表示はログの外に置く。ログの中で出し入れすると、そのたびに読み上げられる
- 点が動くアニメーションは prefers-reduced-motion のときに止める

## 参考にする実装
この形を出発点にして、プロジェクトの命名規則・色・余白に合わせて調整してください。

```html
<div class="x-chat-message">
  <!-- role="log" は aria-live="polite" を暗黙に持つ。新しいメッセージを末尾に足すと、その分だけ読み上げる -->
  <div class="x-chat-message__log" role="log" aria-label="サポートとの会話">
    <div class="x-chat-message__row">
      <span class="x-chat-message__av" aria-hidden="true"><i class="fa-solid fa-robot"></i></span>
      <div class="x-chat-message__body">
        <p class="x-chat-message__meta">サポート <time datetime="2026-09-25T10:02">10:02</time></p>
        <p class="x-chat-message__bubble">ご用件をお聞かせください。</p>
      </div>
    </div>
    <div class="x-chat-message__row x-chat-message__row--me">
      <div class="x-chat-message__body">
        <!-- 自分の名前は画面に出さないが、読み上げでは誰の発言かを伝える -->
        <p class="x-chat-message__meta"><span class="x-chat-message__sr">あなた</span> <time datetime="2026-09-25T10:03">10:03</time></p>
        <p class="x-chat-message__bubble">注文した品がまだ届きません</p>
      </div>
    </div>
  </div>
  <!-- 入力中の表示はログの外に置く。ログの中で出し入れすると、そのたびに読み上げられる -->
  <p class="x-chat-message__typing">
    <span class="x-chat-message__dots" aria-hidden="true"><span></span><span></span><span></span></span>
    サポートが入力中
  </p>
</div>
```

```css
/* 貼った先のリセットに頼らないよう、この部品の中だけ border-box にする */
.x-chat-message,
.x-chat-message *:not([class*="fa-"]),
.x-chat-message *:not([class*="fa-"])::before,
.x-chat-message *: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-chat-message {
  position: relative;
  width: 300px;
  padding: 12px;
  border: 1px solid #e3e6ea;
  border-radius: 10px;
  background: #fff;
}
.x-chat-message__log {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.x-chat-message__row {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  max-width: 85%;
}
/* 自分の発言は右に寄せる。左右の位置と色の両方で、誰の発言かを見分けさせる */
.x-chat-message__row--me {
  align-self: flex-end;
}
.x-chat-message__av {
  display: grid;
  place-items: center;
  flex: none;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: #e8efff;
  color: #1d4ed8;
  font-size: 14px;
}
.x-chat-message__body {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.x-chat-message__row--me .x-chat-message__body {
  align-items: flex-end;
}
.x-chat-message__meta {
  margin: 0 0 3px;
  color: #5b6472;
  font-size: 11px;
}
.x-chat-message__bubble {
  margin: 0;
  padding: 8px 12px;
  border-radius: 4px 14px 14px 14px;
  background: #eef0f3;
  color: #1b2028;
  font-size: 13px;
  line-height: 1.6;
  overflow-wrap: anywhere;
}
.x-chat-message__row--me .x-chat-message__bubble {
  border-radius: 14px 4px 14px 14px;
  background: #1d4ed8;
  color: #fff;
}
/* 画面には出さず、読み上げにだけ渡す */
.x-chat-message__sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.x-chat-message__typing {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 10px 0 0;
  color: #5b6472;
  font-size: 11px;
}
.x-chat-message__dots {
  display: inline-flex;
  gap: 3px;
  padding: 6px 8px;
  border-radius: 10px;
  background: #eef0f3;
}
.x-chat-message__dots span {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: #5b6472;
  animation: x-chat-message-dot 1.2s infinite ease-in-out;
}
.x-chat-message__dots span:nth-child(2) {
  animation-delay: 0.15s;
}
.x-chat-message__dots span:nth-child(3) {
  animation-delay: 0.3s;
}
@keyframes x-chat-message-dot {
  0%, 60%, 100% { opacity: 0.3; transform: translateY(0); }
  30% { opacity: 1; transform: translateY(-3px); }
}
@media (prefers-reduced-motion: reduce) {
  .x-chat-message__dots span {
    animation: none;
    opacity: 0.7;
  }
}
```

## 出力の約束
- HTML と CSS を分けて出す。JavaScript が要るときは素の JavaScript で書き、UIフレームワークは使わない。
- 参考実装がアイコンに Font Awesome のクラスを使っている場合は、プロジェクトで使っているアイコンかインライン SVG に置き換えてよい。
- 上のアクセシビリティ要件を満たす。満たせない箇所があるときは、理由を添えて先に知らせる。
- 色・角丸・余白は既存のデザイントークンがあればそれを使う。無ければ CSS 変数として切り出す。

使いどころ

  • 問い合わせ窓口やサポートのチャット、AIとの対話画面
  • 2人以上のやり取りを、発言の順に追えるように見せたいとき

注意点

  • 誰の発言かを色だけで分けない。左右の位置・アイコン・名前のどれかを併せる
  • 新しいメッセージが来るたびに一番下へ強制スクロールしない。過去を読み返している最中なら位置を保ち、「新しいメッセージ」の案内を出す
  • 長いURLや英数字の続く文字列で枠が横にはみ出す。overflow-wrap: anywhere で折り返す
  • 送信に失敗したメッセージを黙って消さない。枠に失敗の印と再送の操作を付けて残す

アクセシビリティの要点

  • メッセージを並べる領域に role="log" と aria-label を付ける。log は aria-live="polite" を暗黙に持ち、末尾に足した分を読み上げる
  • 各メッセージに発言者の名前を入れる。画面に出さない自分の名前も、視覚的に隠した文字で読み上げに渡す
  • 時刻は time 要素にして、datetime に機械で読める日時を書く
  • 「入力中」の表示はログの外に置く。ログの中で出し入れすると、そのたびに読み上げられる
  • 点が動くアニメーションは prefers-reduced-motion のときに止める

関連するパーツ