Typing Indicator

Shows that another user is typing.

Priya is typing… Priya and another person are typing… Several people are typing…

html

<div class="typing" role="status" aria-live="polite" data-count="1" style="--speed:900ms;--dot:#241430;">
  <span class="typing__avatar" aria-hidden="true"></span>
  <span class="typing__text typing__text--c1">Priya is typing…</span>
  <span class="typing__text typing__text--c2">Priya and another person are typing…</span>
  <span class="typing__text typing__text--c3plus">Several people are typing…</span>
  <span class="typing__dots" aria-hidden="true"><i></i><i></i><i></i></span>
</div>
<style>
.typing { display: inline-flex; align-items: center; gap: 8px; font: 13px system-ui, sans-serif; color: #24143099; background: #FAF7F2; border: 1px solid #24143020; border-radius: 999px; padding: 6px 12px; }
.typing__avatar { width: 20px; height: 20px; border-radius: 50%; background: #24143014; flex-shrink: 0; }
.typing__text { display: none; }
[data-count="1"] .typing__text--c1 { display: inline; }
[data-count="2"] .typing__text--c2 { display: inline; }
[data-count="3plus"] .typing__text--c3plus { display: inline; }
.typing__dots { display: inline-flex; gap: 3px; }
.typing__dots i { width: 5px; height: 5px; border-radius: 50%; background: var(--dot); display: inline-block; }
@keyframes typing-bounce { 0%, 60%, 100% { transform: translateY(0); opacity: .5; } 30% { transform: translateY(-3px); opacity: 1; } }
@media (prefers-reduced-motion: no-preference) {
  .typing__dots i { animation: typing-bounce var(--speed) ease-in-out infinite; }
  .typing__dots i:nth-child(2) { animation-delay: calc(var(--speed) / 6); }
  .typing__dots i:nth-child(3) { animation-delay: calc(var(--speed) / 3); }
}
</style>

tailwind

<div role="status" aria-live="polite" class="typing-tw inline-flex items-center gap-2 text-[13px] text-[#24143099] bg-[#FAF7F2] border border-[#24143020] rounded-full px-3 py-1.5" data-count="1" style="--speed:900ms;--dot:#241430">
  <span aria-hidden="true" class="w-5 h-5 rounded-full bg-[#24143014] flex-shrink-0"></span>
  <span class="typing-tw__text typing-tw__text--c1">Priya is typing…</span>
  <span class="typing-tw__text typing-tw__text--c2">Priya and another person are typing…</span>
  <span class="typing-tw__text typing-tw__text--c3plus">Several people are typing…</span>
  <span aria-hidden="true" class="typing-tw__dots inline-flex gap-[3px]"><i></i><i></i><i></i></span>
</div>
<style>
.typing-tw__text { display: none; }
[data-count="1"] .typing-tw__text--c1 { display: inline; }
[data-count="2"] .typing-tw__text--c2 { display: inline; }
[data-count="3plus"] .typing-tw__text--c3plus { display: inline; }
.typing-tw__dots i { width: 5px; height: 5px; border-radius: 50%; background: var(--dot); display: inline-block; }
@keyframes typing-tw-bounce { 0%, 60%, 100% { transform: translateY(0); opacity: .5; } 30% { transform: translateY(-3px); opacity: 1; } }
@media (prefers-reduced-motion: no-preference) {
  .typing-tw__dots i { animation: typing-tw-bounce var(--speed) ease-in-out infinite; }
  .typing-tw__dots i:nth-child(2) { animation-delay: calc(var(--speed) / 6); }
  .typing-tw__dots i:nth-child(3) { animation-delay: calc(var(--speed) / 3); }
}
</style>

react

export function TypingIndicator({
  names = "Priya",
  count = "1",
  dotColor = "#241430",
  showAvatar = true,
  speed = 900,
}) {
  const text = {
    "1": `${names} is typing…`,
    "2": `${names} and another person are typing…`,
    "3plus": "Several people are typing…",
  }[count];
  return (
    <div role="status" aria-live="polite" style={{ display: "inline-flex", alignItems: "center", gap: 8, fontSize: 13,
      color: "#24143099", background: "#FAF7F2", border: "1px solid #24143020", borderRadius: 999, padding: "6px 12px" }}>
      {showAvatar && <span aria-hidden="true" style={{ width: 20, height: 20, borderRadius: "50%", background: "#24143014", flexShrink: 0 }} />}
      <span>{text}</span>
      <span aria-hidden="true" style={{ display: "inline-flex", gap: 3 }}>
        {[0, 1, 2].map((i) => (
          <i key={i} className="typing-dot" style={{ width: 5, height: 5, borderRadius: "50%", background: dotColor, display: "inline-block",
            "--typing-delay": `${(speed / 6) * i}ms` }} />
        ))}
      </span>
      <style>{`
        @media (prefers-reduced-motion: no-preference) {
          .typing-dot { animation: typing-react-bounce ${speed}ms ease-in-out infinite; animation-delay: var(--typing-delay); }
          @keyframes typing-react-bounce { 0%, 60%, 100% { transform: translateY(0); opacity: .5; } 30% { transform: translateY(-3px); opacity: 1; } }
        }
      `}</style>
    </div>
  );
}