Markdown that stays smooth while it streams
Deterministic pacing, stable layout, and semantic output for AI responses that are still being written.
Framework examples
Smoothstream renders a growing Markdown response as semantic HTML, with stable layout and predictable pacing.
npm install @smoothstream/react
Updates can arrive at any speed or size. Smoothstream turns them into one continuous reveal and keeps unfinished Markdown from appearing as broken syntax.
Pass the whole response as it grows. Smoothstream handles revealing the new text.
In React, render that response as children and use receiving to indicate whether more content is coming:
import { Smoothstream } from "@smoothstream/react";
export function AssistantMessage({ message }) {
return (
<Smoothstream receiving={message.isStreaming}>
{message.content}
</Smoothstream>
);
}
Keep receiving true while the response is being generated. Set it to false when generation ends; the current reveal will finish naturally.
Use a new React key for each response. For messages that were already complete when mounted, use mode="static".
The Vue 3 adapter renders a growing Markdown response as semantic HTML, with stable layout and predictable pacing.
npm install @smoothstream/vue
Pass the response through the reactive markdown prop. As it grows, Smoothstream adds new content to the existing reveal instead of restarting the animation.
<script setup lang="ts">
import { Smoothstream } from "@smoothstream/vue";
</script>
<template>
<Smoothstream
:markdown="message.content"
:receiving="message.isStreaming"
/>
</template>
Vue automatically unwraps refs used in templates, so reactive response state can be passed directly without creating a watcher.
Keep receiving true while the response is being generated. Set it to false when generation ends; the current reveal will finish naturally.
Give each response a new Vue key. For messages that were already complete when mounted, mode="static" renders settled semantic HTML and works with Vue SSR and Nuxt.
Use Smoothstream directly in the browser with no UI framework required. The DOM package renders semantic HTML, includes default styles, and ships with TypeScript types.
npm install @smoothstream/dom
Create a controller for the element that should contain the response. Call update() whenever the Markdown changes; the controller preserves one continuous reveal across updates.
One controller represents one response. Create a new controller for the next response.
Start the controller while input is open, then pass the current Markdown and receiving state to update():
import { createSmoothstream } from "@smoothstream/dom";
const container = document.querySelector("#answer");
if (!container) throw new Error("Missing answer container");
const stream = createSmoothstream(container, {
receiving: true,
});
stream.update(markdown, { receiving });
Keep receiving true while the response is being generated. Set it to false with the final update; the current reveal will finish naturally.
Call stream.destroy() when the containing view is removed. For Markdown that was already complete when mounted, create the controller with mode: "static".