20 CSS Loaders16 / 20

Pure CSSMIT licensed

CSS Typing Dots Loader

Four conversational CSS loaders — a chat-bubble typing indicator, an inline AI-thinking status, a cursor blink, and a bouncing dot trio — matching the patterns used in messaging and AI product UIs.

Published

Live Demo
Try it

This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.

The code

<div class="ld-16">
  <div class="ld-16__stage">
    <div>
      <div class="ld-16__label-row">Chat bubble</div>
      <div class="ld-16__bubble"><span></span><span></span><span></span></div>
    </div>
.ld-16,
.ld-16 *,
.ld-16 *::before,
.ld-16 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.ld-16 {
  --bg: #1c1c2e;
  --c1: #e2e8f0;
  --c2: #94a3b8;
  --c3: #38bdf8;
  --c4: #a78bfa;
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  font-family: 'Segoe UI',sans-serif;
}

.ld-16__stage {
  display: flex;
  gap: 28px;
  flex-direction: column;
  align-items: flex-start;
  padding: 40px;
  max-width: 400px;
  width: 100%;
}

.ld-16__label-row {
  color: rgba(255,255,255,.3);
  font-size: 10px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  margin-bottom: 4px;
}
/* Chat bubble with typing dots */

.ld-16__bubble {
  background: #2d2d44;
  border-radius: 18px 18px 18px 4px;
  padding: 14px 18px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 180px;
}

.ld-16__bubble span {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--c2);
  animation: ld-16-typing 1.2s ease-in-out infinite;
}

.ld-16__bubble span:nth-child(1) {
  animation-delay: 0s;
}

.ld-16__bubble span:nth-child(2) {
  animation-delay: .2s;
}

.ld-16__bubble span:nth-child(3) {
  animation-delay: .4s;
}

@keyframes ld-16-typing {
  0%,
    60%,
    100% {
    transform: translateY(0);
    opacity: .4;
  }

  30% {
    transform: translateY(-6px);
    opacity: 1;
  }
}
/* Inline text dots */

.ld-16__inline {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--c2);
  font-size: 14px;
}

.ld-16__inline-dots {
  display: flex;
  gap: 3px;
}

.ld-16__inline-dots span {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--c3);
  animation: ld-16-typing 1s ease-in-out infinite;
}

.ld-16__inline-dots span:nth-child(1) {
  animation-delay: 0s;
}

.ld-16__inline-dots span:nth-child(2) {
  animation-delay: .15s;
}

.ld-16__inline-dots span:nth-child(3) {
  animation-delay: .3s;
}
/* Cursor blink */

.ld-16__cursor-wrap {
  color: var(--c1);
  font-size: 16px;
  display: flex;
  align-items: center;
  gap: 2px;
}

.ld-16__cursor {
  width: 2px;
  height: 20px;
  background: var(--c4);
  animation: ld-16-blink 1s step-end infinite;
}

@keyframes ld-16-blink {
  0%,
    100% {
    opacity: 1;
  }

  50% {
    opacity: 0;
  }
}
/* Processing dots large */

.ld-16__processing {
  display: flex;
  gap: 10px;
  align-items: center;
}

.ld-16__processing span {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--c4);
  animation: ld-16-proc 1.4s ease-in-out infinite;
}

.ld-16__processing span:nth-child(1) {
  animation-delay: 0s;
}

.ld-16__processing span:nth-child(2) {
  animation-delay: .2s;
  background: var(--c3);
}

.ld-16__processing span:nth-child(3) {
  animation-delay: .4s;
  background: var(--c4);
}

