Elements

Typing indicator

The classic three dots, tuned to read as presence rather than noise.

fig. 01

Installation

npx shadcn@latest add "@assistant-ui/elements-typing-indicator"
First time? Set up a runtime

Runtime components read their state from an assistant-ui runtime. Add one to an existing project:

npx assistant-ui@latest init

Then wrap your app in a runtime provider:

import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useChatRuntime, AssistantChatTransport } from "@assistant-ui/ai-sdk";

export default function App() {
  const runtime = useChatRuntime({
    transport: new AssistantChatTransport({ api: "/api/chat" }),
  });

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      {/* your components */}
    </AssistantRuntimeProvider>
  );
}

The installation guide covers new projects, templates, and API routes.

Three dots bouncing in a staggered wave, either bare or sitting inside a rounded bubble. With a runtime you mount it for the brief window before the first token lands; standalone you show and hide it yourself.

Getting started

This belongs on screen for exactly one window: a run is active and the newest message has not produced any content yet. Nothing about the dots themselves depends on the runtime; only the decision to render it does.

Show it before the first token

components/assistant-ui/elements/typing-indicator.tsx
"use client";

import { useAuiState } from "@assistant-ui/react";
import { TypingIndicator } from "@/components/assistant-ui/elements/typing-indicator";

function AssistantTyping() {
  const waiting = useAuiState((s) => {
    if (!s.thread.isRunning) return false;
    const last = s.thread.messages.at(-1);
    return last?.role === "assistant" && last.parts.length === 0;
  });
  if (!waiting) return null;
  return <TypingIndicator />;
}

Place it where the reply will render

Render AssistantTyping in the same spot the assistant's message body will take over once content starts arriving, so nothing shifts when it disappears.

Examples

Bare dots without the bubble

variant="bare" drops the rounded paper surface and renders only the three dots, for placement inside your own container.

<TypingIndicator variant="bare" className="gap-0.5" />

Restyle the dots

The bubble variant wraps the dots in the shared paper surface from surfaces.tsx; className on either variant only affects the outermost element (the bubble itself for "bubble", the dot row for "bare"). The dots' color, size, and bounce timing are fixed and not exposed as props.

<TypingIndicator className="px-3 py-2.5" />

API reference

Thread state

SelectorTypeDescription
s.thread.isRunningbooleanWhether a run is active.
s.thread.messagesreadonly MessageState[].at(-1) is the newest message; an assistant message with an empty parts array means nothing has streamed in yet.