The react-markdown-parser package provides a React Server Component for rendering markdown content with full support for custom component styling.
Installation
Install the React package:
npm install react-markdown-parser
This package requires markdown-parser as a peer dependency. It will be installed automatically if not present.
Basic usage
Import the Markdown component and pass your content:
import { Markdown } from "react-markdown-parser";
export function Article({ content }: { content: string }) {
return (
<div className="prose">
<Markdown content={content} />
</div>
);
}
The Markdown component is a React Server Component, making it perfect for server-side rendering and static generation.
How it works
Under the hood, the Markdown component:
- Parses the markdown content using
MarkdownParser
- Converts block and inline nodes to React elements
- Applies custom components (if provided)
- Returns a React element tree
// Internally, the component does:
const nodes = new MarkdownParser().parse(content);
return nodes.map(node => <BlockNodeComponent node={node} />);
Default rendering
Without customization, the component renders semantic HTML:
Block elements
<Markdown content="# Heading\n\nThis is a **paragraph**." />
// Renders:
// <h1>Heading</h1>
// <p>This is a <strong>paragraph</strong>.</p>
Lists
<Markdown content="- Item 1\n- Item 2\n- Item 3" />
// Renders:
// <ul>
// <li>Item 1</li>
// <li>Item 2</li>
// <li>Item 3</li>
// </ul>
Code blocks
<Markdown content="```js\nconsole.log('hello');\n```" />
// Renders:
// <pre>console.log('hello');
// </pre>
Styling with Tailwind
Use Tailwind’s typography plugin for instant styling:
import { Markdown } from "react-markdown-parser";
export function StyledArticle({ content }: { content: string }) {
return (
<article className="prose prose-slate lg:prose-lg mx-auto">
<Markdown content={content} />
</article>
);
}
Customizing specific elements
Override individual component rendering with the components prop:
import { Markdown } from "react-markdown-parser";
export function Article({ content }: { content: string }) {
return (
<Markdown
content={content}
components={{
Heading: ({ level, children }) => {
const Heading = `h${level}` as const;
return (
<Heading className="font-bold text-blue-900">
{children}
</Heading>
);
},
Paragraph: ({ children }) => (
<p className="my-4 leading-relaxed text-gray-700">
{children}
</p>
),
Link: ({ href, title, children }) => (
<a
href={href}
title={title}
className="text-blue-500 hover:underline"
>
{children}
</a>
),
}}
/>
);
}
Available component overrides
You can customize any of these components:
Block components
Heading
Paragraph
CodeBlock
List
Blockquote
Table
ThematicBreak
Heading: ({ level, children }) => ReactNode
// level: 1 | 2 | 3 | 4 | 5 | 6
Paragraph: ({ children }) => ReactNode
CodeBlock: ({ content, info }) => ReactNode
// content: the code string
// info: language identifier (e.g., "javascript")
List: (props) => ReactNode
// props for ordered: { type: "ordered", items: [...], start?: number }
// props for unordered: { type: "unordered", items: [...] }
Blockquote: ({ children }) => ReactNode
Table: ({ head, body }) => ReactNode
// head: { cells: [{ children, align }] }
// body: { rows: [{ cells: [{ children, align }] }] }
ThematicBreak: () => ReactNode
// Renders <hr /> by default
Inline components
Text
Strong
Emphasis
Link
Image
CodeSpan
Text: ({ text }) => ReactNode
Strong: ({ children }) => ReactNode
Emphasis: ({ children }) => ReactNode
Link: ({ href, title, children }) => ReactNode
Image: ({ href, title, alt }) => ReactNode
CodeSpan: ({ text }) => ReactNode
Complete styling example
Here’s a fully customized markdown renderer from the demo:
import { Markdown } from "react-markdown-parser";
export default function StyledMarkdown({ content }: { content: string }) {
return (
<div className="max-w-4xl mx-auto py-10 px-8">
<Markdown
content={content}
components={{
Heading: ({ children, level }) => {
const Heading = `h${level}` as const;
const sizes = {
1: "text-3xl leading-10",
2: "text-2xl leading-9",
3: "text-xl leading-8",
4: "text-lg leading-7",
5: "text-base leading-6",
6: "text-sm leading-5",
};
return (
<Heading className={`mt-6 mb-1.5 font-semibold ${sizes[level]}`}>
{children}
</Heading>
);
},
Paragraph: ({ children }) => (
<p className="my-3 leading-relaxed text-pretty">{children}</p>
),
CodeBlock: ({ content }) => (
<pre className="px-4 py-3 rounded-md border overflow-x-auto">
<code className="font-mono">{content}</code>
</pre>
),
CodeSpan: ({ text }) => (
<code className="bg-gray-100 text-[85%] px-1 py-1.5 rounded-sm">
{text}
</code>
),
Blockquote: ({ children }) => (
<blockquote className="my-4 border-l-4 border-gray-200 pl-4">
{children}
</blockquote>
),
List: (props) => {
if (props.type === "ordered") {
return (
<ol className="my-3 pl-5 list-decimal" start={props.start}>
{props.items.map((item, index) => (
<li key={index}>{item.children}</li>
))}
</ol>
);
}
return (
<ul className="my-3 pl-5 list-disc">
{props.items.map((item, index) => (
<li key={index}>{item.children}</li>
))}
</ul>
);
},
Link: ({ href, title, children }) => (
<a
href={href}
title={title}
className="text-blue-500 underline hover:text-blue-600"
>
{children}
</a>
),
Strong: ({ children }) => (
<strong className="font-semibold">{children}</strong>
),
Emphasis: ({ children }) => (
<em className="italic">{children}</em>
),
ThematicBreak: () => (
<hr className="my-6 border-0 border-t border-gray-200" />
),
Table: ({ head, body }) => (
<table className="w-full my-4 border-collapse">
<thead className="border-b-2">
<tr>
{head.cells.map((cell, index) => (
<th
key={index}
align={cell.align}
className="px-3 py-2 text-left"
>
{cell.children}
</th>
))}
</tr>
</thead>
<tbody>
{body.rows.map((row, index) => (
<tr key={index} className="border-b">
{row.cells.map((cell, index) => (
<td
key={index}
align={cell.align}
className="px-3 py-2"
>
{cell.children}
</td>
))}
</tr>
))}
</tbody>
</table>
),
}}
/>
</div>
);
}
Syntax highlighting
Add syntax highlighting to code blocks using a library like Shiki or Prism:
import { codeToHtml } from "shiki";
import { Markdown } from "react-markdown-parser";
export async function HighlightedMarkdown({ content }: { content: string }) {
return (
<Markdown
content={content}
components={{
CodeBlock: async ({ content, info }) => {
const html = await codeToHtml(content, {
lang: info || "text",
theme: "github-dark",
});
return <div dangerouslySetInnerHTML={{ __html: html }} />;
},
}}
/>
);
}
When using dangerouslySetInnerHTML, ensure the content is trusted and properly sanitized.
Link validation
Custom Link components can validate and sanitize URLs:
Link: ({ href, title, children }) => {
// Validate URL
const isExternal = href.startsWith("http");
const isValid = /^(https?:\/\/|\/)/.test(href);
if (!isValid) {
return <span className="text-red-500">{children}</span>;
}
return (
<a
href={href}
title={title}
target={isExternal ? "_blank" : undefined}
rel={isExternal ? "noopener noreferrer" : undefined}
className="text-blue-500 hover:underline"
>
{children}
</a>
);
}
The default Link component includes basic URL validation to prevent javascript: and other dangerous protocols.
TypeScript types
The components prop is fully typed for excellent IDE support:
import { Markdown, type MarkdownComponents } from "react-markdown-parser";
const customComponents: Partial<MarkdownComponents> = {
Heading: ({ level, children }) => {
// TypeScript knows level is 1 | 2 | 3 | 4 | 5 | 6
return <h1>{children}</h1>;
},
// ... other components
};
export function Article({ content }: { content: string }) {
return <Markdown content={content} components={customComponents} />;
}
Next steps
Custom components
Advanced component customization patterns
API reference
Explore the full Markdown component API