@keyframes ld-16-proc {
  0%,
    80%,
    100% {
    transform: scale(.6) translateY(0);
    opacity: .4;
  }

  40% {
    transform: scale(1) translateY(-10px);
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ld-16__bubble span,
    .ld-16__inline-dots span,
    .ld-16__cursor,
    .ld-16__processing span {
    animation: none;
  }
}
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 Loader 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: CSS Typing Dots Loader
Source: https://codefronts.com/motion/css-loader-designs/css-typing-dots-loader/

Four conversational CSS loaders — a chat-bubble typing indicator, an inline AI-thinking status, a cursor blink, and a bouncing dot trio — matching the patterns used in messaging and AI product UIs.
## HTML
```html
<div class="ld-16">
  <div class="ld-16__stage">
    <div>
      <div class="ld-16__label-row">Chat bubble</div>
      <div class="ld-16__bubble"><span></span><span></span><span></span></div>
    </div>
```
## CSS
```css
.ld-16,
.ld-16 *,
.ld-16 *::before,
.ld-16 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.ld-16 {
  --bg: #1c1c2e;
  --c1: #e2e8f0;
  --c2: #94a3b8;
  --c3: #38bdf8;
  --c4: #a78bfa;
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  font-family: 'Segoe UI',sans-serif;
}

.ld-16__stage {
  display: flex;
  gap: 28px;
  flex-direction: column;
  align-items: flex-start;
  padding: 40px;
  max-width: 400px;
  width: 100%;
}

.ld-16__label-row {
  color: rgba(255,255,255,.3);
  font-size: 10px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  margin-bottom: 4px;
}
/* Chat bubble with typing dots */

.ld-16__bubble {
  background: #2d2d44;
  border-radius: 18px 18px 18px 4px;
  padding: 14px 18px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 180px;
}

.ld-16__bubble span {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--c2);
  animation: ld-16-typing 1.2s ease-in-out infinite;
}

.ld-16__bubble span:nth-child(1) {
  animation-delay: 0s;
}

.ld-16__bubble span:nth-child(2) {
  animation-delay: .2s;
}

.ld-16__bubble span:nth-child(3) {
  animation-delay: .4s;
}

@keyframes ld-16-typing {
  0%,
    60%,
    100% {
    transform: translateY(0);
    opacity: .4;
  }

  30% {
    transform: translateY(-6px);
    opacity: 1;
  }
}
/* Inline text dots */

.ld-16__inline {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--c2);
  font-size: 14px;
}

.ld-16__inline-dots {
  display: flex;
  gap: 3px;
}

.ld-16__inline-dots span {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--c3);
  animation: ld-16-typing 1s ease-in-out infinite;
}

.ld-16__inline-dots span:nth-child(1) {
  animation-delay: 0s;
}

.ld-16__inline-dots span:nth-child(2) {
  animation-delay: .15s;
}

.ld-16__inline-dots span:nth-child(3) {
  animation-delay: .3s;
}
/* Cursor blink */

.ld-16__cursor-wrap {
  color: var(--c1);
  font-size: 16px;
  display: flex;
  align-items: center;
  gap: 2px;
}

.ld-16__cursor {
  width: 2px;
  height: 20px;
  background: var(--c4);
  animation: ld-16-blink 1s step-end infinite;
}

@keyframes ld-16-blink {
  0%,
    100% {
    opacity: 1;
  }

  50% {
    opacity: 0;
  }
}
/* Processing dots large */

.ld-16__processing {
  display: flex;
  gap: 10px;
  align-items: center;
}

.ld-16__processing span {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--c4);
  animation: ld-16-proc 1.4s ease-in-out infinite;
}

.ld-16__processing span:nth-child(1) {
  animation-delay: 0s;
}

.ld-16__processing span:nth-child(2) {
  animation-delay: .2s;
  background: var(--c3);
}

.ld-16__processing span:nth-child(3) {
  animation-delay: .4s;
  background: var(--c4);
}

@keyframes ld-16-proc {
  0%,
    80%,
    100% {
    transform: scale(.6) translateY(0);
    opacity: .4;
  }

  40% {
    transform: scale(1) translateY(-10px);
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ld-16__bubble span,
    .ld-16__inline-dots span,
    .ld-16__cursor,
    .ld-16__processing span {
    animation: none;
  }
}
```

How this works

The chat bubble typing indicator wraps three dots inside a styled div with border-radius: 18px 18px 18px 4px to create the asymmetric chat-bubble tail. Each dot uses a single keyframe: translateY(0)translateY(-6px)translateY(0) with ease-in-out, cascaded with 0.2s delays per dot. The dots also fade between opacity:0.4 and opacity:1 in the same keyframe so they dim as they rise, reinforcing depth.

The cursor blink uses animation-timing-function: step-end so the cursor snaps between visible and invisible rather than fading — this matches the hard-cut behaviour of real text cursors. The processing dots use cubic-bezier easing on a compound transform of scale and translateY in the same keyframe, making each dot simultaneously grow and rise at its peak, then shrink as it falls back.

Make it yours

  • Match your chat app's bubble shape by adjusting the four border-radius values on .ld-16__bubble — use 18px 18px 4px 18px for an outgoing-message variant.
  • Slow the typing animation to 1.8s for a more contemplative AI-thinking feel, or drop to 0.8s for an eager autocomplete indicator.
  • Change dot size from 8px to 5px for a compact inline variant that fits within a single line of body text.
  • Add a fourth dot to the bounce sequence by adding a span:nth-child(4) rule with animation-delay: .6s for a slightly longer visual rhythm.
  • Swap translateY(-6px) for scale(1.4) to create a radial pulse variant instead of an upward bounce — useful for non-directional contexts.

Gotchas — read before shipping

  • The cursor blink must use step-end not steps(1, end) — while semantically equivalent, some linters flag the latter; use the keyword form for clarity.
  • The bubble border-radius asymmetry only creates a bottom-left tail pointing left — for a right-aligned outgoing bubble, swap the last two radius values and mirror with transform: scaleX(-1).
  • Staggered animation-delay on dots causes the first animation cycle to start at different offsets — add animation-fill-mode: both to each dot to prevent a flash on the initial render.

Browser support

ChromeSafariFirefoxEdge
49+9+44+49+

Typing indicator loaders use universal CSS; step-end timing function is supported in all modern browsers.

Techniques used in this demo

Search CodeFronts

Loading…