---
title: Configuration
description: Learn how to configure Streamdown in your project.
type: reference
summary: All available props and options for the Streamdown component.
prerequisites:
  - /docs/getting-started
related:
  - /docs/usage
  - /docs/plugins
---

# Configuration



Streamdown can be configured to suit your needs. This guide will walk you through the available options and how to configure them.

## Core Props

<TypeTable
  type={{
  children: {
    description: "The Markdown content to render",
    type: "string",
  },
  parseIncompleteMarkdown: {
    description:
      "Enable remend preprocessor for unterminated Markdown blocks",
    type: "boolean",
    default: "true",
  },
  remend: {
    description: "Configure which Markdown completions remend should perform",
    type: "RemendOptions",
  },
  normalizeHtmlIndentation: {
    description:
      'Normalize indentation in HTML blocks to prevent 4+ space indents from being treated as code blocks',
    type: 'boolean',
    default: 'false',
  },
  isAnimating: {
    description:
      "Indicates if content is currently streaming (disables copy buttons)",
    type: "boolean",
    default: "false",
  },
  className: {
    description: "CSS class for the container element",
    type: "string",
  },
  mode: {
    description: "Mode of the Streamdown component",
    type: '"streaming" | "static"',
    default: "streaming",
    options: ["streaming", "static"],
  },
  dir: {
    description:
      "Text direction. 'ltr'/'rtl' force a single direction. 'auto' detects per block (content-majority strong characters, first-strong on ties). Code is always LTR.",
    type: '"auto" | "ltr" | "rtl"',
  },
}}
/>

### Text direction (`dir`)

Use `dir` when rendering RTL languages (Persian, Arabic, Hebrew, etc.) or mixed-script content:

```tsx title="app/page.tsx"
<Streamdown dir="auto" mode="static">
  {markdown}
</Streamdown>
```

| Value             | Behavior                                                            |
| ----------------- | ------------------------------------------------------------------- |
| `"ltr"` / `"rtl"` | Applies that direction to the root (static) or each streaming block |
| `"auto"`          | Detects direction independently per block                           |
| omitted           | No `dir` attribute is set                                           |

With `dir="auto"`:

* **Streaming mode** — direction is resolved once per parsed markdown block (same unit used for memoized streaming updates).
* **Static mode** — a rehype pass assigns `dir` on each semantic block (headings, paragraphs, list items, blockquotes, definition terms/descriptions, captions, and table cells) so mixed Persian/Hebrew/Arabic/English documents keep the correct base direction without splitting the document (footnotes and cross-block references still resolve).
* **Detection** — counts Unicode strong letters after stripping common markdown syntax. The majority wins; equal counts fall back to the first strong character. Fenced and inline code are excluded from the count so LTR identifiers do not skew surrounding RTL prose.
* **Code** — fenced code blocks and code-related elements are always LTR so line numbers and syntax highlighting stay correct inside RTL documents.

## Styling Props

<TypeTable
  type={{
  shikiTheme: {
    description:
      "Light and dark themes for code syntax highlighting. Accepts bundled theme names or custom theme objects (ThemeRegistrationAny).",
    type: "[ThemeInput, ThemeInput]",
    default: "['github-light', 'github-dark']",
  },
  components: {
    description: "Custom component overrides for Markdown elements",
    type: "object",
  },
  allowedTags: {
    description:
      "Custom HTML tags to allow through sanitization, with their permitted attributes. Use with 'components' to render custom tags like <ref> or <mention>. Only works with default rehype plugins.",
    type: "Record<string, string[]>",
  },
  literalTagContent: {
    description:
      "Tags whose children are treated as plain text (no markdown parsing). Useful when tag children contain underscores or asterisks that would otherwise be formatted. Tags must also appear in allowedTags.",
    type: "string[]",
  },
  prefix: {
    description:
      "Tailwind CSS prefix prepended to all utility classes. Enables Tailwind v4 prefix() support. User-supplied className values are also prefixed.",
    type: "string",
  },
}}
/>

## Plugin Props

<TypeTable
  type={{
  rehypePlugins: {
    description: "Rehype plugins for HTML processing",
    type: "Pluggable[]",
    default: "Object.values(defaultRehypePlugins)",
  },
  remarkPlugins: {
    description: "Remark plugins for Markdown processing",
    type: "Pluggable[]",
    default: "Object.values(defaultRemarkPlugins)",
  },
}}
/>

**Default Rehype Plugins:**

* `rehype-raw` - HTML support
* `rehype-sanitize` - XSS protection and safe HTML rendering
* `rehype-harden` - Security hardening (allows all image and link prefixes, data images enabled)

**Default Remark Plugins:**

* `remark-gfm` - GitHub Flavored Markdown

Math rendering and CJK support require installing separate plugins. See [Mathematics](/docs/plugins/math) and [CJK Language Support](/docs/plugins/cjk).

## Feature-Specific Props

