16 CSS Bounce Animations05 / 16

Pure CSSMIT licensed

Bouncing Loading Dots

The canonical chat-typing / loading indicator — 3 dots inside a rounded speech bubble bouncing sequentially, signaling "loading" or "the other person is typing." iMessage blue bubble (#007aff) on a soft-gray chat surface. The pattern users search for when building chat interfaces, AI streaming responses, or any loading state that needs to feel calm rather than urgent.

Published

Live Demo
Try it

The code

<div class="bn-05">
  <p class="bn-05__label">Alex is typing…</p>
  <div class="bn-05__bubble" aria-label="Loading indicator">
    <span class="bn-05__dot" aria-hidden="true"></span>
    <span class="bn-05__dot" aria-hidden="true"></span>
    <span class="bn-05__dot" aria-hidden="true"></span>
  </div>
</div>
.bn-05 {
  --page-bg: #f2f2f7;
  --bubble: #e5e5ea;
  --ink: #1c1c1e;
  --sub: #8e8e93;
  --dot: #8e8e93;
  font-family: -apple-system,BlinkMacSystemFont,'SF Pro Text','Inter','Segoe UI',sans-serif;
  width: 100%;
  min-height: 100vh;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 24px;
  padding: 32px;
  background: var(--page-bg);
  color: var(--ink);
}

.bn-05 *,
.bn-05 *::before,
.bn-05 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.bn-05 ::selection {
  background: #007aff;
  color: #fff;
}

.bn-05__label {
  font-size: .9rem;
  font-weight: 600;
  color: var(--sub);
  letter-spacing: .02em;
}

.bn-05__bubble {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 22px 28px;
  border-radius: 26px;
  background: var(--bubble);
  border-bottom-left-radius: 10px;
  box-shadow: 0 4px 12px -4px rgba(28,28,30,.08);
}

.bn-05__dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--dot);
  animation: bn-05-dot 1.4s ease-in-out infinite;
}

.bn-05__dot:nth-child(2) {
  animation-delay: .16s;
}

.bn-05__dot:nth-child(3) {
  animation-delay: .32s;
}

