A lightweight, customizable React component for rendering Markdown content with TypeScript support and extensible rendering.
- 🚀 Fast and Lightweight: Built on top of marked for efficient parsing
- 🎨 Fully Customizable: Override any element renderer with custom React components
- 🔧 TypeScript Support: Full type safety with comprehensive TypeScript definitions
- 🧩 Extensible: Support for custom extensions and element types
- 📝 GFM Support: GitHub Flavored Markdown support out of the box
- 📚 Storybook: Interactive examples and documentation
View Live Storybook Documentation →
Explore interactive examples, API documentation, and usage patterns in our Storybook deployment.
npm install @domeadev/react-markdownyarn add @domeadev/react-markdownpnpm add @domeadev/react-markdownimport { ReactMarkdown, useReactMarkdown } from "@domeadev/react-markdown";
function MyComponent() {
const markdown = `
# Hello World
This is **bold** text and this is *italic* text.
- List item 1
- List item 2
- [ ] Todo item
- [x] Completed item
[Link to example](https://example.com)
`;
const { elements, renders } = useReactMarkdown(markdown);
return <ReactMarkdown elements={elements} renders={renders} />;
}The main hook for parsing markdown content.
const { elements, renders } = useReactMarkdown(markdown, options);markdown(string): The markdown content to parseoptions(optional): Configuration options
interface ReactMarkdownOptions {
/** Enable GitHub Flavored Markdown @default true */
gfm?: boolean;
/** Handle line breaks */
breaks?: boolean;
/** Custom extensions */
extensions?: ReactMarkdownExtension[];
/** Override default element renderers */
renders?: Partial<DefaultElementRenders>;
}The main component for rendering parsed markdown elements.
<ReactMarkdown elements={elements} renders={renders} />elements: Parsed markdown elements fromuseReactMarkdownrenders: Element renderers (default + custom overrides)
You can customize how any markdown element is rendered:
import { ReactMarkdown, useReactMarkdown } from "@domeadev/react-markdown";
function CustomMarkdown() {
const markdown = "# Custom Heading\n\nThis is a **bold** paragraph.";
const { elements } = useReactMarkdown(markdown, {
renders: {
// Custom heading renderer
heading: ({ element, children }) => {
const headingElement = element as MarkdownHeadingElement;
return (
<h1 className={`custom-h${headingElement.depth}`}>🎉 {children}</h1>
);
},
// Custom paragraph renderer
paragraph: ({ children }) => (
<p className="custom-paragraph">{children}</p>
),
// Custom strong (bold) renderer
strong: ({ children }) => (
<span className="font-bold text-blue-600">{children}</span>
),
},
});
return <ReactMarkdown elements={elements} renders={renders} />;
}The following markdown elements are supported with default renderers:
| Element | Description | Custom Props |
|---|---|---|
heading |
H1-H6 headings | depth: 1-6 |
paragraph |
Paragraph text | - |
list |
Ordered/unordered lists | ordered: boolean |
list_item |
List items | task: boolean, checked?: boolean |
link |
Links | href: string |
image |
Images | src: string, alt: string |
code |
Code blocks | lang?: string |
codespan |
Inline code | - |
strong |
Bold text | - |
emphasis |
Italic text | - |
delete |
Strikethrough text | - |
blockquote |
Block quotes | - |
table |
Tables | align: TableAlign[] |
table_header |
Table header | - |
table_body |
Table body | - |
table_row |
Table row | - |
table_cell |
Table cell | header: boolean, align: TableAlign |
br |
Line break | - |
hr |
Horizontal rule | - |
text |
Plain text | - |
space |
Whitespace | - |
You can create custom extensions to handle new markdown syntax:
import { ReactMarkdownExtension } from "@domeadev/react-markdown";
// Example: Custom mention extension
const mentionExtension: ReactMarkdownExtension = {
name: "mention",
level: "inline",
start(src: string) {
return src.indexOf("@");
},
tokenizer(src: string) {
// Match @username pattern (letters, numbers, underscore, hyphen)
const rule = /^@([a-zA-Z0-9_-]+)/;
const match = rule.exec(src);
if (match) {
return {
type: "mention",
raw: match[0],
username: match[1],
};
}
return undefined;
},
parser: (token) => ({
type: "mention",
text: token.raw,
username: token.username,
}),
render: ({ element }) => <span className="mention">@{element.username}</span>,
};
const { elements, renders } = useReactMarkdown(markdown, {
extensions: [mentionExtension],
});import { ReactMarkdown, useReactMarkdown } from "@domeadev/react-markdown";
import "./markdown-styles.css";
function StyledMarkdown({ content }: { content: string }) {
const { elements, renders } = useReactMarkdown(content, {
renders: {
heading: ({ element, children }) => {
const HeadingTag = `h${element.depth}` as keyof JSX.IntrinsicElements;
return (
<HeadingTag className={`heading-${element.depth} mb-4 font-bold`}>
{children}
</HeadingTag>
);
},
code: ({ element }) => (
<pre className="bg-gray-100 p-4 rounded-lg overflow-x-auto">
<code className={`language-${element.lang || "text"}`}>
{element.text}
</code>
</pre>
),
link: ({ element, children }) => (
<a
href={element.href}
className="text-blue-600 hover:text-blue-800 underline"
target="_blank"
rel="noopener noreferrer"
>
{children}
</a>
),
},
});
return (
<div className="prose prose-lg max-w-none">
<ReactMarkdown elements={elements} renders={renders} />
</div>
);
}function TaskListExample() {
const todoMarkdown = `
## My Todo List
- [x] Complete the documentation
- [x] Add TypeScript support
- [ ] Write more tests
- [ ] Add more examples
`;
const { elements, renders } = useReactMarkdown(todoMarkdown, {
renders: {
list_item: ({ element, children }) => (
<li className="flex items-center gap-2">
{element.task && (
<input
type="checkbox"
checked={element.checked}
className="form-checkbox h-4 w-4 text-blue-600"
readOnly
/>
)}
<span className={element.checked ? "line-through text-gray-500" : ""}>
{children}
</span>
</li>
),
},
});
return <ReactMarkdown elements={elements} renders={renders} />;
}pnpm buildpnpm testStart the development server:
pnpm storybookBuild Storybook for production:
pnpm build-storybookThe Storybook documentation is automatically deployed to GitHub Pages on every push to the main branch.
For repository maintainers, the GitHub Pages deployment is handled automatically via GitHub Actions. To enable this:
- Go to your repository's Settings → Pages
- Set Source to "GitHub Actions"
- The workflow will automatically deploy Storybook to
https://[username].github.io/[repository-name]/
Contributions are welcome! Please feel free to submit a Pull Request.
MIT
- marked - Fast markdown parser
- @domeadev/react-elements-renderer - Efficient React element rendering
@domeadev/react-elements-renderer- The underlying element rendering engine