agentsclimarketplace

Streamdown

Skill BjornMelin/dev-skills/skills/streamdown

Expert guidance for Vercel's Streamdown library - a streaming-optimized react-markdown replacement for AI applications. Use when: (1) Rendering AI-generated markdown from useChat/streamText, (2) Building chat UIs with streaming responses, (3) Migrating from react-markdown to streaming-friendly rendering, (4) Configuring code blocks (Shiki), math (KaTeX), diagrams (Mermaid), (5) Handling incomplete markdown during AI streaming (remend preprocessor), (6) Customizing markdown styling with Tailwind/CSS variables, (7) Securing AI output with rehype-harden (link/image protocols). Triggers: Streamdown, streaming markdown, AI chat markdown, react-markdown replacement, AI Elements Response, incomplete markdown, remend, Shiki themes, Mermaid diagrams, KaTeX math, rehype-harden, isAnimating, markdown streaming.From its SKILL.md

Install
npx -y skills add BjornMelin/dev-skills --skill streamdown

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 5 stars5 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

8.9 KB, ~2.1k tokens by cl100k_base, as published. Nobody here has run it

Streamdown - AI Streaming Markdown

Streamdown is a drop-in react-markdown replacement designed for AI-powered streaming applications. It handles incomplete markdown syntax gracefully using the remend preprocessor.

Quick Start

Installation

# Direct installation
pnpm add streamdown

# Or via AI Elements CLI (includes Response component)
pnpm dlx ai-elements@latest add message

Tailwind Configuration

Tailwind v4 (globals.css):

@source "../node_modules/streamdown/dist/*.js";

Tailwind v3 (tailwind.config.js):

module.exports = {
  content: [
    './app/**/*.{js,ts,jsx,tsx}',
    './node_modules/streamdown/dist/*.js',
  ],
}

Basic Chat Example

'use client';
import { useChat } from '@ai-sdk/react';
import { Streamdown } from 'streamdown';

export default function Chat() {
  const { messages, sendMessage, status } = useChat();

  return (
    <>
      {messages.map(message => (
        <div key={message.id}>
          {message.parts
            .filter(part => part.type === 'text')
            .map((part, index) => (
              <Streamdown
                key={index}
                isAnimating={status === 'streaming'}
              >
                {part.text}
              </Streamdown>
            ))}
        </div>
      ))}
    </>
  );
}

Core Props

PropTypeDefaultDescription
childrenstringrequiredMarkdown content to render
isAnimatingbooleanfalseDisables interactive controls during streaming
mode"streaming" | "static""streaming"Rendering mode
shikiTheme[BundledTheme, BundledTheme]['github-light', 'github-dark']Light/dark syntax themes
controlsControlsConfig | booleantrueButton visibility for code/table/mermaid
mermaidMermaidOptions{}Diagram configuration
componentsobject{}Custom element overrides
classNamestring""Container CSS class
remarkPluginsPluggable[]GFM, math, CJKMarkdown preprocessing
rehypePluginsPluggable[]raw, katex, hardenHTML processing
parseIncompleteMarkdownbooleantrueEnable remend preprocessor

AI SDK Integration

Status-Based isAnimating

The status from useChat maps directly to Streamdown's isAnimating:

const { messages, status } = useChat();
// status: 'submitted' | 'streaming' | 'ready' | 'error'

<Streamdown isAnimating={status === 'streaming'}>
  {content}
</Streamdown>

Message Parts Pattern

AI SDK v6 uses message parts instead of content string:

{messages.map(message => (
  <div key={message.id}>
    {message.parts
      .filter(part => part.type === 'text')
      .map((part, index) => (
        <Streamdown key={index} isAnimating={status === 'streaming'}>
          {part.text}
        </Streamdown>
      ))}
  </div>
))}

Memoized Response Component

Wrap Streamdown with React.memo for performance:

import { memo, ComponentProps } from 'react';
import { Streamdown } from 'streamdown';

export const Response = memo(
  ({ className, ...props }: ComponentProps<typeof Streamdown>) => (
    <Streamdown
      className={cn('prose dark:prose-invert max-w-none', className)}
      {...props}
    />
  )
);

Configuration Examples

Shiki Themes

import type { BundledTheme } from 'shiki';

const themes: [BundledTheme, BundledTheme] = ['github-light', 'github-dark'];

<Streamdown shikiTheme={themes}>{content}</Streamdown>

Controls

<Streamdown
  controls={{
    code: true,           // Copy button on code blocks
    table: true,          // Download button on tables
    mermaid: {
      copy: true,         // Copy diagram source
      download: true,     // Download as SVG
      fullscreen: true,   // Fullscreen view
      panZoom: true,      // Pan/zoom controls
    },
  }}
>
  {content}
</Streamdown>

Mermaid Diagrams

import type { MermaidConfig } from 'streamdown';

const mermaidConfig: MermaidConfig = {
  theme: 'base',
  themeVariables: {
    fontFamily: 'Inter, sans-serif',
    primaryColor: 'hsl(var(--primary))',
    lineColor: 'hsl(var(--border))',
  },
};

