Component Authoring
Make your own MDX components editable in place
Without a spec, a component's children stay editable and its tags are edited as text. A spec makes it a real block, with editable regions, a slash-menu entry and an attributes panel.
The editor never renders your real components. Each one gets:
- a spec describing which attributes and children are editable
- a renderer, a lightweight look-alike drawn around them
Example
Take a <Feature title="…" soon={true}> card.
Declare the spec
export const featureSpec: UiComponentSpec = {
name: "Feature",
label: "Feature",
attributeRegions: [{ attribute: "title", region: "title", placeholder: "Feature name…" }],
childrenRegion: { region: "body", placeholder: "Describe the feature…" },
props: [{ name: "soon", label: "Coming soon", type: "boolean", default: false }],
// regions render outside the renderer's tree; style them by name
regions: { title: "font-medium" },
insert: (specs) => emptyComponent(featureSpec, specs),
render: Feature,
};| Field | Does |
|---|---|
attributeRegions | makes the title attribute an inline editable region |
childrenRegion | makes the children a block region |
props | other attributes, in the attributes panel |
insert | what the slash menu inserts |
regions | a class name per region |
Draw the renderer
function Feature({ literals, children }: ComponentRenderProps) {
const soon = literals.soon === true;
return (
<div className="rounded-xl border border-fd-border bg-fd-card p-4">
{soon && (
<span
contentEditable={false}
className="float-right rounded-full bg-fd-primary/10 px-2 py-0.5 text-xs text-fd-primary"
>
Soon
</span>
)}
{children}
</div>
);
}children holds the editable regions. Style the rest however your app
styles things.
Register it
const components = [...fumadocsUiComponents, featureSpec];
export function Editor({ text }: { text: string }) {
return <MdxEditor defaultValue={text} components={components} />;
}Regions
children contains every region in document order: here title, then
body.
- Style a region with
regions, or with[data-region="<name>"]. - Mark decorations
contentEditable={false}so the caret cannot enter them. - Leave some chrome around the regions (an icon, a border, padding): double-clicking it selects the component.
Nested components
A component that sits inside another one needs a spot for the touch drag handle: an empty element
with data-fde-controls in its chrome.
Props and literals
Attributes reach the renderer in two forms:
| Render prop | Holds | Example |
|---|---|---|
props | string attributes | type="info" → props.type === "info" |
literals | static expression values | soon={true} → literals.soon === true |
Change them from the renderer with setProp(name, value) and
setLiteral(name, value). Expressions that are not literals, like a
variable, are edited as source in the attributes panel.
Reference
ComponentSpec from @fumadocs-editor/core:
Prop
Type
UiComponentSpec from @fumadocs-editor/ui adds:
Prop
Type
For containers like Cards, Files and Tabs, the built-in specs are the best reference.
Last updated on