Thinking Text
A shimmering status line that cycles through states behind a pulsing dot.
Installation
npx shadcn@latest add @loomui/thinking-textUsage
import { ThinkingText } from "@/components/ui/thinking-text"<ThinkingText
active={isLoading}
states={["Reading files", "Searching the web", "Drafting a reply"]}
/>How it works
Each state exits upward with a blur and a fade, then the next one enters
from below the same way, held apart by a short unstyled gap so the two
never visibly cross. The swap is a plain class toggle: .exit carries a
transition, .enter starts from the opposite offset with transition: none
so it snaps into place before being released on the next frame — the same
enter/exit split ShimmerText and MatrixDotLoader use, just applied to
position and blur instead of a highlight sweep.
A hidden sizer holds the longest state in the list, so the box never resizes
as shorter and longer lines swap through it. While a state holds, the same
background-clip: text shimmer from ShimmerText sweeps its glyphs.
The dot pulses independently of the text swap — it's a read on "still working," not tied to any individual state.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
active | boolean | false | Runs the cycle and the shimmer. Off is static. |
states | string[] | - | The lines to cycle through, in order. |
dot | boolean | true | Shows the pulsing status dot. |
holdDuration | number | 2000 | Milliseconds each state stays on screen. |
swapDuration | number | 150 | Milliseconds the exit/enter transition takes. |
gapDuration | number | 50 | Milliseconds held blank between enter and hold. |
className | string | - | Merged onto the wrapper. |
Any other <span> prop is forwarded.
Reduced motion
The blur, offset, and shimmer all drop out under prefers-reduced-motion.
States still swap on the same schedule, just as an instant cut instead of a
transition.