@keyframes bn-05-dot {
  0%,
    60%,
    100% {
    transform: translateY(0);
    opacity: .4;
  }

  30% {
    transform: translateY(-8px);
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .bn-05__dot {
    animation: none!important;
    transform: none!important;
    opacity: .7!important;
  }
}
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 Bounce Animation 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: Bouncing Loading Dots
Source: https://codefronts.com/motion/css-bounce-animation/bouncing-loading-dots/

The canonical chat-typing / loading indicator — 3 dots inside a rounded speech bubble bouncing sequentially, signaling "loading" or "the other person is typing." iMessage blue bubble (#007aff) on a soft-gray chat surface. The pattern users search for when building chat interfaces, AI streaming responses, or any loading state that needs to feel calm rather than urgent.
## HTML
```html
<div class="bn-05">
  <p class="bn-05__label">Alex is typing…</p>
  <div class="bn-05__bubble" aria-label="Loading indicator">
    <span class="bn-05__dot" aria-hidden="true"></span>
    <span class="bn-05__dot" aria-hidden="true"></span>
    <span class="bn-05__dot" aria-hidden="true"></span>
  </div>
</div>
```
## CSS
```css
.bn-05 {
  --page-bg: #f2f2f7;
  --bubble: #e5e5ea;
  --ink: #1c1c1e;
  --sub: #8e8e93;
  --dot: #8e8e93;
  font-family: -apple-system,BlinkMacSystemFont,'SF Pro Text','Inter','Segoe UI',sans-serif;
  width: 100%;
  min-height: 100vh;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 24px;
  padding: 32px;
  background: var(--page-bg);
  color: var(--ink);
}

.bn-05 *,
.bn-05 *::before,
.bn-05 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.bn-05 ::selection {
  background: #007aff;
  color: #fff;
}

.bn-05__label {
  font-size: .9rem;
  font-weight: 600;
  color: var(--sub);
  letter-spacing: .02em;
}

.bn-05__bubble {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 22px 28px;
  border-radius: 26px;
  background: var(--bubble);
  border-bottom-left-radius: 10px;
  box-shadow: 0 4px 12px -4px rgba(28,28,30,.08);
}

.bn-05__dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--dot);
  animation: bn-05-dot 1.4s ease-in-out infinite;
}

.bn-05__dot:nth-child(2) {
  animation-delay: .16s;
}

.bn-05__dot:nth-child(3) {
  animation-delay: .32s;
}

@keyframes bn-05-dot {
  0%,
    60%,
    100% {
    transform: translateY(0);
    opacity: .4;
  }

  30% {
    transform: translateY(-8px);
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .bn-05__dot {
    animation: none!important;
    transform: none!important;
    opacity: .7!important;
  }
}
```

How this works

Three <span> elements inside a speech bubble share the same 10px circular dot shape. Each dot has the same @keyframes bn-05-dot animation cycling transform: translateY(0) → translateY(-8px) → translateY(0) over 1.4 seconds. The three dots use staggered animation-delay: dot 1 at 0s, dot 2 at 0.16s, dot 3 at 0.32s — creating the classic wave.

Two design details separate this from beginner tutorials: (1) the stagger delay (160ms) is exactly half the individual dot's up-phase — this creates the wave-motion illusion (dot 2 rises just as dot 1 begins descending). (2) The keyframe uses opacity: .4 → 1 → .4 alongside translateY — dots not currently "active" fade to 40% opacity, making the active dot pop. This dual-property animation is what iMessage's indicator does; static-opacity 3-dot loaders feel flat.

The demo centers the typing bubble as the hero at ~180px wide with a small label above signaling context. No chat thread wrapping; the bubble IS the demo.

Make it yours

  • Change the vertical bounce height by editing -8px — 4px reads as subtle (Slack pattern), 12px reads as energetic (Messenger). Match your app's motion vocabulary.
  • Adjust the wave speed by editing animation-duration: 1.4s. Faster (1s) reads as urgent, slower (1.8s) as calm.
  • Add a 4th dot for the Messenger pattern — same animation with animation-delay: .48s. More than 4 dots reads as morse code, not typing.
  • For AI streaming responses (ChatGPT / Claude pattern), use lighter background (avoiding the "someone is typing" mental model). Not a bubble — just dots on transparent background.
  • For Tailwind: animate-[bn05_1.4s_ease-in-out_infinite] + register keyframes in tailwind.config.js. See FAQ #4.

Gotchas — read before shipping

  • The stagger delay MUST be less than the animation duration divided by number of dots. For 3 dots at 1.4s cycle, stagger of 160ms works. Larger stagger and dots don't overlap — they read as 3 separate blinks, not a wave.
  • For chat contexts, the typing indicator should hide when the other person stops typing. If your indicator persists after they stopped, users get confused. Show for max 5 seconds without additional signal, then hide.
  • Do NOT use opacity: 0 at rest — .4 opacity keeps dots visible so users see the shape. Full 0-opacity dots vanish and the bubble looks empty half the cycle.
  • For accessibility, use aria-label on the bubble container announcing what's happening ("Loading", "Alex is typing") — not on individual dots. Screen readers get meaningful context, not "dot, dot, dot."
  • AI streaming responses are DIFFERENT from chat typing — AI uses a caret / cursor blink, not typing dots. Typing dots imply "a person is composing." AI streaming implies "text is being generated." Match the mental model.
  • For dark-mode chat, swap the iOS system gray for the iOS dark chat surface (#1c1c1e) + the iMessage dark-mode blue (#0a84ff). Never mechanically invert.
  • For prefers-reduced-motion, show static dots at consistent opacity — screen readers announce via aria-label. Information preserved, vestibular motion disabled.

Browser support

ChromeSafariFirefoxEdge
45+10+40+45+

transform + opacity + @keyframes. Universal, GPU-accelerated.

Techniques used in this demo

Search CodeFronts

Loading…