Elements · Thread
Onboarding
First run: three moves that teach what this assistant is actually for.
It reads the repo you point it at, so questions can be specific rather than general.
Why does the composer re-render on every keystroke?Installation
npx shadcn@latest add "@assistant-ui/elements-onboarding"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 initThen 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.
npx shadcn@latest add "@assistant-ui/elements-onboarding"Props-driven: no runtime or provider required.
A short, dismissible tour: one step at a time, each with a line of body copy and a worked example, a progress dot per step, and Skip next to Next. With a runtime the one thing worth wiring is what happens when the tour ends; standalone every transition is a callback you already control.
Getting started
The steps themselves, their copy and their order, are content you write, not state the runtime holds; there is nothing to read from useAuiState here. The one real touchpoint is what "Skip" and the final "Next" actually do: hand off to the thread.
Hand off to the thread when the tour ends
"use client";
import { useState } from "react";
import { useAui } from "@assistant-ui/react";
import {
Onboarding,
type OnboardingStep,
} from "@/components/assistant-ui/elements/onboarding";
const STEPS: OnboardingStep[] = [
{ title: "Ask anything", body: "Type a question, it answers in place.", example: "Summarize this PDF" },
{ title: "It can act, not just answer", body: "Grant a tool and it can search, run code, or call your API.", example: "Check the weather in Tokyo" },
{ title: "Every answer is a branch", body: "Regenerating never loses the original; step between versions.", example: "n / m stepper on any reply" },
];
export function FirstRun({ onFinish }: { onFinish: () => void }) {
const aui = useAui();
const [index, setIndex] = useState(0);
const last = index >= STEPS.length - 1;
const finish = () => {
aui.threads.switchToNewThread();
onFinish();
};
return (
<Onboarding
steps={STEPS}
index={index}
onNext={() => (last ? finish() : setIndex((i) => i + 1))}
onSkip={finish}
/>
);
}switchToNewThread() only matters if your app defers creating a thread until the tour is dismissed; if the thread and composer already exist behind the overlay, finish can just call onFinish() and let them take over.
Standalone, the tour is entirely local: index is state you hold, and onNext deciding whether to advance or finish is the only logic involved.
Hold the step index
"use client";
import { useState } from "react";
import {
Onboarding,
type OnboardingStep,
} from "@/components/assistant-ui/elements/onboarding";
const steps: OnboardingStep[] = [
{ title: "Welcome", body: "Three quick things before you start.", example: "…" },
{ title: "Ask in plain language", body: "No special syntax needed.", example: "What changed in v4?" },
];
export function Tour({ onDone }: { onDone: () => void }) {
const [index, setIndex] = useState(0);
const last = index >= steps.length - 1;
return (
<Onboarding
steps={steps}
index={index}
onNext={() => (last ? onDone() : setIndex(index + 1))}
onSkip={onDone}
/>
);
}Anatomy
<div data-slot="onboarding">
<div>
{/* keyed on the current step, so it fades in fresh every change */}
<span>{/* "n of m" */}</span>
<span>{/* step title */}</span>
<p>{/* step body */}</p>
<span>{/* step example */}</span>
</div>
<div>
<span>{/* one dot per step, decorative only */}</span>
<button>{/* Skip */}</button>
<button>{/* Next, or Start on the last step */}</button>
</div>
</div>With zero steps the element renders nothing at all, not an empty shell; steps[0] being undefined is the signal it checks. index is floored and clamped into 0…steps.length - 1 before use, so a value outside that range, or a fractional one, still resolves to a real step instead of rendering blank. The element owns no progression: onNext and onSkip are pure signals, not state changes, so nothing advances until the caller moves index itself, as in both examples above. The progress dots are aria-hidden and purely visual; they are not buttons, so nothing lets a user jump back to an earlier step by clicking one.
Examples
Adding a way back
Since index is a plain controlled prop and the element never advances it, a back control is exactly as valid as onNext, even though nothing built in renders one:
<button type="button" onClick={() => setIndex((i) => Math.max(0, i - 1))}>
Back
</button>Restyle the tour
Both lanes take className on the root. The example block sits on the field surface and the final button on inkButton, so retheming those two tokens covers the card's two accent surfaces together.
<Onboarding className="max-w-xs" /* ... */ />API reference
This element has no dedicated primitive; the only runtime call involved is finishing the tour, as in Getting started.
Threads
| Method | Type | Description |
|---|---|---|
aui.threads.switchToNewThread() | () => void | Selects a fresh, unsent thread as the open one. Only needed when thread creation is deferred until onboarding completes. |
Onboarding
| Prop | Type | Default | Description |
|---|---|---|---|
steps | readonly OnboardingStep[] | required | The tour's steps, in order. An empty array renders nothing. |
index | number | required | The step to show. Floored and clamped into 0…steps.length - 1. |
onNext | () => void | Called when "Next" (or "Start", on the last step) is pressed. Does not change index itself. | |
onSkip | () => void | Called when "Skip" is pressed. | |
className | string | Merged onto the root. |
All other div props are forwarded to the root.
OnboardingStep
| Field | Type | Description |
|---|---|---|
title | string | Step heading. |
body | string | One paragraph of explanation. |
example | string | A worked example shown in its own field below the body. |