Thinking Text

A shimmering status line that cycles through states behind a pulsing dot.

Thinking

Installation

npx shadcn@latest add @loomui/thinking-text

Usage

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

PropTypeDefaultDescription
activebooleanfalseRuns the cycle and the shimmer. Off is static.
statesstring[]-The lines to cycle through, in order.
dotbooleantrueShows the pulsing status dot.
holdDurationnumber2000Milliseconds each state stays on screen.
swapDurationnumber150Milliseconds the exit/enter transition takes.
gapDurationnumber50Milliseconds held blank between enter and hold.
classNamestring-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.