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 MelonyProvider to 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