30 CSS Badges02 / 30

Pure CSSMIT licensed

Typing Indicator

The three bouncing dots are the convention. Without the animation the badge could mean "typed recently" or "about to type." The motion defines the tense: present continuous, active right now.

Published

Live Demo
Try it

The code

<div class="bdg-02-stage">
  <div class="bdg-02-wrap">
    <div class="bdg-02-join">
      <div class="bdg-02-join-dot"></div>
      Ana Sousa joined the conversation
    </div>

    <div class="bdg-02-msg">Let me check the latest build and get back to you.</div>

    <div class="bdg-02-msg bdg-02-sent">Sure — does the badge spec look right to you?</div>

    <div class="bdg-02-bubble">
      <div class="bdg-02-inner">
        <svg class="bdg-02-avatar" viewBox="0 0 20 20" aria-hidden="true">
          <circle cx="10" cy="10" r="10" fill="#3d7ebf"/>
          <text x="10" y="14.5" text-anchor="middle" font-size="10" fill="white" font-family="sans-serif" font-weight="bold">A</text>
        </svg>
        <span class="bdg-02-name">Ana</span>
        <div class="bdg-02-dots">
          <div class="bdg-02-dot"></div>
          <div class="bdg-02-dot"></div>
          <div class="bdg-02-dot"></div>
        </div>
      </div>
    </div>
  </div>
</div>
.bdg-02-stage {
  background: #ebe4d8;
  padding: 40px 36px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  justify-content: center;
  align-items: center;
  width: 100%;
  min-height: 100vh;
}

.bdg-02-wrap {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
  max-width: 360px;
}

.bdg-02-msg {
  align-self: flex-end;
  background: #fff;
  border-radius: 18px 18px 4px 18px;
  padding: 10px 16px;
  font-family: system-ui, "Bricolage Grotesque", sans-serif;
  font-size: 14px;
  color: #2a2018;
  box-shadow: 0 1px 4px rgba(0,0,0,0.08);
  max-width: 80%;
}

.bdg-02-sent {
  background: #1a1612;
  color: #f0ece0;
  border-radius: 18px 18px 18px 4px;
  align-self: flex-start;
}

.bdg-02-bubble {
  align-self: flex-start;
}

.bdg-02-inner {
  background: #fff;
  border-radius: 18px 18px 18px 4px;
  padding: 12px 16px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  box-shadow: 0 1px 4px rgba(0,0,0,0.08);
}

.bdg-02-avatar {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  flex-shrink: 0;
}

.bdg-02-name {
  font-family: system-ui, "Bricolage Grotesque", sans-serif;
  font-size: 11px;
  letter-spacing: 0.08em;
  color: #888;
  text-transform: uppercase;
}

.bdg-02-dots {
  display: flex;
  gap: 3px;
  align-items: center;
}

.bdg-02-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #aaa;
}

.bdg-02-dot:nth-child(1) {
  animation: bdg-02-bounce 1.4s ease-in-out infinite 0ms;
}

.bdg-02-dot:nth-child(2) {
  animation: bdg-02-bounce 1.4s ease-in-out infinite 160ms;
}

.bdg-02-dot:nth-child(3) {
  animation: bdg-02-bounce 1.4s ease-in-out infinite 320ms;
}

