Fumadocs Editor

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

feature.tsx
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,
};
FieldDoes
attributeRegionsmakes the title attribute an inline editable region
childrenRegionmakes the children a block region
propsother attributes, in the attributes panel
insertwhat the slash menu inserts
regionsa class name per region

Draw the renderer

feature.tsx
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

editor.tsx
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 propHoldsExample
propsstring attributestype="info" → props.type === "info"
literalsstatic expression valuessoon={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

On this page