llms.txt
# Streamdown documentation
> Streamdown is a markdown renderer designed for streaming content from AI models. It is highly interactive, customizable, and easy to use.
## When to use Streamdown
- Category: AI / React components
- Audience: React developers, AI application developers
Common use cases:
- Render streaming markdown from AI models
- Build chat interfaces with rich markdown output
- Display code blocks, math, diagrams, and CJK text from LLM responses
## Documentation
---
title: Animation
description: Smooth per-word text animation for streaming content.
url: "https://streamdown.ai/docs/animation"
docs_index: /llms.txt
lastUpdated: 2026-09-30
type: reference
summary: Token-by-token text reveal animations that create a natural typing effect during streaming.
related:
- /docs/carets
- /docs/memoization
---
> For an index of all documentation, see [/llms.txt](/llms.txt).
Streamdown supports per-word streaming animation through the built-in `animated` prop. Words fade in as they mount, creating a smooth text-reveal effect during AI streaming. When streaming ends, the animation is removed entirely, leaving zero DOM overhead on completed messages.
## Enabling animation
Import the animation CSS and set the `animated` prop:
```tsx title="app/page.tsx"
import { Streamdown } from "streamdown";
import "streamdown/styles.css";
export default function Page() {
return (
<Streamdown animated isAnimating={status === "streaming"}>
{markdown}
</Streamdown>
);
}
```
The `isAnimating` prop controls when the animation is active. When `false`, the animate plugin is excluded from the rehype pipeline entirely, so completed messages render as plain text with no extra `<span>` wrappers.
## How it works
The animation is a rehype transformer that:
1. Walks the HAST tree, visiting text nodes
2. Splits each text node into per-word `<span>` elements with `data-sd-animate`
3. Sets CSS custom properties for animation name, duration, and easing
4. Skips text inside `pre`, `svg`, `math`, and `annotation` elements (inline `code` is animated with surrounding prose)
React's reconciliation ensures only newly-mounted spans trigger the CSS animation. Combined with a short default duration (150ms), this makes batch token arrivals look smooth rather than "chunky."
## Animation types
Three built-in animations are included in `styles.css`:
### fadeIn (default)
A simple opacity transition from invisible to visible.
```tsx
<Streamdown animated={{ animation: "fadeIn" }} isAnimating={status === "streaming"}>
{markdown}
</Streamdown>
```
### blurIn
Combines opacity with a blur-to-sharp transition. Works well with fast-streaming models where many tokens arrive at once — the blur masks the batch appearance better than pure opacity.
```tsx
<Streamdown animated={{ animation: "blurIn" }} isAnimating={status === "streaming"}>
{markdown}
</Streamdown>
```
### slideUp
Words fade in while sliding up 4px, creating a subtle rising effect.
```tsx
<Streamdown animated={{ animation: "slideUp" }} isAnimating={status === "streaming"}>
{markdown}
</Streamdown>
```
## Configuration
Pass an options object to `animated` to customize animation behavior:
```tsx title="app/page.tsx"
import { Streamdown } from "streamdown";
import "streamdown/styles.css";
export default function Page() {
return (
<Streamdown
animated={{
animation: "blurIn", // "fadeIn" | "blurIn" | "slideUp" | custom string
duration: 200, // milliseconds (default: 150)
easing: "ease-out", // CSS timing function (default: "ease")
sep: "word", // "word" | "char" (default: "word")
}}
isAnimating={status === "streaming"}
>
{markdown}
</Streamdown>
);
}
```
### Options
| Option | Type | Default | Description |
| ----------- | ------------------ | ---------- | ------------------------------------------------------------------------------------------------ |
| `animation` | `string` | `"fadeIn"` | Animation name. Built-in: `fadeIn`, `blurIn`, `slideUp`. Custom strings are prefixed with `sd-`. |
| `duration` | `number` | `150` | Animation duration in milliseconds. |
| `easing` | `string` | `"ease"` | CSS timing function. |
| `sep` | `"word" \| "char"` | `"word"` | Split text by word or character. |
### Character-level animation
Set `sep: "char"` to animate each character individually instead of whole words:
```tsx
<Streamdown animated={{ animation: "fadeIn", sep: "char" }} isAnimating={status === "streaming"}>
{markdown}
</Streamdown>
```
This creates a typewriter-like effect but generates more DOM nodes. Use it sparingly.
## Custom animations
Define your own `@keyframes` and reference them by name:
```css title="app/globals.css"
@keyframes sd-myCustomAnimation {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
```
```tsx
<Streamdown animated={{ animation: "myCustomAnimation" }} isAnimating={status === "streaming"}>
{markdown}
</Streamdown>
```
The animation name is automatically prefixed with `sd-`, so define your keyframes as `sd-yourName`.
## Callbacks
Use `onAnimationStart` and `onAnimationEnd` to react to animation state changes. These fire when `isAnimating` transitions from `false` to `true` and vice versa. Both callbacks are suppressed in `mode="static"`.
```tsx title="app/page.tsx"
import { Streamdown } from "streamdown";
import "streamdown/styles.css";
import { useCallback } from "react";
export default function Page() {
const handleAnimationStart = useCallback(() => {
console.log("Streaming started");
}, []);
const handleAnimationEnd = useCallback(() => {
console.log("Streaming ended");
}, []);
return (
<Streamdown
animated
isAnimating={status === "streaming"}
onAnimationStart={handleAnimationStart}
onAnimationEnd={handleAnimationEnd}
>
{markdown}
</Streamdown>
);
}
```
Memoize callbacks with `useCallback` to avoid unnecessary effect re-runs.
## Advanced usage
For direct access to the rehype plugin (e.g. in custom pipelines), use `createAnimatePlugin`:
```tsx
import { createAnimatePlugin } from "streamdown";
const animate = createAnimatePlugin({
animation: "blurIn",
duration: 200,
});
// animate.rehypePlugin is a standard rehype plugin
```
## Skipped elements
The animation skips text inside these elements to avoid breaking their layout:
- `<pre>` — preformatted / fenced code blocks (CommonMark always emits `pre > code`, so Shiki-highlighted blocks stay un-split)
- `<svg>` — vector graphics
- `<math>` — MathML elements
- `<annotation>` — MathML annotations
Inline `<code>` (backtick spans) **is** animated — wrapping words in style-inheriting spans is layout-neutral, same as surrounding paragraphs.
This means fenced code blocks, syntax-highlighted code, math equations, and diagrams render without animation spans, while inline code fades in with the prose.
## Fast-streaming models
Fast models can dump many tokens per React commit. The default 150ms duration with `animation-fill-mode: both` ensures words start invisible and end visible, making simultaneous mounts look intentional.
For smoother results with fast models:
- Use `blurIn` — blur masks batch arrivals better than opacity alone
- Increase duration slightly to 200-300ms
- Consider `ease-out` easing for a more natural deceleration
```tsx
<Streamdown
animated={{
animation: "blurIn",
duration: 250,
easing: "ease-out",
}}
isAnimating={status === "streaming"}
>
{markdown}
</Streamdown>
```
## CSS custom properties
Each animated span receives these CSS custom properties via inline styles:
| Property | Description |
| ---------------- | ---------------------------- |
| `--sd-animation` | The `@keyframes` name to use |
| `--sd-duration` | Animation duration |
| `--sd-easing` | CSS timing function |
The `[data-sd-animate]` selector in `styles.css` reads these properties:
```css
[data-sd-animate] {
animation: var(--sd-animation, sd-fadeIn)
var(--sd-duration, 150ms)
var(--sd-easing, ease) both;
}
```
You can override these in your own CSS for more control.
## Related features
- [Carets](/docs/carets) — Blinking cursor indicator during streaming
- [Plugins](/docs/plugins) — Overview of the plugin system
- [Configuration](/docs/configuration) — All Streamdown props including `isAnimating`
---
For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)
For an index of all available documentation, see [/llms.txt](/llms.txt)
For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)
---
title: Carets
description: Visual cursor indicators for streaming content to show active generation.
url: "https://streamdown.ai/docs/carets"
docs_index: /llms.txt
lastUpdated: 2026-09-30
type: reference
summary: Blinking cursor indicators that show where new content is being generated.
related:
- /docs/animation
---
> For an index of all documentation, see [/llms.txt](/llms.txt).
Streamdown includes built-in caret (cursor) indicators that display at the end of streaming content. Carets provide a visual cue to users that content is actively being generated, similar to a blinking cursor in a text editor.
## Overview
The `caret` prop adds a visual indicator at the end of your streaming markdown content. This feature enhances the user experience by making it clear when content is actively being generated versus when generation is complete.
Key features:
- **Two built-in styles** - Choose between block (`▋`) and circle (`●`) carets
- **Automatic positioning** - Carets automatically appear at the end of the last rendered element
- **Streaming-aware** - Only displays when `isAnimating={true}` and `mode="streaming"` (default)
- **CSS-based** - Uses CSS custom properties and pseudo-elements for efficient rendering
## Usage
To enable carets, pass the `caret` prop with either `"block"` or `"circle"`:
```tsx title="chat.tsx"
import { Streamdown } from 'streamdown';
function StreamingChat() {
const [isStreaming, setIsStreaming] = useState(true);
const [content, setContent] = useState('');
return (
<Streamdown
caret="block"
isAnimating={isStreaming}
>
{content}
</Streamdown>
);
}
```
## Caret Styles
Streamdown provides two built-in caret styles:
### Block Caret
The block caret displays a vertical bar (`▋`) similar to a terminal cursor:
```tsx title="chat.tsx"
<Streamdown caret="block" isAnimating={true}>
Streaming content...
</Streamdown>
```
### Circle Caret
The circle caret displays a filled circle (`●`) for a subtler indicator:
```tsx title="chat.tsx"
<Streamdown caret="circle" isAnimating={true}>
Streaming content...
</Streamdown>
```
## Behavior
The caret visibility is controlled by two conditions:
1. **`caret` prop is set** - You must specify either `"block"` or `"circle"`
2. **`isAnimating={true}`** - The caret only appears during active streaming
When streaming stops (when `isAnimating` becomes `false`), the caret automatically disappears, leaving only the completed content.
## Conditional Display
Streamdown doesn't know about roles or message ordering, so you should conditionally show carets for specific messages, such as only displaying them for the last message in a chat and only displaying them from assistant messages:
```tsx title="chat.tsx"
{messages.map((message, index) => (
<Streamdown
key={message.id}
caret={
message.role === 'assistant' &&
index === messages.length - 1
? 'block'
: undefined
}
isAnimating={isStreaming}
>
{message.content}
</Streamdown>
))}
```
## Technical Details
Carets are implemented using CSS custom properties and pseudo-elements:
- The caret value is passed as a CSS custom property (`--streamdown-caret`)
- A `::after` pseudo-element is added to the last child element
- The pseudo-element displays the caret character inline
- When `isAnimating` becomes `false` or `caret` is `undefined`, the styles are removed
This approach ensures efficient rendering without additional DOM elements.
---
For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)
For an index of all available documentation, see [/llms.txt](/llms.txt)
For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)
---
title: Code Blocks
description: Beautiful syntax highlighting and interactive code blocks powered by Shiki.
url: "https://streamdown.ai/docs/code-blocks"
docs_index: /llms.txt
lastUpdated: 2026-09-30
type: reference
summary: Shiki-powered syntax highlighting with line numbers, copy buttons, and language detection.
prerequisites:
- /docs/getting-started
related:
- /docs/interactivity
- /docs/plugins
---
> For an index of all documentation, see [/llms.txt](/llms.txt).
Streamdown provides beautiful, interactive code blocks with syntax highlighting powered by [Shiki](https://shiki.style/). Every code block includes a copy button and supports a wide range of programming languages.
## Basic Usage
Create code blocks using triple backticks with an optional language identifier:
````markdown
```javascript
function greet(name) {
return `Hello, ${name}!`;
}
```
````
Streamdown will automatically apply syntax highlighting based on the specified language.
## Enabling Syntax Highlighting
Syntax highlighting requires the code plugin. Install it:
```bash
npm install @streamdown/code
```
```bash
pnpm add @streamdown/code
```
```bash
yarn add @streamdown/code
```
```bash
bun add @streamdown/code
```
Then import and pass the plugin to Streamdown:
```tsx title="app/page.tsx"
import { Streamdown } from "streamdown";
import { code } from "@streamdown/code";
export default function Page() {
return (
<Streamdown plugins={{ code: code }}>
{markdown}
</Streamdown>
);
}
```
Without the code plugin, code blocks render as plain text with no highlighting.
## Supported Languages
Streamdown supports 200+ programming languages through Shiki. All languages are lazy-loaded on demand, so only the grammars you use are downloaded.
### Common Languages
- **Web**: JavaScript, TypeScript, JSX, TSX, HTML, CSS
- **Data**: JSON, YAML, TOML
- **Shell**: Bash, Shell Script, PowerShell
- **Backend**: Python, Go, Java, Rust, C, C++, C#, PHP, Ruby
- **Functional**: Haskell, Elixir, Clojure, F#, OCaml
- **Markup**: Markdown, LaTeX, MDX, XML
- **And 180+ more languages**
### Language Examples
#### TypeScript
````markdown
```typescript
interface User {
id: number;
name: string;
email: string;
}
async function fetchUser(id: number): Promise<User> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
```
````
#### Python
````markdown
```python
def fibonacci(n: int) -> list[int]:
"""Generate Fibonacci sequence up to n terms."""
fib = [0, 1]
for i in range(2, n):
fib.append(fib[i-1] + fib[i-2])
return fib
print(fibonacci(10))
```
````
#### Rust
````markdown
```rust
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let sum: i32 = numbers.iter().sum();
println!("Sum: {}", sum);
}
```
````
## Theme Configuration
Streamdown uses dual themes for light and dark modes. You can customize the themes using the `shikiTheme` prop:
```tsx title="app/page.tsx"
import { Streamdown } from "streamdown";
import { code } from "@streamdown/code";
export default function Page() {
return (
<Streamdown
plugins={{ code: code }}
shikiTheme={["dracula", "dracula"]}
>
{markdown}
</Streamdown>
);
}
```
### Available Themes
Streamdown supports all Shiki themes including:
- `github-light` (default light theme)
- `github-dark` (default dark theme)
- `dracula`, `nord`, `one-dark-pro`, `monokai`
- `catppuccin-latte`, `catppuccin-mocha`
- `vitesse-light`, `vitesse-dark`
- `tokyo-night`, `slack-dark`, `slack-ochin`
- And [many more](https://shiki.style/themes)
### Custom theme objects
The `shikiTheme` prop accepts `[ThemeInput, ThemeInput]` where `ThemeInput` is either a bundled theme name (`BundledTheme`) or a custom theme object (`ThemeRegistrationAny`). You can mix and match:
```tsx title="app/page.tsx"
import { Streamdown } from "streamdown";
import { code } from "@streamdown/code";
import myCustomDarkTheme from "./my-dark-theme.json";
export default function Page() {
return (
<Streamdown
plugins={{ code: code }}
shikiTheme={["github-light", myCustomDarkTheme]}
>
{markdown}
</Streamdown>
);
}
```
> Bundled theme names (strings) load from Shiki's built-in registry. Custom theme objects follow the `ThemeRegistrationAny` format from Shiki — any VS Code `.tmTheme` or JSON theme file works.
## Line Numbers
Line numbers are shown by default on all code blocks.
### Disable globally
Turn off line numbers for every code block with the `lineNumbers` prop:
```tsx title="app/page.tsx"
<Streamdown lineNumbers={false}>{markdown}</Streamdown>
```
### Disable per block
Add `noLineNumbers` to the code fence meta to hide line numbers on a single block:
````markdown
```typescript noLineNumbers
const user = await getUser(id);
const profile = await getProfile(user);
```
````
When `lineNumbers` is set to `false` globally, all blocks hide line numbers regardless of the meta string.
### Custom start line
Set the starting line number for a code block using `startLine=N` in the code fence meta:
````markdown
```typescript startLine=10
const user = await getUser(id);
const profile = await getProfile(user);
```
````
Line numbers begin at the value you specify instead of 1. The value must be a positive integer (>= 1).
## Interactive Features
### Copy Button
Every code block includes a copy button that appears on hover. Users can click to copy the entire code block content to their clipboard.
The copy button:
- Appears on hover (desktop) or is always visible (mobile)
- Provides visual feedback on successful copy
- Is automatically disabled during streaming (when `isAnimating={true}`)
### Copy Callbacks
Pass `onCopy` and/or `onError` through `controls.code.copy` to run your own logic after the default copy button succeeds or fails. The button still renders — you do not need a custom component.
```tsx title="app/page.tsx"
<Streamdown
controls={{
code: {
copy: {
onCopy: () => toast.success("Copied to clipboard"),
onError: (error) => toast.error(error.message),
},
},
}}
>
{markdown}
</Streamdown>
```
### Disable Controls
Disable individual code block buttons using the `controls` prop:
```tsx title="app/page.tsx"
// Hide the download button, keep copy
<Streamdown controls={{ code: { download: false } }}>{markdown}</Streamdown>
// Hide the copy button, keep download
<Streamdown controls={{ code: { copy: false } }}>{markdown}</Streamdown>
// Hide all code block controls
<Streamdown controls={{ code: false }}>{markdown}</Streamdown>
// Hide all controls across all block types
<Streamdown controls={false}>{markdown}</Streamdown>
```
### Custom Download Filename
Pass `download: { filename }` to set a custom base name. Streamdown appends the language-appropriate extension automatically (for example `myScript.ts`). The default is `file.<ext>`.
```tsx title="app/page.tsx"
<Streamdown
controls={{
code: {
download: { filename: "myScript" },
},
}}
>
{markdown}
</Streamdown>
```
## Inline Code
Inline code uses backticks and receives subtle styling:
```markd