@keyframes bdg-02-bounce {
  0%,
    60%,
    100% {
    transform: translateY(0);
  }

  30% {
    transform: translateY(-5px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .bdg-02-dot {
    animation: none;
  }
}

.bdg-02-join {
  align-self: center;
  background: rgba(255,255,255,0.7);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border-radius: 20px;
  padding: 6px 14px;
  display: flex;
  align-items: center;
  gap: 7px;
  font-family: system-ui, "Bricolage Grotesque", sans-serif;
  font-size: 11px;
  letter-spacing: 0.06em;
  color: #666;
  box-shadow: 0 1px 4px rgba(0,0,0,0.1);
}

.bdg-02-join-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #0cce6b;
}
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Badge from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Typing Indicator
Source: https://codefronts.com/snippets/css-badges/typing-indicator/

The three bouncing dots are the convention. Without the animation the badge could mean "typed recently" or "about to type." The motion defines the tense: present continuous, active right now.
## HTML
```html
<div class="bdg-02-stage">
  <div class="bdg-02-wrap">
    <div class="bdg-02-join">
      <div class="bdg-02-join-dot"></div>
      Ana Sousa joined the conversation
    </div>

    <div class="bdg-02-msg">Let me check the latest build and get back to you.</div>

    <div class="bdg-02-msg bdg-02-sent">Sure — does the badge spec look right to you?</div>

    <div class="bdg-02-bubble">
      <div class="bdg-02-inner">
        <svg class="bdg-02-avatar" viewBox="0 0 20 20" aria-hidden="true">
          <circle cx="10" cy="10" r="10" fill="#3d7ebf"/>
          <text x="10" y="14.5" text-anchor="middle" font-size="10" fill="white" font-family="sans-serif" font-weight="bold">A</text>
        </svg>
        <span class="bdg-02-name">Ana</span>
        <div class="bdg-02-dots">
          <div class="bdg-02-dot"></div>
          <div class="bdg-02-dot"></div>
          <div class="bdg-02-dot"></div>
        </div>
      </div>
    </div>
  </div>
</div>
```
## CSS
```css
.bdg-02-stage {
  background: #ebe4d8;
  padding: 40px 36px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  justify-content: center;
  align-items: center;
  width: 100%;
  min-height: 100vh;
}

.bdg-02-wrap {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
  max-width: 360px;
}

.bdg-02-msg {
  align-self: flex-end;
  background: #fff;
  border-radius: 18px 18px 4px 18px;
  padding: 10px 16px;
  font-family: system-ui, "Bricolage Grotesque", sans-serif;
  font-size: 14px;
  color: #2a2018;
  box-shadow: 0 1px 4px rgba(0,0,0,0.08);
  max-width: 80%;
}

.bdg-02-sent {
  background: #1a1612;
  color: #f0ece0;
  border-radius: 18px 18px 18px 4px;
  align-self: flex-start;
}

.bdg-02-bubble {
  align-self: flex-start;
}

.bdg-02-inner {
  background: #fff;
  border-radius: 18px 18px 18px 4px;
  padding: 12px 16px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  box-shadow: 0 1px 4px rgba(0,0,0,0.08);
}

.bdg-02-avatar {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  flex-shrink: 0;
}

.bdg-02-name {
  font-family: system-ui, "Bricolage Grotesque", sans-serif;
  font-size: 11px;
  letter-spacing: 0.08em;
  color: #888;
  text-transform: uppercase;
}

.bdg-02-dots {
  display: flex;
  gap: 3px;
  align-items: center;
}

.bdg-02-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #aaa;
}

.bdg-02-dot:nth-child(1) {
  animation: bdg-02-bounce 1.4s ease-in-out infinite 0ms;
}

.bdg-02-dot:nth-child(2) {
  animation: bdg-02-bounce 1.4s ease-in-out infinite 160ms;
}

.bdg-02-dot:nth-child(3) {
  animation: bdg-02-bounce 1.4s ease-in-out infinite 320ms;
}

@keyframes bdg-02-bounce {
  0%,
    60%,
    100% {
    transform: translateY(0);
  }

  30% {
    transform: translateY(-5px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .bdg-02-dot {
    animation: none;
  }
}

.bdg-02-join {
  align-self: center;
  background: rgba(255,255,255,0.7);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border-radius: 20px;
  padding: 6px 14px;
  display: flex;
  align-items: center;
  gap: 7px;
  font-family: system-ui, "Bricolage Grotesque", sans-serif;
  font-size: 11px;
  letter-spacing: 0.06em;
  color: #666;
  box-shadow: 0 1px 4px rgba(0,0,0,0.1);
}

.bdg-02-join-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #0cce6b;
}
```

How this works

Three dots inside a rounded pill, each bouncing on its own delay via @keyframes translateY. The staggered delays (0ms, 160ms, 320ms) create the classic “other side is typing” wave — the same pattern iMessage, WhatsApp, Discord, Slack, Facebook Messenger, and every modern chat app ships.

Zero JavaScript. The bounce is entirely CSS keyframes with animation-delay per dot. The pill's rounded background sits behind the dots as the visual anchor.

Make it yours

  • Rebrand pill and dot colour from --pill-bg and --dot on the wrapper.
  • Tune bounce speed via the @keyframes duration — 900ms for slow / calm, 500ms for eager.
  • Add a fourth dot for a heavier wave — just add another element with a longer delay.
  • Change bounce distance via the translateY value in the keyframes — 4px subtle, 10px pronounced.
  • Swap dots for tiny circles-with-fade by animating opacity instead of translateY.

Gotchas — read before shipping

  • The three delays must be evenly spaced fractions of the total animation duration or the wave breaks into an arrhythmic stagger.
  • Continuous keyframe motion needs @media (prefers-reduced-motion:reduce) { animation: none } — the typing pattern is a common vestibular trigger.
  • For screen-reader parity, wrap in an aria-live="polite" region with visually-hidden text (&ldquo;Alex is typing&rdquo;) so blind users get the same signal.
  • Don't shrink the pill below 40×24px — smaller and the dots crowd; 44×24px matches iOS/Android chat conventions.

Browser support

ChromeSafariFirefoxEdge
76+9+103+76+

Floor set by backdrop-filter as this demo is written. The core technique itself goes back further — Chrome 43+.

CSS keyframes with per-element animation-delay is universally supported. No polyfills needed.

Techniques used in this demo

Search CodeFronts

Loading…