<Streamdown mermaid={{ config: mermaidConfig }}>{content}</Streamdown>

Custom Error Component for Mermaid

import type { MermaidErrorComponentProps } from 'streamdown';

const MermaidError = ({ error, chart, retry }: MermaidErrorComponentProps) => (
  <div className="p-4 border border-destructive rounded">
    <p>Failed to render diagram</p>
    <button onClick={retry}>Retry</button>
  </div>
);

<Streamdown mermaid={{ errorComponent: MermaidError }}>{content}</Streamdown>

Custom Components

Override any markdown element:

<Streamdown
  components={{
    h1: ({ children }) => <h1 className="text-4xl font-bold">{children}</h1>,
    a: ({ href, children }) => (
      <a href={href} className="text-primary underline">{children}</a>
    ),
    code: ({ children, className }) => (
      <code className={cn('bg-muted px-1 rounded', className)}>{children}</code>
    ),
  }}
>
  {content}
</Streamdown>

Security Configuration

Restrict protocols for AI-generated content:

import { defaultRehypePlugins } from 'streamdown';
import { harden } from 'rehype-harden';

<Streamdown
  rehypePlugins={[
    defaultRehypePlugins.raw,
    defaultRehypePlugins.katex,
    [harden, {
      allowedProtocols: ['http', 'https', 'mailto'],
      allowedLinkPrefixes: ['https://your-domain.com'],
      allowDataImages: false,
    }],
  ]}
>
  {content}
</Streamdown>

Streaming vs Static Mode

ModeUse CaseFeatures
streamingAI chat responsesBlock parsing, incomplete markdown handling, memoization
staticBlog posts, docsSimpler rendering, no streaming optimizations
// Static mode for pre-rendered content
<Streamdown mode="static">{blogContent}</Streamdown>

Offline Stack Scanner

Run the local scanner before Streamdown migrations or chat-markdown reviews:

python3 skills/streamdown/scripts/ai_stack_scan.py --root <repo> --pretty

It emits ai_stack_scan.v1, uses no network by default, skips symlinks, and flags likely migration gaps such as react-markdown in AI streaming surfaces, Streamdown usage without isAnimating, missing streamdown dependencies, and missing Tailwind streamdown/dist source/content configuration in config or CSS. Verify signals against current Streamdown docs/source before editing. Keep full scanner JSON local; share only specific redacted signals externally.

Built-in Features

  • GFM: Tables, task lists, strikethrough, autolinks
  • Math: KaTeX rendering with $$...$$ syntax
  • Code: Shiki syntax highlighting (200+ languages)
  • Diagrams: Mermaid with interactive controls
  • CJK: Proper emphasis handling for Chinese/Japanese/Korean
  • Security: rehype-harden for link/image protocol restrictions

Reference Files

ReferenceTopics
api-reference.mdComplete props, types, plugins, data attributes
ai-sdk-integration.mduseChat patterns, server setup, message parts
styling-security.mdTailwind, CSS variables, custom components, rehype-harden

Common Patterns

Next.js Configuration

If you see bundling errors with Mermaid:

// next.config.js
module.exports = {
  serverComponentsExternalPackages: ['langium', '@mermaid-js/parser'],
  webpack: (config, { isServer }) => {
    if (!isServer) {
      config.resolve.alias = {
        ...config.resolve.alias,
        'vscode-jsonrpc': false,
        'langium': false,
      };
    }
    return config;
  },
};

Shiki External Package

// next.config.js
{
  transpilePackages: ['shiki'],
}

Version Notes

  • Streamdown: Works with React 18+ (optimized for React 19)
  • AI SDK: Designed for v6 (status-based streaming state)
  • Tailwind: Supports v3 and v4 configurations

What ships with it: 5 files

73.8 KB alongside SKILL.md, 1 of them executable

agents/

scripts/

Gives 0 of the 12 instructions most css styling skills give in ~2.1k tokens

Counted across 512 of the 512 authors here whose files we hold, read 2026-09-06

  • Animate only transform and opacityin 32 of 512, across 30 files
  • Respect prefers-reduced-motionin 21 of 512
  • Support reduced motion preferencesin 16 of 512, across 6 files
  • Use Tailwind CSS for stylingin 14 of 512, across 13 files
  • Specify AnimatePresence mode explicitlyin 12 of 512, across 2 files
  • Set initial states explicitlyin 12 of 512, across 2 files
  • Use semantic HTML elementsin 11 of 512, across 10 files
  • Use oklch for color valuesin 11 of 512, across 10 files
  • Honor prefers-reduced-motion in animationsin 10 of 512
  • Provide a reduced-motion fallback for animationsin 10 of 512, across 9 files
  • Use property names in camelCasein 9 of 512, across 4 files
  • Ensure UI animations stay under 300msin 9 of 512, across 6 files

Said here and by no other author read

  • run the local scanner before migrations

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.