Components
Loading
Two marks say the app is working right now — as opposed to the
Skeleton, which stands in for content that hasn't arrived
yet. Both stop under prefers-reduced-motion and still read at rest.
Dots
Three dots pulsing on a staggered 1.4s loop — the app's one “working on it”
signal: the chat thinking state, the per-item fetch placeholder, and the
composer's upload slot all use it. Each dot is shrink-0 so a narrow
container cannot squash the circles into ellipses; the container is what has
to make room for the 1.13rem row.
import { LoadingDots } from "@/components/base/loading-dots";
<LoadingDots />Character
The agent's face — the chat's “the agent is still working” mark, at the foot
of its reply. thinking, it ponders: the eyes drift up and away with a brow
cocked, hold, blink, and look somewhere else. Turned off, it eases back to the
drawn pose and stays there, which is how the latest reply keeps it once the
turn settles. Frames are tweened only while something moves, and each rewrites
the paths rather than transforming them, so a blink keeps the stroke's weight.
Colour comes from currentColor, so the caller tones it with a text colour —
it wants to sit a shade brighter than the label beside it.
Thinking
import { AiCharacter } from "@/components/ai/character";
<span className="flex items-center gap-2 text-ink-secondary">
<AiCharacter thinking />
Thinking
</span>