# ReasoningPanel

> Collapsible reasoning trace that streams while the model thinks, then folds itself into a one-line summary.

- Package: `fancy-ui-svelte` (Svelte 5 + Tailwind CSS v4)
- Category: AI Agents
- Tags: ai, agents, reasoning, disclosure, streaming
- Docs: https://fancy-ui.rama.app/docs/components/reasoning-panel
- Source: https://github.com/RamaHerbin/fancy-ui/tree/main/src/lib/fancy-ui/reasoning-panel

## Import

```svelte
<script lang="ts">
  import { ReasoningPanel } from 'fancy-ui-svelte';
</script>
```

Setup (once per project): `npm install fancy-ui-svelte`, then add `@import "fancy-ui-svelte/tailwind.css";` after `@import "tailwindcss";` in your main CSS file. Full guide: https://fancy-ui.rama.app/llms.txt

React: `import { ReasoningPanel } from 'fancy-ui-react';` (same prop names; `class` becomes `className`, snippets become `ReactNode`).

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `text` (required) | `string` | — | The reasoning trace so far; hand over a longer string to stream more in |
| `streaming` | `boolean` | `false` | Whether the trace is still growing; drives the timer, shimmer, and autoscroll |
| `open` | `boolean` | — | Expanded state (bindable). Left undefined, the panel opens while streaming and collapses 600ms after it ends, until the reader toggles it by hand |
| `label` | `string` | `"Reasoning"` | Header text |
| `since` | `number` | — | Epoch ms the current burst started at; pins the live timer's origin |
| `durationMs` | `number` | — | Final duration for the summary line; falls back to the duration the panel measured itself |
| `maxHeight` | `string` | `"12rem"` | Scroll height of the trace once expanded |
| `onToggle` | `(open: boolean) => void` | — | Called on every open/close, by click or on the panel's own initiative |
| `sound` | `boolean` | `false` | Plays open or close when the reader toggles the reasoning trace, once the user has enabled sound |

## Examples

### Basic Usage

```svelte
<script lang="ts">
	import { onMount } from "svelte";
	import { ReasoningPanel } from "fancy-ui-svelte";

	const TRACE = [
		"The user wants the total per region, but the orders table only stores a city.",
		" So I need the city → region mapping before anything else can be aggregated.",
		" There is a `regions` lookup table with a `city_id` foreign key — that joins cleanly.",
		" One catch: cities with no region row would vanish under an inner join.",
		" A left join keeps them and buckets them as “Unassigned”, which is the safer default.",
		" Grouping on the region name rather than its id keeps the output readable.",
	];

	/** How long a finished trace rests before the loop starts over. */
	const REST_MS = 1200;
	const CHUNK_MS = 220;

	let text = $state("");
	let streaming = $state(false);
	let since = $state<number | undefined>(undefined);
	let open = $state<boolean | undefined>(undefined);

	onMount(() => {
		// A looping demo that keeps re-animating is exactly what reduced motion is
		// asking us not to do, so it gets the finished trace, expanded and still.
		if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
			text = TRACE.join("");
			open = true;
			return;
		}

		let timer: ReturnType<typeof setTimeout>;
		let i = 0;

		function tick() {
			if (i < TRACE.length) {
				text += TRACE[i];
				i += 1;
				timer = setTimeout(tick, CHUNK_MS);
				return;
			}
			streaming = false;
			timer = setTimeout(restart, REST_MS);
		}

		function restart() {
			text = "";
			i = 0;
			streaming = true;
			since = Date.now();
			timer = setTimeout(tick, CHUNK_MS);
		}

		restart();
		return () => clearTimeout(timer);
	});
</script>

<div class="w-full max-w-xl">
	<ReasoningPanel sound {text} {streaming} {since} bind:open label="Reasoning" />
	<p class="text-muted-foreground mt-3 text-xs">
		Streams while it thinks, then folds itself into a summary. Click the header at any point and it
		stays exactly where you left it.
	</p>
</div>
```