<TypeTable
  type={{
  mermaid: {
    description: "Mermaid diagram configuration and error handling",
    type: "MermaidOptions",
  },
  controls: {
    description:
      "Control visibility of interactive buttons and custom download filenames for code, tables, and mermaid diagrams.",
    type: "ControlsConfig",
    default: "true",
  },
  lineNumbers: {
    description: "Show line numbers in code blocks. Can be overridden per block with the noLineNumbers meta string.",
    type: "boolean",
    default: "true",
  },
  codeBlockMaxHeight: {
    description:
      "Max height for fenced code blocks. Numbers are treated as px. Strings pass through as CSS values (e.g. `50vh`). Set to `0` or `Infinity` to disable. While streaming (`isAnimating`), the block auto-scrolls to the bottom and unpins if the user scrolls up. See [Code Blocks](/docs/code-blocks#max-height).",
    type: "number | string",
    default: "400",
  },
  tableMaxHeight: {
    description:
      "Max height for tables. Numbers are treated as px. Strings pass through as CSS values (e.g. `50vh`). Set to `0` or `Infinity` to disable. While streaming (`isAnimating`), the table body auto-scrolls to the bottom and unpins if the user scrolls up. See [GitHub Flavored Markdown](/docs/gfm#max-height).",
    type: "number | string",
    default: "300",
  },
  animated: {
    description: "Enable character-by-character animation for streaming content. See [Animation](/docs/animation).",
    type: "boolean | AnimateOptions",
  },
  linkSafety: {
    description: "Configure link safety modals for external URLs. See [Link Safety](/docs/link-safety).",
    type: "LinkSafetyConfig",
    default: "{ enabled: true }",
  },
  portal: {
    description:
      "DOM node (or getter) used as the portal target for built-in overlays. Useful for micro-frontends and scoped or prefixed CSS.",
    type: "HTMLElement | null | (() => HTMLElement | null)",
    default: "document.body",
  },
  plugins: {
    description: "Plugin configuration for math, mermaid, code highlighting, and CJK support. See [Plugins](/docs/plugins).",
    type: "PluginConfig",
  },
  icons: {
    description:
      "Custom icons to override the defaults used in controls. See the IconMap interface for available keys.",
    type: "Partial<IconMap>",
  },
  translations: {
    description:
      "Override default English labels for controls and modals. See [Internationalization](/docs/internationalization).",
    type: "Partial<StreamdownTranslations>",
  },
  caret: {
    description: "Show a caret indicator at the end of streaming content. See [Carets](/docs/carets).",
    type: '"block" | "circle"',
  },
  onAnimationStart: {
    description: "Called when isAnimating transitions from false to true. Suppressed in static mode. Memoize with useCallback. See [Animation](/docs/animation).",
    type: "() => void",
  },
  onAnimationEnd: {
    description: "Called when isAnimating transitions from true to false. Suppressed in static mode. Memoize with useCallback. See [Animation](/docs/animation).",
    type: "() => void",
  },
}}
/>

### Portal Target

By default, Mermaid fullscreen, table fullscreen, and the built-in link safety modal render into `document.body`. Use `portal` to keep these overlays inside a micro-frontend or another subtree that provides scoped CSS, prefixed Tailwind utilities, or a specific stacking context.

```tsx title="app/page.tsx"
const portalRef = useRef<HTMLDivElement>(null);

return (
  <div className="my-app">
    <div ref={portalRef} />
    <Streamdown portal={() => portalRef.current}>{markdown}</Streamdown>
  </div>
);
```

The getter form is useful when the portal element is assigned after the first render. Returning `null` falls back to `document.body`. A custom `linkSafety.renderModal` controls its own placement and is not moved by this prop. Like `urlTransform` and `allowElement`, `portal` is an initializing prop: swapping it for a different target alone does not re-render, so prefer the getter form — it is resolved each time an overlay opens.

### Mermaid Options

The `mermaid` prop accepts an object with the following properties:

<TypeTable
  type={{
  config: {
    description: "Custom configuration for Mermaid diagrams",
    type: "MermaidConfig",
  },
  errorComponent: {
    description:
      "Custom React component for handling Mermaid rendering errors",
    type: "React.ComponentType<MermaidErrorComponentProps>",
  },
}}
/>

## Element Filtering Props

