MelonyMarkdown
Component for rendering markdown content with embedded HTML-like Melony components.
Import
import { MelonyMarkdown } from "melony";Basic Usage
import { MelonyMarkdown } from "melony";
const content = `
# Hello World
Here's a component:
<card title="Weather">
<text value="Sunny, 72°F" />
</card>
Some more markdown text.
`;
function Component() {
return <MelonyMarkdown>{content}</MelonyMarkdown>;
}Props
children
string • Required
Markdown content with optional embedded Melony component tags. The content is parsed progressively, rendering markdown text and components as they become complete.
<MelonyMarkdown>
## Title
<card>
<text value="Content" />
</card>
</MelonyMarkdown>components
Partial<Components> • Optional
Custom React components to override default markdown element rendering (e.g., custom heading, paragraph, or link components).
const customComponents = {
h1: ({ children }) => (
<h1 className="text-4xl font-bold text-purple-600">
{children}
</h1>
),
p: ({ children }) => (
<p className="my-4 text-gray-700">
{children}
</p>
),
a: ({ href, children }) => (
<a href={href} className="text-blue-500 underline">
{children}
</a>
),
};
<MelonyMarkdown components={customComponents}>
{content}
</MelonyMarkdown>context
Record<string, any> • Optional
Custom data object that becomes available as variables in component templates for dynamic content rendering.
const context = {
user: {
name: "John Doe",
email: "john@example.com",
},
isAuthenticated: true,
};
const content = `
Welcome, {{user.name}}!
<card title="Profile">
<text value="Email: {{user.email}}" />
<text value="Status: {{isAuthenticated ? 'Active' : 'Guest'}}" />
</card>
`;
<MelonyMarkdown context={context}>{content}</MelonyMarkdown>Progressive Rendering
MelonyMarkdown is designed for streaming AI responses. As content arrives, it progressively parses and renders:
- Markdown text is rendered immediately
- Component tags are parsed as they become complete
- Components render instantly when their closing tag arrives
- No waiting for the entire response to complete
import { useChat } from "ai/react";
function Chat() {
const { messages } = useChat({ api: "/api/chat" });
return messages.map((message) => (
<div key={message.id}>
{message.role === "assistant" ? (
<MelonyMarkdown>{message.content}</MelonyMarkdown>
) : (
<p>{message.content}</p>
)}
</div>
));
}Supported Markdown
MelonyMarkdown supports GitHub Flavored Markdown (GFM):
- Headings (# ## ### etc.)
- Bold, italic, strikethrough
- Lists (ordered and unordered)
- Links and images
- Code blocks and inline code
- Blockquotes
- Tables
- Task lists
Embedded Components
All Melony components can be embedded using HTML-like syntax:
## My Dashboard
Here's some markdown text.
<card title="Stats" padding="lg">
<row gap="md">
<column>
<text value="Users" size="sm" color="muted" />
<text value="1,234" size="xl" weight="bold" />
</column>
<column>
<text value="Revenue" size="sm" color="muted" />
<text value="$12,345" size="xl" weight="bold" />
</column>
</row>
</card>
More markdown text here.
<button label="Refresh" action='{"type":"refresh"}' />
The end.With Context
Use context to pass dynamic data to templates:
const context = {
stats: {
users: 1234,
revenue: 12345,
},
products: [
{ name: "Product A", sales: 120 },
{ name: "Product B", sales: 98 },
],
};
const content = `
## Dashboard
<card title="Statistics">
<text value="Users: {{stats.users}}" />
<text value="Revenue: \$\{{stats.revenue\}}" />
</card>
<card title="Top Products">
<for items="{{products}}">
<text value="{{item.name}}: {{item.sales}} sales" />
</for>
</card>
`;
<MelonyMarkdown context={context}>{content}</MelonyMarkdown>Complete Example
import { MelonyProvider, MelonyMarkdown } from "melony";
import { useChat } from "ai/react";
export default function Chat() {
const { messages, input, handleInputChange, handleSubmit } = useChat({
api: "/api/chat",
});
const context = {
user: {
name: "Alice",
email: "alice@example.com",
},
timestamp: new Date().toLocaleString(),
};
const handleAction = (action) => {
console.log("Action:", action.type);
};
return (
<MelonyProvider onAction={handleAction}>
<div className="flex flex-col h-screen">
<div className="flex-1 overflow-auto p-4 space-y-4">
{messages.map((message) => (
<div key={message.id} className="message">
{message.role === "assistant" ? (
<MelonyMarkdown context={context}>
{message.content}
</MelonyMarkdown>
) : (
<div className="user-message">
{message.content}
</div>
)}
</div>
))}
</div>
<form onSubmit={handleSubmit} className="border-t p-4">
<input
value={input}
onChange={handleInputChange}
placeholder="Type a message..."
className="w-full px-4 py-2 border rounded"
/>
</form>
</div>
</MelonyProvider>
);
}Notes
- Must be used within a
MelonyProviderto access theme and action handlers - Component tags must be properly closed (self-closing or with closing tag)
- Context variables use
{{variable}}syntax - Progressive rendering works best with streaming AI responses
- Markdown and components can be freely mixed in any order
See Also
MelonyProvider - Root provider component
Components - Available component tags
Context System - Learn about context variables