MarkdownFlow UI Component Library
React component library for rendering interactive MarkdownFlow documents with typewriter effects and real-time streaming. Features automated CI/CD release management.
MarkdownFlow (also known as MDFlow or markdown-flow) extends standard Markdown with AI to create personalized, interactive pages. Its tagline is "Write Once, Deliver Personally".
๐ Quick Start
Install
npm install markdown-flow-ui
yarn add markdown-flow-ui
pnpm add markdown-flow-ui
Basic Usage
import { MarkdownFlow } from "markdown-flow-ui";
function App() {
return (
<MarkdownFlow
initialContentList={[
{
content:
"# Hello World\n\nThis is **MarkdownFlow** with typewriter effect!",
},
]}
disableTyping={false}
typingSpeed={30}
/>
);
}
Interactive Elements
import { MarkdownFlow } from "markdown-flow-ui";
function InteractiveExample() {
const content = `
Choose your language: ?[%{{lang}} English | ไธญๆ | Espaรฑol]
Your name: ?[%{{name}} Enter your name...]
?[Continue | Cancel]
`;
return (
<MarkdownFlow
initialContentList={[{ content }]}
onSend={(params) => {
console.log("User interaction:", params);
// Handle button clicks and input submissions
}}
/>
);
}
Streaming with SSE
import { ScrollableMarkdownFlow } from "markdown-flow-ui";
import { useSSE } from "markdown-flow-ui";
function StreamingChat() {
const [messages, setMessages] = useState([]);
const { data, isConnected } = useSSE("/api/stream", {
onMessage: (chunk) => {
setMessages((prev) => {
const last = prev[prev.length - 1];
if (last && !last.isFinished) {
return [
...prev.slice(0, -1),
{ ...last, content: last.content + chunk },
];
}
return [...prev, { content: chunk, isFinished: false }];
});
},
});
return (
<ScrollableMarkdownFlow
height="500px"
initialContentList={messages}
onSend={(params) => {
// Send user input to backend
fetch("/api/chat", {
method: "POST",
body: JSON.stringify(params),
});
}}
/>
);
}
๐ API Reference
Components
MarkdownFlow
Main component for rendering markdown with typewriter effects.
interface MarkdownFlowProps {
initialContentList?: ContentItem[];
customRenderBar?: CustomRenderBarProps;
onSend?: (content: OnSendContentParams) => void;
typingSpeed?: number;
disableTyping?: boolean;
onBlockComplete?: (blockIndex: number) => void;
}
type ContentItem = {
content: string;
isFinished?: boolean;
defaultInputText?: string;
defaultButtonText?: string;
readonly?: boolean;
customRenderBar?: CustomRenderBarProps;
};
type OnSendContentParams = {
buttonText?: string;
variableName?: string;
inputText?: string;
};
Props:
initialContentList - Array of content blocks to render
typingSpeed - Typing animation speed (default: 30ms/char)
disableTyping - Disable typewriter effect (default: false)
onSend - Callback for user interactions
onBlockComplete - Called when a block finishes typing
customRenderBar - Custom component for additional UI
Example:
<MarkdownFlow
initialContentList={[
{
content: "# Welcome\n\nChoose: ?[%{{choice}} A | B | C]",
isFinished: false,
},
]}
typingSpeed={50}
onSend={(params) => {
if (params.variableName === "choice") {
console.log("Selected:", params.buttonText);
}
}}
/>
ScrollableMarkdownFlow
Enhanced version with auto-scrolling and scroll management.
interface ScrollableMarkdownFlowProps extends MarkdownFlowProps {
height?: string | number;
className?: string;
}
Additional Props:
height - Container height (default: "100%")
className - Additional CSS classes
Features:
- Auto-scrolls to bottom on new content
- Shows scroll-to-bottom button when needed
- Smooth scrolling behavior
Example:
<ScrollableMarkdownFlow
height="400px"
initialContentList={messages}
onSend={handleUserMessage}
className="chat-container"
/>
ContentRender
Core component for rendering individual markdown blocks.
interface ContentRenderProps {
content: string;
customRenderBar?: CustomRenderBarProps;
onSend?: (content: OnSendContentParams) => void;
typingSpeed?: number;
disableTyping?: boolean;
defaultButtonText?: string;
defaultInputText?: string;
readonly?: boolean;
onTypeFinished?: () => void;
tooltipMinLength?: number;
}
Props:
content - Markdown content to render
typingSpeed - Animation speed (default: 30)
disableTyping - Disable animation (default: true)
readonly - Make interactive elements read-only
onTypeFinished - Called when typing completes
tooltipMinLength - Min length for tooltips (default: 10)
Supported Markdown:
- Standard markdown (headers, lists, links, etc.)
- GitHub Flavored Markdown (tables, task lists)
- Math expressions with KaTeX:
$E = mc^2$
- Mermaid diagrams
- Code syntax highlighting
- Custom interactive syntax
Custom Syntax:
# Buttons
?[Click me]
# Variable inputs
?[%{{userName}} Enter name...]
# Multiple choice
?[%{{color}} Red | Blue | Green]
# Mermaid diagrams
```mermaid
graph LR
A --> B
B --> C
```
Hooks
useTypewriter
Manages typewriter animation effects.
function useTypewriter(
content: string,
speed?: number,
disabled?: boolean
): {
displayText: string;
isComplete: boolean;
start: () => void;
pause: () => void;
reset: () => void;
};
Example:
const { displayText, isComplete, start, pause } = useTypewriter(
"Hello, World!",
50,
false
);
return (
<div>
<p>{displayText}</p>
{!isComplete && <button onClick={pause}>Pause</button>}
</div>
);
useScrollToBottom
Auto-scroll management for containers.
function useScrollToBottom(
containerRef: RefObject<HTMLElement>,
dependencies: any[],
options?: {
behavior?: "smooth" | "auto";
autoScrollOnInit?: boolean;
scrollDelay?: number;
}
): {
showScrollToBottom: boolean;
handleUserScrollToBottom: () => void;
};
Example:
const containerRef = useRef(null);
const { showScrollToBottom, handleUserScrollToBottom } = useScrollToBottom(
containerRef,
[messages.length],
{ behavior: "smooth" }
);
return (
<div ref={containerRef}>
{messages.map((msg) => (
<div key={msg.id}>{msg.text}</div>
))}
{showScrollToBottom && (
<button onClick={handleUserScrollToBottom}>โ</button>
)}
</div>
);
useSSE
Server-Sent Events integration.
function useSSE(
url: string,
options?: {
onMessage?: (data: any) => void;
onError?: (error: Error) => void;
onOpen?: () => void;
reconnect?: boolean;
reconnectInterval?: number;
}
): {
data: any;
isConnected: boolean;
error: Error | null;
close: () => void;
};
Example:
const { data, isConnected, error } = useSSE("/api/stream", {
onMessage: (chunk) => {
setContent((prev) => prev + chunk);
},
reconnect: true,
reconnectInterval: 3000,
});
Types
type ContentItem = {
content: string;
isFinished?: boolean;
defaultInputText?: string;
defaultButtonText?: string;
readonly?: boolean;
customRenderBar?: CustomRenderBarProps;
};
type OnSendContentParams = {
buttonText?: string;
variableName?: string;
inputText?: string;
};
type CustomRenderBarProps = React.ComponentType<{
content?: string;
onSend?: (content: OnSendContentParams) => void;
displayContent: string;
}>;
import type {
MarkdownFlowProps,
ScrollableMarkdownFlowProps,
ContentRenderProps,
} from "markdown-flow-ui";
Plugins
Built-in Plugins
Custom Variable Plugin:
Handles interactive buttons and inputs.
?[Button Text] # Simple button
?[%{{variable}} Placeholder...] # Input field
?[%{{choice}} A | B | C] # Multiple choice
Mermaid Plugin:
Renders diagrams using Mermaid.
```mermaid
graph TD
A[Start] --> B[Process]
B --> C[End]
```
Creating Custom Plugins
const CustomPlugin: React.FC<{ value: string; type?: string }> = ({
value,
type = 'default'
}) => {
return (
<div className="custom-plugin">
<span>{type}: {value}</span>
</div>
);
};
const components = {
'custom-element': CustomPlugin,
};
Styling
The library uses Tailwind CSS and provides customization through:
CSS Classes:
.markdown-flow {
}
.content-render {
}
.content-render-table {
}
.content-render-ol {
}
.content-render-ul {
}
.scrollable-markdown-container {
}
.scroll-to-bottom-btn {
}
CSS Variables:
:root {
--markdown-flow-primary: #2563eb;
--markdown-flow-background: #ffffff;
--markdown-flow-text: #1f2937;
--markdown-flow-border: #d1d5db;
--markdown-flow-code-bg: #f3f4f6;
}
Component Classes:
<MarkdownFlow className="my-custom-flow" />
<ScrollableMarkdownFlow className="chat-interface" />
๐งฉ Advanced Examples
Custom Render Bar
const CustomBar: CustomRenderBarProps = ({ displayContent, onSend }) => {
return (
<div className="flex gap-2 mt-4">
<button
onClick={() => onSend({ buttonText: "Regenerate" })}
className="px-4 py-2 bg-blue-500 text-white rounded"
>
Regenerate
</button>
<button
onClick={() => navigator.clipboard.writeText(displayContent)}
className="px-4 py-2 bg-gray-500 text-white rounded"
>
Copy
</button>
</div>
);
};
<MarkdownFlow customRenderBar={CustomBar} initialContentList={messages} />;
Streaming Integration
const StreamingChat = () => {
const [content, setContent] = useState("");
useSSE("/api/stream", {
onMessage: (data) => {
setContent((prev) => prev + data.chunk);
},
});
return (
<ScrollableMarkdownFlow
initialContentList={[{ content, isFinished: false }]}
disableTyping={false}
typingSpeed={20}
/>
);
};
Multi-Block Conversation
const Conversation = () => {
const [blocks, setBlocks] = useState([
{ content: "# Assistant\n\nHello! How can I help?", isFinished: true },
{
content: "What would you like to know?\n\n?[%{{topic}} Enter topic...]",
isFinished: false,
},
]);
const handleSend = (params) => {
if (params.variableName === "topic") {
setBlocks((prev) => [
...prev,
{ content: `You asked about: ${params.inputText}`, isFinished: false },
]);
}
};
return (
<MarkdownFlow
initialContentList={blocks}
onSend={handleSend}
onBlockComplete={(index) => {
setBlocks((prev) =>
prev.map((b, i) => (i === index ? { ...b, isFinished: true } : b))
);
}}
/>
);
};
๐ MarkdownFlow Ecosystem
markdown-flow-ui is part of the MarkdownFlow ecosystem for creating personalized, AI-driven interactive documents:
- markdown-flow - The main repository containing homepage, documentation, and interactive playground
- markdown-flow-agent-py - Python agent for transforming MarkdownFlow documents into personalized content
- markdown-it-flow - markdown-it plugin to parse and render MarkdownFlow syntax
- remark-flow - Remark plugin to parse and process MarkdownFlow syntax in React applications
๐ License
MIT License - see LICENSE file for details.
๐ Acknowledgments
๐ Support