Markdown Example
This is a sample paragraph that demonstrates how markdown content can be rendered in your application. It shows various markdown elements working together.
Features List
- Clean and simple syntax
- Support for headings and paragraphs
- Bullet point lists
- Easy to read and write
- Customizable styling
Text Formatting
You can make text bold, italic, or both. You can also use strikethrough text.
Links and Images
Here's a link to GitHub and below is a sample image:
![]()
Code Examples
Inline code: const greeting = "Hello World!"
Code Block
// Code block
function sayHello() {
console.log("Hello!");
}
Blockquotes
This is a blockquote It can span multiple lines
And can be nested
Tables
| Header 1 | Header 2 | Header 3 |
|---|---|---|
| Row 1 | Data | Data |
| Row 2 | Data | Data |
Task Lists
- Completed task
- Pending task
- Another task
Horizontal Rule
Ordered List
- First item
- Second item
- Third item
- Sub-item 1
- Sub-item 2
"use client";
import { useState } from "react";
import { MarkdownContent } from "@/components/ui/markdown-content";
export function MarkdownContentDemo() {
const [content] = useState(
`# Markdown Example
This is a sample paragraph that demonstrates how markdown content can be rendered in your application. It shows various markdown elements working together.
## Features List
- Clean and simple syntax
- Support for headings and paragraphs
- Bullet point lists
- Easy to read and write
- Customizable styling
## Text Formatting
You can make text **bold**, *italic*, or ***both***. You can also use ~~strikethrough~~ text.
### Links and Images
Here's a [link to GitHub](https://github.com) and below is a sample image:

### Code Examples
Inline code: \`const greeting = "Hello World!"\`
#### Code Block
\`\`\`javascript
// Code block
function sayHello() {
console.log("Hello!");
}
\`\`\`
### Blockquotes
> This is a blockquote
> It can span multiple lines
>> And can be nested
### Tables
| Header 1 | Header 2 | Header 3 |
|----------|----------|----------|
| Row 1 | Data | Data |
| Row 2 | Data | Data |
### Task Lists
- [x] Completed task
- [ ] Pending task
- [ ] Another task
### Horizontal Rule
---
### Ordered List
1. First item
2. Second item
3. Third item
1. Sub-item 1
2. Sub-item 2
`,
);
return (
<div className="w-full max-h-[400px] overflow-y-auto">
<MarkdownContent content={content} />
</div>
);
}
This component provides a simple way to render Markdown content with optimized performance through memoization. The implementation uses memoized blocks which prevents unnecessary re-renders and improves rendering efficiency, especially when dealing with streaming content.
The memoization approach is inspired by the Vercel AI SDK's markdown chatbot example. As they explain, memoization caches parsed Markdown blocks and reuses them to avoid redundant parsing and rendering operations. This is particularly beneficial when content is being streamed or updated frequently, as it ensures that only changed blocks are re-rendered rather than the entire content.
Installation
pnpm dlx shadcn@latest add @simple-ai/markdown-content
Usage
import { MarkdownContent } from "@/components/ui/markdown-content"<MarkdownContent id="my-id" content={content} />Examples
Streaming
This component supports streaming content with automatic batching
"use client";
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import { MarkdownContent } from "../ui/markdown-content";
export function MarkdownStreamingDemo() {
const [content, setContent] = useState("");
const [isStreaming, setIsStreaming] = useState(false);
const fullContent = `# The Lost Key
## A Tale of Mystery
* Sarah woke up to find her favorite golden key missing from its usual spot
* She remembered using it last night to lock her diary
* The search began:
* Under the bed - nothing but dust
* In her coat pockets - empty
* On her desk - just scattered papers
> "Sometimes what we're looking for is right where we least expect it"
* As she made her bed, something shiny caught her eye
* The key had slipped between the pages of her book
* With a smile, she realized she'd been using it as a bookmark
*The End*`;
useEffect(() => {
// Typically you'd use a streaming API to get the content, this is just a demo
if (!isStreaming) {
return;
}
let currentIndex = 0;
const words = fullContent.split(" ");
const streamInterval = setInterval(() => {
if (currentIndex >= words.length) {
clearInterval(streamInterval);
setIsStreaming(false);
return;
}
const nextChunk = words.slice(0, currentIndex + 3).join(" ");
setContent(nextChunk);
currentIndex += 3;
}, 70);
return () => clearInterval(streamInterval);
}, [isStreaming]);
const handleStart = () => {
setContent("");
setIsStreaming(true);
};
return (
<div className="space-y-4 w-full max-h-[400px] overflow-y-auto">
<div className="flex gap-2">
<Button onClick={handleStart} disabled={isStreaming}>
{content ? "Restart" : "Start"} Streaming
</Button>
</div>
<div className="p-4 w-full min-h-[200px] border rounded-md overflow-y-auto">
<MarkdownContent content={content} />
</div>
</div>
);
}