Thinking Steps
Vertical list of chain-of-thought steps. Each item flies in 6px with a 40ms-per-index stagger, and each row composes ThinkingIndicator for its status glyph. Reduced-motion collapses fly + delay to zero.
Custom
Basic
- Read the input
- Plan the approach
thinking-steps.svelte
<script lang="ts">
import { ThinkingSteps } from '$lib/components/ui/thinking-steps';
const steps = [
{ id: 's1', label: 'Read the input', status: 'done' },
{ id: 's2', label: 'Plan the approach', status: 'active' },
{ id: 's3', label: 'Draft the response', status: 'pending' }
];
</script>
<ThinkingSteps {steps} />Full sequence
- Read the input
- Plan the approach
- Search the codebase
- Draft the response
thinking-steps.svelte
<script lang="ts" module>
import { cn } from '$lib/utils.js';
export type ThinkingStep = {
id: string;
label: string;
status?: 'pending' | 'active' | 'done';
};
export type ThinkingStepsProps = {
steps: ThinkingStep[];
class?: string;
};
</script>
<script lang="ts">
import { fly } from 'svelte/transition';
import { cubicOut } from 'svelte/easing';
import { prefersReducedMotion } from '$lib/motion';
import ThinkingIndicator from '../thinking-indicator/thinking-indicator.svelte';
let { steps, class: className }: ThinkingStepsProps = $props();
function duration(): number {
return prefersReducedMotion() ? 0 : 260;
}
function delayFor(i: number): number {
return prefersReducedMotion() ? 0 : i * 40;
}
</script>
<ol
data-slot="thinking-steps"
class={cn('flex flex-col gap-2 text-sm text-muted-foreground', className)}
>
{#each steps as step, i (step.id)}
<li
data-slot="thinking-step"
data-status={step.status ?? 'pending'}
class={cn(
'flex items-center gap-2.5 transition-colors duration-(--motion-duration-fast)',
step.status === 'active' && 'text-foreground',
step.status === 'done' && 'text-foreground/70'
)}
in:fly={{ y: 6, duration: duration(), delay: delayFor(i), easing: cubicOut }}
>
<ThinkingIndicator
size="sm"
status={step.status === 'done'
? 'done'
: step.status === 'active'
? 'thinking'
: 'thinking'}
class={step.status === 'pending' ? 'opacity-40' : ''}
/>
<span>{step.label}</span>
</li>
{/each}
</ol>
ThinkingSteps props
| Prop | Type | Default | Description |
|---|---|---|---|
ThinkingStep[] | — | Ordered list of steps. Each step has `{ id, label, status? }` where status is `pending | active | done`. | |
string | — | Merged onto the <ol> root via tailwind-merge. |
ThinkingStep
| Prop | Type | Default | Description |
|---|---|---|---|
string | — | Stable key for each row. Required for the fly-in stagger to associate correctly. | |
string | — | Copy for the step. | |
'pending' | 'active' | 'done' | 'pending' | Drives both the indicator glyph and the row color — pending is muted, active is foreground, done is foreground/70. |