Skip to content

Chat bubble

For stacking what was said in order, so it's clear who spoke and when.

Data display

Live preview

Source

<div class="dc-chat">
  <p class="dc-chat__date">3월 14일 목요일</p>
  <div class="dc-chat__turn">
    <span class="dc-chat__avatar" aria-hidden="true">최</span>
    <div class="dc-chat__stack">
      <p class="dc-chat__who">최영호 소장</p>
      <p class="dc-chat__msg">
        <span class="dc-chat__bubble">내일 아침 8시에 자재 들어옵니다. 두 분만 먼저 나와 주세요.</span>
        <span class="dc-chat__time">오전 9:12</span>
      </p>
    </div>
  </div>
  <div class="dc-chat__turn dc-chat__turn--mine">
    <div class="dc-chat__stack">
      <p class="dc-chat__msg">
        <span class="dc-chat__bubble dc-chat__bubble--mine">네, 저랑 응우옌 씨가 나가겠습니다.</span>
        <span class="dc-chat__time">오전 9:14<span class="dc-chat__read">읽음</span></span>
      </p>
    </div>
  </div>
</div>
.dc-chat {
  display: flex; flex-direction: column; gap: var(--dc-3);
  padding: var(--dc-4);
  background: var(--dc-bg);
  font-family: var(--dc-font);
}
.dc-chat__date {
  align-self: center; margin: 0;
  padding: var(--dc-1) var(--dc-3);
  border-radius: var(--dc-r-full);
  background: var(--dc-sunken); color: var(--dc-text-mute);
  font-size: 11px;
}
.dc-chat__system {
  align-self: center; max-width: 86%; margin: 0;
  padding: var(--dc-2) var(--dc-3);
  border-radius: var(--dc-r-sm);
  background: var(--dc-info-soft); color: var(--dc-text-sub);
  font-size: 12px; line-height: 1.5; text-align: center;
}
.dc-chat__turn { display: flex; align-items: flex-start; gap: var(--dc-2); }
.dc-chat__turn--mine { justify-content: flex-end; }
.dc-chat__avatar {
  flex: 0 0 auto; display: grid; place-items: center;
  width: 36px; height: 36px; border-radius: var(--dc-r-full);
  background: var(--dc-brand-soft); color: var(--dc-brand-text);
  font-size: 14px; font-weight: 700;
}
.dc-chat__stack {
  display: flex; flex-direction: column; gap: var(--dc-1);
  min-width: 0; max-width: 80%;
}
.dc-chat__who { margin: 0; font-size: 12px; color: var(--dc-text-sub); }
.dc-chat__msg { display: flex; align-items: flex-end; gap: var(--dc-2); margin: 0; }
.dc-chat__turn--mine .dc-chat__msg { flex-direction: row-reverse; }
.dc-chat__bubble {
  padding: var(--dc-3);
  border: 1px solid var(--dc-line);
  border-radius: var(--dc-r-md);
  background: var(--dc-surface); color: var(--dc-text);
  font-size: 14px; line-height: 1.6;
  overflow-wrap: anywhere;
}
.dc-chat__bubble--mine {
  background: var(--dc-brand); border-color: var(--dc-brand);
  color: var(--dc-text-on);
}
.dc-chat__time {
  flex: 0 0 auto;
  display: flex; flex-direction: column; align-items: flex-end;
  font-size: 10px; line-height: 1.4;
  color: var(--dc-text-mute); white-space: nowrap;
}
.dc-chat__read { color: var(--dc-brand-text); font-weight: 700; }
.dc-chat__photo {
  display: block; width: 180px; aspect-ratio: 4 / 3;
  margin-bottom: var(--dc-2);
  border-radius: var(--dc-r-sm);
  background: linear-gradient(135deg, var(--dc-brand-soft), var(--dc-sunken));
}

How to use it

  • Mine on the right, theirs on the left — side carries the distinction first
  • When one person speaks twice, keep the name and avatar on the first bubble only
  • Times sit small beside the bubble; dates become a centered divider

Watch out for

  • Don't signal the sender by color alone — side or name has to say it too
  • Let long URLs and long words wrap so a bubble can't push past the screen

Next