These props match the [react-markdown](https://github.com/remarkjs/react-markdown) API, making Streamdown a drop-in replacement.

<TypeTable
  type={{
  allowedElements: {
    description:
      "Tag names to allow (all others are removed). Cannot combine with disallowedElements.",
    type: "string[]",
  },
  disallowedElements: {
    description:
      "Tag names to disallow (all others are kept). Cannot combine with allowedElements.",
    type: "string[]",
    default: "[]",
  },
  allowElement: {
    description:
      "Custom filter function called for each element. Return false to remove. Applied after allowedElements/disallowedElements.",
    type: "(element: Element, index: number, parent: Parent | undefined) => boolean",
  },
  unwrapDisallowed: {
    description:
      "When true, disallowed elements are replaced by their children instead of being removed entirely.",
    type: "boolean",
    default: "false",
  },
  skipHtml: {
    description:
      "Ignore raw HTML in Markdown completely (removes raw HTML nodes from the tree).",
    type: "boolean",
    default: "false",
  },
  urlTransform: {
    description:
      "Transform all URLs in the Markdown (links, images, etc). Return an empty string to remove the URL. Defaults to defaultUrlTransform (passthrough). URL security is handled by rehype-sanitize and rehype-harden.",
    type: "(url: string, key: string, node: Element) => string | null | undefined",
    default: "defaultUrlTransform",
  },
}}
/>

### Element filtering example

```tsx title="app/page.tsx"
// Only allow paragraphs, links, and emphasis
<Streamdown allowedElements={["p", "a", "em"]}>
  {markdown}
</Streamdown>

// Remove images but keep everything else
<Streamdown disallowedElements={["img"]}>
  {markdown}
</Streamdown>

// Remove images but keep their alt text
<Streamdown disallowedElements={["img"]} unwrapDisallowed>
  {markdown}
</Streamdown>

// Custom filter: remove all h3+ headings
<Streamdown
  allowElement={(element) =>
    !["h3", "h4", "h5", "h6"].includes(element.tagName)
  }
>
  {markdown}
</Streamdown>
```

### URL transform example

```tsx title="app/page.tsx"
import { Streamdown, defaultUrlTransform } from 'streamdown';

// Proxy all image URLs through your CDN
<Streamdown
  urlTransform={(url, key, node) => {
    if (key === 'src') {
      return `https://your-cdn.com/proxy?url=${encodeURIComponent(url)}`;
    }
    return defaultUrlTransform(url, key, node);
  }}
>
  {markdown}
</Streamdown>
```

## Advanced Props

<TypeTable
  type={{
  BlockComponent: {
    description:
      "Custom block component for rendering individual markdown blocks",
    type: "React.ComponentType<BlockProps>",
    default: "Block",
  },
  parseMarkdownIntoBlocksFn: {
    description: "Custom function to parse markdown into blocks",
    type: "(markdown: string) => string[]",
    default: "parseMarkdownIntoBlocks",
  },
}}
/>

The `controls` prop can be configured granularly. Set a block type to `false` to hide all of its buttons, or pass an object to toggle individual actions. For downloads, pass `{ filename: "customName" }` to set a custom base filename — the file extension is added automatically.

```tsx title="app/page.tsx"
<Streamdown
  controls={{
    table: {
      copy: true, // Show table copy button
      download: { filename: "report" }, // Download as report.csv / report.md
      fullscreen: true, // Show table fullscreen button
      csvSeparator: ",", // "," | ";" | "\t" | "auto"
    },
    code: {
      copy: true, // Show code copy button
      download: { filename: "myScript" }, // Download as myScript.js, myScript.py, etc.
    },
    mermaid: {
      download: { filename: "flowchart" }, // Download as flowchart.svg / flowchart.png / flowchart.mmd
      copy: true, // Show mermaid copy button
      fullscreen: true, // Show mermaid fullscreen button
      panZoom: true, // Show mermaid pan/zoom controls
    },
  }}
>
  {markdown}
</Streamdown>
```

You can still use `download: true` (or omit it) to keep the default filenames: `file.<ext>` for code, `table.csv` / `table.md` for tables, and `diagram.svg` / `diagram.png` / `diagram.mmd` for mermaid.

### Remend Options

The `remend` prop configures which Markdown completions are performed during streaming. All options default to `true` when not specified. Set an option to `false` to disable that completion:

<TypeTable
  type={{
  links: {
    description: "Complete incomplete links",
    type: "boolean",
    default: "true",
  },
  images: {
    description: "Complete incomplete images",
    type: "boolean",
    default: "true",
  },
  bold: {
    description: "Complete bold formatting (**)",
    type: "boolean",
    default: "true",
  },
  italic: {
    description: "Complete italic formatting (* and _)",
    type: "boolean",
    default: "true",
  },
  boldItalic: {
    description: "Complete bold-italic formatting (***)",
    type: "boolean",
    default: "true",
  },
  inlineCode: {
    description: "Complete inline code formatting (`)",
    type: "boolean",
    default: "true",
  },
  strikethrough: {
    description: "Complete strikethrough formatting (~~)",
    type: "boolean",
    default: "true",
  },
  katex: {
    description: "Complete block KaTeX math ($$)",
    type: "boolean",
    default: "true",
  },
  setextHeadings: {
    description: "Handle incomplete setext headings",
    type: "boolean",
    default: "true",
  },
  comparisonOperators: {
    description: "Escape > as comparison operators in list items",
    type: "boolean",
    default: "true",
  },
  htmlTags: {
    description: "Strip incomplete HTML tags at end of streaming text",
    type: "boolean",
    default: "true",
  },
  linkMode: {
    description: "How to handle incomplete links: 'protocol' uses a placeholder URL, 'text-only' displays plain text",
    type: '"protocol" | "text-only"',
    default: '"protocol"',
  },
  handlers: {
    description: "Custom handlers to extend remend with your own completion logic",
    type: "RemendHandler[]",
  },
}}
/>

```tsx title="app/page.tsx"
<Streamdown
  remend={{
    links: false, // Disable link completion
    katex: false, // Disable KaTeX completion
  }}
>
  {markdown}
</Streamdown>
```


---

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)