Chat

A conversation in bubbles. .chat holds a <ul> of messages, and each <li class="bubble"> is one message.

Standalone

<link rel="stylesheet" href="https://esm.sh/@erikt/ui/chat.css" />

Example

An example of all parts of the Chat component.

New chat

How can I help you today?


Morning, erikt!

What are we working on today? Press send to start a new conversation

Demo is read only. Press send to send messages.

Bubble

Each message is an <li class="bubble">. When the <li> holds more than the message, like an avatar, put the bubble on a <div> inside it instead. Add me for the current user's messages. They move to the other side and use the primary color. Give the chat role="log" and a label, so new messages are announced as they arrive.

Other messages have no background, which suits long answers from an assistant. Add secondary to put them in a gray bubble, like in a chat between people.

  • Which fruit has the most vitamin C?
  • Per 100 grams, a few stand out:

    • Guava, about 230 mg
    • Kiwi, about 90 mg
    • Orange, about 50 mg
  • Didn't expect guava!
  • Most people don't. It has about four times as much as an orange.
<div class="chat" role="log" aria-label="Conversation">
  <ul>
    <li class="bubble me">Which fruit has the most vitamin C?</li>
    <li class="bubble ghost">
      <p>Per 100 grams, a few stand out:</p>
      <ul>
        <li><strong>Guava</strong>, about 230 mg</li>
        <li><strong>Kiwi</strong>, about 90 mg</li>
        <li><strong>Orange</strong>, about 50 mg</li>
      </ul>
    </li>
    <li class="bubble me">Didn't expect guava!</li>
    <li class="bubble">
      Most people don't. It has about four times as much as an orange.
    </li>
  </ul>
</div>

Avatar

Put an Avatar first in the <li> to show who sent the message. It sits next to the last bubble, so one <li> can hold several bubbles from the same sender. On me messages it moves to the other side.

Give it role="img" and the sender's name in aria-label, so screen readers read the name instead of the initials. For an image avatar, put the name in alt.

  • AL
    Did you see the new designs?
    I think the colors are much better now.
  • JR
    Just looked. I love the new buttons.
<div class="chat" role="log" aria-label="Conversation">
  <ul>
    <li>
      <span class="avatar" role="img" aria-label="Alex Lee">AL</span>
      <div class="bubble">Did you see the new designs?</div>
      <div class="bubble">I think the colors are much better now.</div>
    </li>
    <li>
      <span class="avatar" role="img" aria-label="You">JR</span>
      <div class="bubble me">Just looked. I love the new buttons.</div>
    </li>
  </ul>
</div>

Pending

Show that a reply is on its way with a Shimmer in the <li>, in place of the bubble. It gets the bubble's padding, so its text lines up with the reply. Swap it for a bubble once the reply arrives. If the reply streams in, set aria-busy="true" on the bubble until it is done, so screen readers read it once instead of word by word.

  • What's the tallest mountain in Europe?
  • Thinking...

<div class="chat" role="log" aria-label="Conversation">
  <ul>
    <li class="bubble me">What's the tallest mountain in Europe?</li>
    <li>
      <p class="shimmer">Thinking...</p>
    </li>
  </ul>
</div>

Jump to latest

Put a <button class="jump"> last in the chat. It sticks to the bottom while there are newer messages below, and hides once the chat is scrolled to the end. Scroll up in the example to see it. Give the chat a height so it scrolls.

Showing and hiding needs no JavaScript. Scrolling down on click does. Move focus to the chat on click too, since the button hides once the chat is scrolled down. In browsers without scroll-state queries the button is always visible.

  • Can you help me plan a trip to Lisbon?
  • Sure. How many days do you have?
  • Four, in May.
  • Four days is a good length. Spend the first in Alfama and Baixa, the second in Belém, and take a day trip to Sintra on the third.
  • And the last day?
  • Keep it slow. Walk along the river to LX Factory, and end the day at a miradouro for the sunset.
<div
  class="chat"
  role="log"
  aria-label="Conversation"
  tabindex="0"
  style="height: 16rem"
>
  <ul>
    <li class="bubble me">Can you help me plan a trip to Lisbon?</li>
    <li class="bubble">Sure. How many days do you have?</li>
    <li class="bubble me">Four, in May.</li>
    <li class="bubble">
      Four days is a good length. Spend the first in Alfama and Baixa, the
      second in Belém, and take a day trip to Sintra on the third.
    </li>
    <li class="bubble me">And the last day?</li>
    <li class="bubble">
      Keep it slow. Walk along the river to LX Factory, and end the day at a
      miradouro for the sunset.
    </li>
  </ul>

  <button
    class="jump secondary square round"
    aria-label="Jump to latest"
    onclick="
      const chat = this.parentElement;
      chat.focus();
      chat.scrollTo({ top: chat.scrollHeight, behavior: 'smooth' });
    "
  >
    <svg>...</svg>
  </button>
</div>

API reference

Type Classes / attributes What it does
Chat .chat The scrolling container. Holds a ul of bubbles and an optional jump button
Bubble .bubble On an li, or a div inside one. One message, with no background
Avatar .avatar First in an li. Shows who sent the bubbles, next to the last one
Sender .me On a bubble. Moves it to the other side and uses the primary color
Style .secondary On a bubble. A gray background
Jump to latest .jump On a button last in .chat. Shows while there are newer messages below
Radius --ui-chat-radius Corner radius of a bubble
Tail --ui-chat-tail Corner radius where bubbles meet

Search 5021 icons

Type a name to find icons from the Tabler icon set.

      Move Open Esc Close