Skip to main content

Overview

WebEditor comes with a rich set of pre-built components that can be inserted using the command menu (/). Each component is implemented as a ProseMirror NodeView with React, providing an interactive editing experience.

Component architecture

All components follow a consistent pattern:
  1. Node specification - Defines the component’s schema, attributes, and DOM serialization
  2. React NodeView - Renders the component with editable attributes
  3. Input rule - Allows typing markdown-like syntax (e.g., <card title="..." />)
  4. Insert function - Programmatically inserts the component via the command menu

Block components

Block components contain other block content and occupy their own line.

Card

Display content in a bordered card with an optional icon:
// Node spec attributes
attrs: {
  title: { default: "Card Title" },
  icon: { default: null },
  showIcon: { default: true },
  horizontal: { default: false },
  href: { default: null }
}
Example usage:
<card title="Getting Started" icon="FileText" horizontal="true" />
Features:
  • Click the title to edit it
  • Click the icon to change it (searches Lucide icons)
  • Toggle between horizontal and vertical layouts
  • Supports nested content
Cards with an href attribute become clickable links with hover effects.

Callout

Highlight important information with styled callouts:
// Node spec attributes
attrs: {
  type: { default: "info" } // info | warning | caution | important | tip
}
Example usage:
<callout type="warning">
This is important information!
</callout>
Available types:
  • info - Blue, for general notes
  • warning - Yellow, for warnings
  • caution - Red, for critical information
  • important - Purple, for important notes
  • tip - Green, for helpful tips

Accordion

Create collapsible sections:
// Node spec attributes
attrs: {
  title: { default: "Accordion Title" }
}
Example usage:
<accordion title="Click to expand">
Hidden content goes here.
</accordion>

Frame

Add a bordered frame with optional caption:
// Node spec attributes
attrs: {
  caption: { default: null }
}
Example usage:
<frame caption="Figure 1: System Architecture">
Frame content here.
</frame>

Field

Document parameters or API fields:
// Node spec attributes
attrs: {
  name: { default: "fieldName" },
  type: { default: "string" },
  required: { default: false }
}
Example usage:
<field name="userId" type="string" required="true">
The unique identifier for the user.
</field>
The Field component is designed for API documentation and parameter descriptions.

Columns

Create responsive grid layouts:
// Node spec attributes
attrs: {
  cols: { default: 2 } // Number of columns
}
Example usage:
<columns cols="3">
  <column>
    First column
  </column>
  <column>
    Second column
  </column>
  <column>
    Third column
  </column>
</columns>

Step

Create numbered step-by-step instructions:
// Node spec attributes
attrs: {
  title: { default: "Step Title" }
}
Example usage:
<step title="Install dependencies">
Run `npm install` to install all required packages.
</step>

Tabs

Organize content in tabbed interfaces:
// Node spec attributes
attrs: {
  tabs: { default: [] } // Array of tab labels
}
Example usage:
<tabs tabs='["JavaScript", "TypeScript", "Python"]'>
Tab content here.
</tabs>

Code Snippet

Embed syntax-highlighted code with CodeMirror:
// Node spec attributes
attrs: {
  language: { default: "javascript" },
  code: { default: "" }
}
Example usage:
<code_snippet language="typescript">
const hello = "world";
</code_snippet>
Code snippets use CodeMirror for syntax highlighting and support many languages.

Mermaid

Create diagrams using Mermaid syntax:
// Node spec attributes
attrs: {
  code: { default: "graph TD\n  A-->B" }
}
Example usage:
<mermaid>
graph LR
  A[Start] --> B[Process]
  B --> C[End]
</mermaid>

Inline components

Inline components are rendered within text content.

Badge

Add inline badges with different variants:
// Node spec attributes
attrs: {
  variant: { default: "default" }, // default | secondary | destructive | outline
  label: { default: "" }
}
Example usage:
This is <badge variant="destructive" label="deprecated" /> in v2.0.
Available variants:
  • default - Primary color
  • secondary - Secondary color
  • destructive - Red/destructive color
  • outline - Bordered outline style

Icon

Insert Lucide icons inline:
// Node spec attributes
attrs: {
  icon: { default: "FileText" },
  size: { default: 20 }
}
Example usage:
Click the <icon icon="Settings" size="16" /> icon to configure.
All icon names reference the Lucide icon library. Icons are dynamically loaded and support PascalCase, camelCase, kebab-case, and snake_case naming.

Break

Add vertical spacing between content:
// Node spec attributes
attrs: {
  size: { default: "medium" } // small | medium | large
}
Example usage:
Section 1

<break size="large" />

Section 2

Editing components

When editable={true} (default), components provide interactive editing:
1

Click to edit

Click on component titles, icons, or content to open edit modals
2

Hover controls

Hover over components to reveal additional controls (e.g., layout toggle on Card)
3

Nested content

Type inside block components to add nested content
4

Delete components

Select a component and press Delete/Backspace to remove it

Component implementation pattern

Here’s how a typical component is structured (using Card as an example):
// 1. Node specification
export const cardNodeSpec: NodeSpec = {
  group: "block",
  content: "block*",
  attrs: {
    title: { default: "Card Title" },
    icon: { default: null },
    showIcon: { default: true },
    horizontal: { default: false },
    href: { default: null },
  },
  selectable: true,
  parseDOM: [{
    tag: "card",
    getAttrs: (dom) => ({
      title: dom.getAttribute("title") || "Card Title",
      icon: dom.getAttribute("icon") || null,
      // ... more attributes
    }),
  }],
  toDOM: (node) => [
    "card",
    {
      title: node.attrs.title,
      icon: node.attrs.icon,
      // ... more attributes
    },
    0,
  ],
};

// 2. React NodeView
export const CardNodeView = React.forwardRef<HTMLDivElement, NodeViewComponentProps>(
  function Card({ nodeProps, ...props }, ref) {
    const title = nodeProps.node.attrs.title;
    const editable = useEditorEditable();

    const updateTitle = useEditorEventCallback((view, newTitle: string) => {
      const pos = nodeProps.getPos();
      const tr = view.state.tr.setNodeMarkup(pos, undefined, {
        ...nodeProps.node.attrs,
        title: newTitle,
      });
      view.dispatch(tr);
    });

    return (
      <div className="card">
        {editable ? (
          <span onClick={() => setModalOpen(true)}>{title}</span>
        ) : (
          <span>{title}</span>
        )}
        <div {...props} ref={ref} />
      </div>
    );
  }
);

// 3. Insert function
export function insertCard(state: EditorState): Transaction {
  const card = state.schema.nodes.card.create(
    { title: "Card Title" },
    state.schema.nodes.paragraph.create()
  );
  return state.tr.replaceSelectionWith(card);
}

Adding components via command menu

See the Command Menu guide for details on inserting components using the / command system.

Read-only mode

When editable={false}, all component edit controls are disabled:
<WebEditor value={content} editable={false} />
In read-only mode:
  • Edit buttons and modals are hidden
  • Click handlers are disabled
  • Components display as static content
  • Interactive elements like links still work
Components are not extensible in the current version. You cannot add custom components without modifying the source code. The available components are defined in command-system.tsx:60-161.

Build docs developers (and LLMs) love