EditorRoot
The root editor component that wraps Lexical's LexicalComposer.
EditorRoot is the foundational component of every Blokhaus editor instance. It wraps Lexical's LexicalComposer with all registered custom nodes, sets up the rich text plugin, history tracking, and auto-focus. All other Blokhaus components and plugins must be rendered as children of EditorRoot.
Import
import { EditorRoot } from "@blokhaus/core";Props
| Prop | Type | Default | Description |
|---|---|---|---|
namespace | string | required | Unique identifier for multi-editor isolation. Each editor on the page must have a distinct namespace. |
initialState | string | null | undefined | Serialized Lexical JSON state. Pass null or omit for a blank editor. |
children | React.ReactNode | undefined | Plugin and UI components (FloatingToolbar, SlashMenu, etc.) |
className | string | undefined | CSS class applied to the editor container <div>. |
dir | "ltr" | "rtl" | "auto" | undefined | Document-level text direction. When set to "ltr" or "rtl", the root node direction is set on mount. Omit for per-paragraph auto-detection. |
placeholder | string | "Start writing..." | Placeholder text shown when the editor is empty. |
editable | boolean | true | Whether the editor content is editable. Set to false for read-only mode. |
autoFocus | boolean | true | Whether the editor auto-focuses on mount. |
onError | (error: Error) => void | console.error + throw | Custom error handler for Lexical errors. The default handler logs to the console and re-throws. |
Basic usage
"use client";
import {
EditorRoot,
FloatingToolbar,
SlashMenu,
InputRulePlugin,
} from "@blokhaus/core";
export default function EditorPage() {
return (
<EditorRoot
namespace="my-editor"
className="relative min-h-[400px] p-4 border rounded-lg"
placeholder="Start writing something amazing..."
>
<FloatingToolbar />
<SlashMenu />
<InputRulePlugin />
</EditorRoot>
);
}With plugins as children
Every feature in Blokhaus is a composable plugin passed as a React child. There is no prop explosion -- you only include what you need:
"use client";
import {
EditorRoot,
FloatingToolbar,
SlashMenu,
OverlayPortal,
MobileToolbar,
InputRulePlugin,
ImagePlugin,
PastePlugin,
LinkPlugin,
ListPlugin,
ColorPlugin,
BlockSelectionPlugin,
useEditorState,
} from "@blokhaus/core";
function StateTracker() {
const { serializedState } = useEditorState({
debounceMs: 300,
onChange: (json) => {
// Persist to your backend
fetch("/api/save", {
method: "POST",
body: json,
});
},
});
return null;
}
const uploadHandler = async (file: File): Promise<string> => {
const formData = new FormData();
formData.append("file", file);
const res = await fetch("/api/upload", { method: "POST", body: formData });
const { url } = await res.json();
return url;
};
export default function EditorPage() {
return (
<EditorRoot
namespace="my-editor"
className="relative min-h-[500px] p-4 border rounded-lg"
>
<StateTracker />
{/* UI Components */}
<FloatingToolbar />
<SlashMenu />
<OverlayPortal namespace="my-editor" />
<MobileToolbar />
{/* Plugins */}
<InputRulePlugin />
<ImagePlugin uploadHandler={uploadHandler} />
<PastePlugin />
<LinkPlugin />
<ListPlugin />
<ColorPlugin />
<BlockSelectionPlugin />
</EditorRoot>
);
}Multi-editor example
Multiple EditorRoot instances can coexist on the same page. Each must have a unique namespace:
"use client";
import { EditorRoot, FloatingToolbar, InputRulePlugin } from "@blokhaus/core";
export default function DocumentPage() {
return (
<div className="space-y-8">
{/* Title editor */}
<EditorRoot
namespace="title-editor"
className="text-3xl font-bold p-2 border-b"
placeholder="Untitled"
autoFocus={true}
>
<InputRulePlugin />
</EditorRoot>
{/* Body editor */}
<EditorRoot
namespace="body-editor"
className="relative min-h-[400px] p-4"
placeholder="Start writing..."
autoFocus={false}
>
<FloatingToolbar />
<InputRulePlugin />
</EditorRoot>
</div>
);
}Each editor maintains fully isolated state. Typing in one editor does not affect the other. Keyboard shortcuts (e.g., Cmd+B) are automatically scoped to the editor that has focus via Lexical's COMMAND_PRIORITY_EDITOR.
Read-only mode
Set editable={false} to render the editor in read-only mode. This is useful for displaying saved content without allowing modifications:
<EditorRoot
namespace="viewer"
initialState={savedJsonState}
editable={false}
autoFocus={false}
className="prose"
/>Restoring saved state
Pass a serialized Lexical JSON string to initialState to restore previously saved content:
const savedState = localStorage.getItem("editor-state");
<EditorRoot namespace="my-editor" initialState={savedState}>
{/* plugins */}
</EditorRoot>;RTL support
For right-to-left languages, set the dir prop:
<EditorRoot namespace="rtl-editor" dir="rtl" placeholder="...ابدأ الكتابة">
<FloatingToolbar />
<InputRulePlugin />
</EditorRoot>When dir is set to "ltr" or "rtl", the root node's direction is set once on mount. Per-paragraph direction can still be changed via the floating toolbar's direction toggle.
Notes
EditorRootis a client component ('use client'). It cannot be used as a Server Component.- It wraps
LexicalComposerwith the fullALL_NODESregistry, which includes all custom Blokhaus nodes (ImageNode, CodeBlockNode, MentionNode, etc.). - The Lexical theme (
blokhausTheme) is applied automatically for consistent CSS class names on editor elements. - The
namespaceprop is passed through toLexicalComposerand is also rendered as adata-namespaceattribute on the wrapper<div>for DOM identification. - The editor container includes
data-blokhaus-rootfor use byOverlayPortaland other components that need to find the editor boundary. - Custom error handling can be configured via
onError. In production, you should replace the default handler with your error tracking service.
Related
- useEditorState -- Hook for reading serialized editor state
- OverlayPortal -- Drag-and-drop block handles (requires matching
namespace) - Multi-Editor guide -- Detailed guide on multi-editor setups
- Quick Start -- Full setup walkthrough