Overview
Typing dots are a compact indefinite loader: three small circles use staggered animation-delay so they read as a wave instead of blinking in sync. The pattern is not inherently chat-specific, and the showcase should vary the expression method: a horizontal translateY() row wave, a vertical translateX() column wave, or a no-travel scale pulse.
When to use it
Use this when the system is actively working but cannot report determinate progress. It is strongest for short waits in tight UI: composing a reply, checking a field, or waiting on an assistant step. Avoid it for long-running work where users need progress, retry affordances, or a cancel path.
How it works
Build a semantic status wrapper with role="status" and aria-live="polite", then mark the dot group aria-hidden. The dots use identical size and shape inside each expression method; :nth-child() offsets the second and third dot with staggered animation-delay. Reserve the wrapper footprint for the chosen method so row bounce, column nudge, or scale pulse all happen inside stable line metrics.
Production gotchas
Do not animate top, margin, orheight for the bounce. Those properties can re-measure inline content, push neighboring controls, or make UI below the row jump on every loop. Keep the moving property to transform, and keep the cycle calm enough that the loader reads as waiting rather than urgency.
Accessibility
The dots are visual decoration; the status copy carries the accessible meaning. Under prefers-reduced-motion: reduce, stop the bounce and show the dots at a steady opacity. Do not replace the bounce with another fade or pulse, and do not leave a typing indicator running after the request has failed or completed.
References
Implementation depth
Typing dots are a loader timing pattern, not a chat-only component. The reusable mechanism is staggered animation-delay over fixed-position dots; the expression can be a horizontal row wave, a vertical column wave, or a no-travel scale pulse.
The production risk is layout movement and misleading status. Reserve the wrapper footprint for the chosen expression, animate transform rather than margin or height, expose a single polite status message, and stop the indefinite loop when the real state resolves or fails.