惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

W
WeLiveSecurity
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Hacker News - Newest:
Hacker News - Newest: "LLM"
Cloudbric
Cloudbric
V
Visual Studio Blog
L
LangChain Blog
A
About on SuperTechFans
B
Blog
T
Tenable Blog
罗磊的独立博客
Hacker News: Ask HN
Hacker News: Ask HN
Blog — PlanetScale
Blog — PlanetScale
博客园 - 三生石上(FineUI控件)
The Register - Security
The Register - Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
P
Palo Alto Networks Blog
U
Unit 42
WordPress大学
WordPress大学
D
Darknet – Hacking Tools, Hacker News & Cyber Security
N
News and Events Feed by Topic
T
Threat Research - Cisco Blogs
C
Check Point Blog
Security Latest
Security Latest
M
MIT News - Artificial intelligence
Application and Cybersecurity Blog
Application and Cybersecurity Blog
宝玉的分享
宝玉的分享
P
Proofpoint News Feed
NISL@THU
NISL@THU
Forbes - Security
Forbes - Security
S
Securelist
Security Archives - TechRepublic
Security Archives - TechRepublic
Hugging Face - Blog
Hugging Face - Blog
aimingoo的专栏
aimingoo的专栏
Latest news
Latest news
GbyAI
GbyAI
T
Troy Hunt's Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
L
LINUX DO - 热门话题
V2EX - 技术
V2EX - 技术
小众软件
小众软件
Google DeepMind News
Google DeepMind News
K
Kaspersky official blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
O
OpenAI News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
N
Netflix TechBlog - Medium
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Google DeepMind News
Google DeepMind News
P
Proofpoint News Feed

Streamdown Documentation

Configuration FAQ Getting Started Introduction Security Carets Code Blocks Components GitHub Flavored Markdown Interactivity Internationalization Link Safety Memoization Migrate from react-markdown Styling Unterminated Block Parsing Typography Usage @streamdown/cjk @streamdown/code Built-in Plugins @streamdown/math @streamdown/mermaid Custom renderers
Animation
Vercel · 2026-04-11 · via Streamdown Documentation

Smooth per-word text animation for streaming content.

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.

Import the animation CSS and set the animated prop:

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.

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 code, pre, svg, math, and annotation elements

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."

Three built-in animations are included in styles.css:

fadeIn (default)

A simple opacity transition from invisible to visible.

<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.

<Streamdown animated={{ animation: "blurIn" }} isAnimating={status === "streaming"}>
  {markdown}
</Streamdown>

slideUp

Words fade in while sliding up 4px, creating a subtle rising effect.

<Streamdown animated={{ animation: "slideUp" }} isAnimating={status === "streaming"}>
  {markdown}
</Streamdown>

Pass an options object to animated to customize animation behavior:

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

OptionTypeDefaultDescription
animationstring"fadeIn"Animation name. Built-in: fadeIn, blurIn, slideUp. Custom strings are prefixed with sd-.
durationnumber150Animation duration in milliseconds.
easingstring"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:

<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.

Define your own @keyframes and reference them by name:

@keyframes sd-myCustomAnimation {
  from {
    opacity: 0;
    transform: scale(0.95);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}
<Streamdown animated={{ animation: "myCustomAnimation" }} isAnimating={status === "streaming"}>
  {markdown}
</Streamdown>

The animation name is automatically prefixed with sd-, so define your keyframes as sd-yourName.

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".

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.

For direct access to the rehype plugin (e.g. in custom pipelines), use createAnimatePlugin:

import { createAnimatePlugin } from "streamdown";

const animate = createAnimatePlugin({
  animation: "blurIn",
  duration: 200,
});

// animate.rehypePlugin is a standard rehype plugin

The animation skips text inside these elements to avoid breaking their layout:

  • <code> — inline and block code
  • <pre> — preformatted text
  • <svg> — vector graphics
  • <math> — MathML elements
  • <annotation> — MathML annotations

This means code blocks, syntax-highlighted code, math equations, and diagrams render without animation spans.

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
<Streamdown
  animated={{
    animation: "blurIn",
    duration: 250,
    easing: "ease-out",
  }}
  isAnimating={status === "streaming"}
>
  {markdown}
</Streamdown>

Each animated span receives these CSS custom properties via inline styles:

PropertyDescription
--sd-animationThe @keyframes name to use
--sd-durationAnimation duration
--sd-easingCSS timing function

The [data-sd-animate] selector in styles.css reads these properties:

[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.

  • Carets — Blinking cursor indicator during streaming
  • Plugins — Overview of the plugin system
  • Configuration — All Streamdown props including isAnimating