fgmd has no React component yet. I dont use React myself, so I prioritised Svelte. This will come in a future version.
For the time being: markdown() returns an HTML string that is safe to put on a page with the default options, so rendering it takes one line:
import { useMemo } from 'react';
import { markdown, callouts } from '@decbr/fgmd';
const options = { plugins: [callouts()], classes: { p: 'mb-4', a: 'text-accent' } };
export function Markdown({ source }) {
const html = useMemo(() => markdown(source, options), [source]);
return <div dangerouslySetInnerHTML={{ __html: html }} />;
}Keep the options outside the component, or add them to useMemo's dependencies if they change.
When this runs in the browser, fgmd decodes named entities like © with the browser's own HTML parser instead of shipping its 2,000-name table, and the output is the same as on the server (see Svelte).
For a single line without the wrapping <p>, use markdownInline():
<span dangerouslySetInnerHTML={{ __html: markdownInline(blurb) }} />With the default options, and with html: 'sanitize', yes. Raw HTML is escaped or rebuilt from an allowlist, URLs are checked, and all text and attributes are escaped. See Safety.
html: true is not safe for input other people write, since it passes their HTML through untouched. Use it only for content you wrote.
In a React Server Component (such as a Next.js App Router page), the Markdown is rendered on the server and the parser never reaches the browser:
import { markdown } from '@decbr/fgmd';
export default function Post({ source }) {
return <article dangerouslySetInnerHTML={{ __html: markdown(source) }} />;
}Every option works, including highlight and renderers, because this is HTML output. classes sets per-element classes, and linkAttrs and urlPolicy control links.
Unlike the Svelte component, there's no way to render a node with your own React component, such as a link as Next.js <Link> or a code block as a <Highlight>. renderers can change a node's HTML, but it returns a string, not React elements.
The usual reason to want <Link> is client-side navigation. A click handler on the container gets you that for internal links:
'use client';
import { useMemo } from 'react';
import { useRouter } from 'next/navigation';
import { markdown } from '@decbr/fgmd';
export function Markdown({ source }) {
const router = useRouter();
const html = useMemo(() => markdown(source), [source]);
function onClick(event) {
const a = event.target.closest('a');
if (!a || a.target || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey) return;
const href = a.getAttribute('href');
if (!href?.startsWith('/') || href.startsWith('//')) return;
event.preventDefault();
router.push(href);
}
return <div onClick={onClick} dangerouslySetInnerHTML={{ __html: html }} />;
}If you need real React elements throughout, parse() gives a plain mdast-style tree you can map to elements yourself. See The tree. You'd then be responsible for rendering every node type, including footnotes and plugin nodes.