
Syncfusion React Blockeditor
- 440 installs
- 3 repo stars
- Updated July 28, 2026
- syncfusion/react-ui-components-skills
syncfusion-react-blockeditor is an agent skill that implements Syncfusion React BlockEditorComponent with block types, slash menus, drag-and-drop reordering, and JSON or HTML export for developers building CMS-style rich
About
syncfusion-react-blockeditor is a Syncfusion agent skill (metadata version 33.1.44) for implementing BlockEditorComponent from @syncfusion/ej2-react-blockeditor in React apps. It documents block-based architecture with paragraphs, headings, lists, tables, code, callouts, and collapsible sections, plus slash commands, context menus, inline toolbars, @mentions, labels, and enableDragAndDrop reordering. Nine reference guides cover getting started, built-in blocks, menus, drag-drop, mentions, API methods, styling, advanced sanitization, and WCAG 2.1 accessibility. Reach for this skill when replacing markdown editors, building Notion-style CMS UIs, or wiring export to JSON, HTML, or plain text with XSS-safe paste cleanup.
- syncfusion-react-blockeditor
Syncfusion React Blockeditor by the numbers
- 440 all-time installs (skills.sh)
- +52 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #994 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/syncfusion/react-ui-components-skills --skill syncfusion-react-blockeditorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 440 |
|---|---|
| repo stars | ★ 3 |
| Last updated | July 28, 2026 |
| Repository | syncfusion/react-ui-components-skills ↗ |
How do you add a block editor in React?
Use syncfusion-react-blockeditor for development tasks
Who is it for?
React developers using Syncfusion who need a block-based rich editor with CMS workflows, mentions, and accessible keyboard navigation.
Skip if: Teams using TipTap, Slate, or Lexical who are not on Syncfusion's paid component stack.
When should I use this skill?
User asks to integrate Syncfusion BlockEditor, block-based CMS UI, slash commands, or drag-and-drop content blocks in React.
What you get
React BlockEditorComponent with BlockModel arrays, customized menus, drag-and-drop blocks, and exported JSON or HTML content.
- BlockEditorComponent integration
- BlockModel initial content
- customized editor menus
By the numbers
- Syncfusion skill metadata version 33.1.44
- Includes 9 reference guides from getting-started through accessibility
- Supports JSON, HTML, and plain-text content export
Files
Syncfusion React Block Editor in React
Component Overview
The Syncfusion React BlockEditorComponent is a powerful block-based rich text editor that allows users to create, edit, and format content using a modern block architecture. Each piece of content (paragraphs, headings, lists, tables, code snippets) is a discrete, manageable block.
Key Capabilities
The BlockEditorComponent provides:
- Block-based architecture - Content structured as discrete, reorderable blocks
- Built-in block types - Paragraphs, headings, lists, tables, code, callouts, quotes, collapsible sections
- Intuitive menus - Slash commands, context menus, inline toolbars
- Drag-and-drop - Reorder blocks easily with visual handles
- Content export - Export as JSON, HTML, or plain text
- Accessibility - WCAG 2.1 compliant with keyboard navigation and screen reader support
- Customization - Custom styling, themes, RTL support, globalization
Documentation Navigation Guide
Getting Started
📄 Read: references/getting-started.md
- Installation and npm package setup
- Basic component implementation
- CSS imports and theme configuration
- Creating your first block editor
- Setting initial content with blocks
Built-in Block Types
📄 Read: references/built-in-blocks.md
- Block type reference (Paragraph, Heading, List, Table, Code, Quote, Callout)
- Nested block types (CollapsibleHeading, CollapsibleParagraph)
- Block indentation and CSS class styling
- Content configuration and properties
Menus and Commands
📄 Read: references/block-editor-menus.md
- Slash command menu customization
- Context menu configuration
- Inline toolbar setup
- Menu events and filtering
- Block actions and shortcuts
Drag-Drop and Content Management
📄 Read: references/drag-drop-and-content.md
- Drag-and-drop block reordering (
enableDragAndDrop) - Content insertion and nesting
- Drag events (
blockDragStart,blockDragging,blockDropped) - Programmatic block movement
Mentions and Labels
📄 Read: references/mentions-and-labels.md
usersprop andUserModelinterface for@mentionfeaturelabelSettingsprop andLabelItemModelinterface for label featureContentType.Mention/ContentType.Labelinline contentIMentionContentSettings/ILabelContentSettings
Methods and API
📄 Read: references/methods-and-api.md
- Block management methods (add, remove, update, move)
- Selection and cursor control
- Data export/import (JSON, HTML)
- Content formatting and rendering
Styling and Appearance
📄 Read: references/styling-and-appearance.md
- CSS theming and imports
- Block styling with custom CSS classes
- Typography and formatting options
- Dark mode and responsive design
Advanced Features
📄 Read: references/advanced-features.md
- Paste cleanup and content sanitization
- Undo/redo functionality
- Keyboard shortcut customization
- Read-only mode configuration
- XSS protection and HTML sanitization
- RTL support and internationalization
Accessibility
📄 Read: references/accessibility.md
- WCAG 2.1 compliance
- Keyboard navigation patterns
- Screen reader support and ARIA attributes
- Focus management
- Color contrast and visual indicators
Quick Start Example
import { BlockEditorComponent } from '@syncfusion/ej2-react-blockeditor';
import { BlockModel, ContentType } from '@syncfusion/ej2-react-blockeditor';
import '@syncfusion/ej2-react-blockeditor/styles/material.css';
function App() {
// Define initial blocks
const blocksData: BlockModel[] = [
{
id: 'block-1',
blockType: 'Heading',
properties: { level: 1 },
content: [
{
contentType: ContentType.Text,
content: 'Welcome to Block Editor'
}
]
},
{
id: 'block-2',
blockType: 'Paragraph',
content: [
{
contentType: ContentType.Text,
content: 'This is your first paragraph. Click the "+" button to add more blocks.'
}
]
}
];
return (
<BlockEditorComponent
id="block-editor"
blocks={blocksData}
/>
);
}
export default App;Common Patterns
1. Add a Block Programmatically
const editorRef = React.useRef<BlockEditorComponent>(null);
const addNewBlock = () => {
const newBlock: BlockModel = {
blockType: 'Paragraph',
content: [
{
contentType: ContentType.Text,
content: 'New paragraph block'
}
]
};
editorRef.current?.addBlock(newBlock);
};2. Handle Menu Item Selection
const commandMenuSettings = {
itemSelect: (args) => {
console.log('Selected command:', args.command.label, args.command.id);
// Handle custom actions based on selected command
}
};3. Export Content as JSON
const exportContent = () => {
const jsonContent = editorRef.current?.getDataAsJson();
console.log('Exported content:', jsonContent);
};4. Enable Read-Only Mode
<BlockEditorComponent
id="block-editor"
blocks={blocksData}
readOnly={true}
/>Key Props
⚠️ PropstoolbarSettings,containerCssClass, andshowBlockHandledo not exist in the BlockEditor API and must not be used.
| Prop | Type | Description |
|---|---|---|
id | string | Unique identifier for the component |
blocks | BlockModel[] | Array of block objects defining content |
readOnly | boolean | Enable read-only mode (default: false) |
width | `string \ | number` |
height | `string \ | number` |
commandMenuSettings | CommandMenuSettingsModel | Customize slash command (/) menu |
contextMenuSettings | ContextMenuSettingsModel | Configure right-click context menu |
inlineToolbarSettings | InlineToolbarSettingsModel | Configure inline text selection toolbar |
blockActionMenuSettings | BlockActionMenuSettingsModel | Configure block action (⋮) menu |
transformSettings | TransformSettingsModel | Configure block type transform menu |
imageBlockSettings | ImageBlockSettingsModel | Configure image upload and rendering |
codeBlockSettings | CodeBlockSettingsModel | Configure code block languages |
pasteCleanupSettings | PasteCleanupSettingsModel | Control paste sanitization behavior |
users | UserModel[] | User list for @mention feature |
labelSettings | LabelSettingsModel | Label items and trigger char for label feature |
enableDragAndDrop | boolean | Enable/disable drag-and-drop reordering (default: true) |
undoRedoStack | number | Max number of undo/redo history steps |
keyConfig | { [key: string]: string } | Custom keyboard shortcut mappings |
locale | string | Localization language code (default: 'en-US') |
blockChanged | EmitType<BlockChangedEventArgs> | Fires when block content changes |
Common Use Cases
Content Management System - Build a CMS with block-based editing, custom block types, and content export
Document Editor - Create a collaborative document editor with formatting, templates, and version control
Note-Taking App - Implement a personal notes app with nesting, tagging, and search capabilities
Blog Editor - Enable blog authors to write with rich formatting, media embeds, and preview
Knowledge Base - Build internal documentation with organized blocks, search, and linked references
Survey/Form Builder - Create dynamic surveys with conditional blocks and response capture
---
Accessibility Features
Table of Contents
- WCAG 2.1 Compliance
- Keyboard Navigation
- Screen Reader Support
- ARIA Attributes
- Focus Management
- Color Contrast and Visual Indicators
- Testing for Accessibility
WCAG 2.1 Compliance
The BlockEditor is designed to meet WCAG 2.1 accessibility standards at the AA level for most features and AAA level for critical interactions.
WCAG 2.1 Principles
1. Perceivable - Content must be perceivable to all users
- ✓ Text alternatives for images
- ✓ Color not sole means of conveying information
- ✓ Sufficient color contrast ratios
- ✓ Readable font sizes and line spacing
2. Operable - Users must be able to navigate and control
- ✓ Full keyboard accessibility
- ✓ No keyboard traps
- ✓ Focus indicators always visible
- ✓ No content with seizure triggers
3. Understandable - Information must be clear
- ✓ Language marked in code
- ✓ Consistent navigation
- ✓ Clear labels and instructions
- ✓ Predictable interactions
4. Robust - Content must work with assistive technologies
- ✓ Valid HTML/ARIA
- ✓ Semantic markup
- ✓ Proper roles and attributes
- ✓ Screen reader compatible
Compliance Checklist
// BlockEditor with accessibility best practices
import { BlockEditorComponent, BlockModel, ContentType } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function AccessibleBlockEditor() {
return (
<div>
{/* Descriptive heading */}
<h1>Document Editor</h1>
{/* Usage instructions */}
<p id="editor-instructions">
Use "/" for commands, Tab to navigate, Enter to edit blocks,
and arrow keys to move between blocks.
</p>
{/* Accessible editor */}
<BlockEditorComponent
id="editor"
aria-label="Block-based document editor"
aria-describedby="editor-instructions"
blocks={[]}
/>
</div>
);
}
export default AccessibleBlockEditor;Keyboard Navigation
Essential Keyboard Shortcuts
| Key | Action | Purpose |
|---|---|---|
| Tab | Focus next element | Navigate forward |
| Shift+Tab | Focus previous element | Navigate backward |
| Enter | Edit/Select block | Activate focused item |
| / | Open command menu | Quick access to commands |
| Escape | Close menu/Cancel | Exit current context |
| Arrow Up | Previous block | Navigate between blocks |
| Arrow Down | Next block | Navigate between blocks |
| Arrow Left | Start of line/Previous char | Text cursor control |
| Arrow Right | End of line/Next char | Text cursor control |
| Ctrl+A | Select all | Select all blocks |
| Ctrl+Z | Undo | Reverse last action |
| Ctrl+Y | Redo | Redo last undone action |
| Ctrl+B | Bold | Toggle bold formatting |
| Ctrl+I | Italic | Toggle italic formatting |
| Ctrl+U | Underline | Toggle underline |
| Ctrl+K | Insert link | Link insertion |
| Backspace | Delete/Outdent | Delete or decrease indent |
| Delete | Delete content | Remove block content |
Keyboard Navigation Example
function KeyboardNavigationDemo() {
const editorRef = React.useRef<BlockEditorComponent>(null);
// Custom keyboard handler
const handleKeyDown = (event: KeyboardEvent) => {
// Cmd/Ctrl + S: Save
if ((event.metaKey || event.ctrlKey) && event.key === 's') {
event.preventDefault();
console.log('Save with keyboard shortcut');
}
// Alt + H: Focus heading
if (event.altKey && event.key === 'h') {
event.preventDefault();
editorRef.current?.focusIn();
}
// Alt + L: Focus list
if (event.altKey && event.key === 'l') {
event.preventDefault();
console.log('Navigate to list block');
}
};
React.useEffect(() => {
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, []);
return <BlockEditorComponent id="editor" ref={editorRef} />;
}No Keyboard Traps
The BlockEditor ensures no keyboard traps where users get stuck unable to navigate away:
// Proper focus management prevents traps
<BlockEditorComponent
id="editor"
showBlockHandle={true} // Draggable but not a trap
enableKeyboardInteraction={true} // Full keyboard support
/>Screen Reader Support
Semantic HTML
The BlockEditor generates semantic HTML that screen readers can interpret:
import { BlockEditorComponent } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function ScreenReaderAccessible() {
const blocks = [
{
id: 'block-1',
blockType: 'Heading',
properties: { level: 1 },
content: [{ contentType: 'Text', content: 'Main Title' }]
},
{
id: 'block-2',
blockType: 'Paragraph',
content: [{ contentType: 'Text', content: 'Introductory paragraph' }]
},
{
id: 'block-3',
blockType: 'BulletList',
content: [{ contentType: 'Text', content: 'List item' }]
}
];
return (
<>
<h1>Document with Screen Reader Support</h1>
<BlockEditorComponent
id="editor"
blocks={blocks}
aria-label="Accessible block editor"
role="main"
/>
</>
);
}Testing with Screen Readers
Supported screen readers:
- NVDA (Windows) - Free, open-source
- JAWS (Windows) - Commercial
- VoiceOver (macOS/iOS) - Built-in
- Narrator (Windows 10+) - Built-in
- TalkBack (Android) - Built-in
ARIA Attributes
Essential ARIA Attributes
import { BlockEditorComponent } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function AccessibleEditor() {
return (
<div>
{/* Main editor container */}
<BlockEditorComponent
id="editor"
role="main"
aria-label="Document editor"
aria-describedby="help-text"
aria-live="polite" // Announce changes
/>
{/* Help text for screen readers */}
<div id="help-text" className="sr-only">
This is a block-based document editor. Press "/" to see available commands.
Use Tab to navigate between blocks.
</div>
{/* Status region for dynamic updates */}
<div
aria-live="assertive"
aria-atomic="true"
className="sr-only"
id="status-region"
>
{/* Editor status updates announced here */}
</div>
</div>
);
}
export default AccessibleEditor;Block-Level ARIA
import { BlockModel } from '@syncfusion/ej2-react-blockeditor';
const accessibleBlocks: BlockModel[] = [
{
id: 'heading-1',
blockType: 'Heading',
properties: {
level: 1,
'aria-level': '1'
},
content: [{ contentType: 'Text', content: 'Section Title' }]
},
{
id: 'list-1',
blockType: 'BulletList',
content: [{ contentType: 'Text', content: 'Item 1' }],
properties: {
role: 'list',
'aria-label': 'Key points'
}
}
];ARIA Live Regions
// Announce changes to screen reader users
<div aria-live="polite" aria-atomic="true">
Block added successfully
</div>
<div aria-live="assertive" aria-atomic="true">
Warning: Unsaved changes!
</div>Focus Management
Visible Focus Indicators
/* Ensure focus is always visible */
.e-block-editor:focus,
.e-block-editor *:focus {
outline: 2px solid #4A90E2;
outline-offset: 2px;
}
/* Keyboard focus vs mouse click */
.e-block-editor:focus-visible {
outline: 3px solid #4A90E2;
}
/* High contrast mode support */
@media (prefers-contrast: more) {
.e-block-editor:focus {
outline: 3px solid;
outline-offset: 3px;
}
}Programmatic Focus Control
function FocusManagement() {
const editorRef = React.useRef<BlockEditorComponent>(null);
const setFocusToEditor = () => {
editorRef.current?.focusIn();
};
const setFocusToBlock = (blockId: string) => {
editorRef.current?.setCursorPosition(blockId, 0);
editorRef.current?.focusIn();
};
const trapFocusInEditor = () => {
// Prevent focus from leaving editor
editorRef.current?.focusIn();
};
return (
<div>
<button onClick={setFocusToEditor}>Focus Editor</button>
<button onClick={() => setFocusToBlock('block-1')}>Focus First Block</button>
<BlockEditorComponent id="editor" ref={editorRef} />
</div>
);
}Focus Order
// Maintain logical focus order
<div>
<button tabIndex={0}>Save</button> {/* First */}
<button tabIndex={0}>Undo</button> {/* Second */}
<BlockEditorComponent
id="editor"
tabIndex={0} {/* Third - editor content */}
/>
<button tabIndex={0}>Export</button> {/* Fourth */}
</div>Color Contrast and Visual Indicators
WCAG Contrast Requirements
Text contrast:
- Normal text: 4.5:1 (AA) / 7:1 (AAA)
- Large text (18pt+): 3:1 (AA) / 4.5:1 (AAA)
High Contrast Styles
/* Default high contrast support */
.e-block-editor {
color: #000000;
background-color: #ffffff;
}
/* Ensure sufficient contrast */
.e-toolbar button {
color: #000000;
background-color: #f0f0f0;
border: 1px solid #333333;
min-contrast-ratio: 4.5;
}
/* Focus indicators with strong contrast */
.e-block-editor:focus {
outline: 3px solid #000000;
}
/* Support forced colors mode */
@media (forced-colors: active) {
.e-block-editor {
border: 1px solid CanvasText;
color: CanvasText;
background-color: Canvas;
}
.e-block-editor:focus {
outline: 3px solid Highlight;
}
}Visual Indicators Beyond Color
/* Don't rely on color alone */
.block-success {
/* Use both color AND icon/symbol */
background: linear-gradient(
90deg,
#4caf50 0%,
#4caf50 4px,
#e8f5e9 4px
);
padding-left: 12px;
}
.block-success::before {
content: '✓'; /* Symbol in addition to color */
margin-right: 5px;
font-weight: bold;
}
.block-error {
background: linear-gradient(
90deg,
#f44336 0%,
#f44336 4px,
#ffebee 4px
);
padding-left: 12px;
}
.block-error::before {
content: '⚠'; /* Symbol */
margin-right: 5px;
}Testing for Accessibility
Automated Testing
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
test('BlockEditor should not have accessibility violations', async () => {
const { container } = render(<BlockEditorComponent id="editor" />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});Manual Testing Checklist
□ Keyboard navigation works for all features
□ Tab order is logical
□ Focus indicators are visible
□ Screen reader announces all content
□ Color contrast meets WCAG AA (4.5:1 for normal text)
□ No keyboard traps
□ All interactive elements are labeled
□ Error messages are announced
□ Form inputs have associated labels
□ Images have alt text
□ Focus management is appropriate
□ No auto-playing content
□ Motion/animation can be disabledScreen Reader Testing
// Test with screen reader
function ScreenReaderTest() {
const blocks = [
{
id: 'test-1',
blockType: 'Heading',
properties: { level: 1 },
content: [{ contentType: 'Text', content: 'Test Heading' }]
},
{
id: 'test-2',
blockType: 'Paragraph',
content: [{ contentType: 'Text', content: 'Test paragraph content' }]
}
];
return (
<BlockEditorComponent
id="editor"
blocks={blocks}
// Enable screen reader features
aria-label="Screen reader test editor"
aria-live="polite"
/>
);
}Complete Accessible Example
import { BlockEditorComponent, BlockModel, ContentType } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function FullyAccessibleEditor() {
const editorRef = React.useRef<BlockEditorComponent>(null);
const [status, setStatus] = React.useState('');
const blocks: BlockModel[] = [
{
id: 'title',
blockType: 'Heading',
properties: { level: 1 },
content: [{
contentType: ContentType.Text,
content: 'Accessible Document Editor'
}]
},
{
id: 'intro',
blockType: 'Paragraph',
content: [{
contentType: ContentType.Text,
content: 'This editor is fully accessible using keyboard and screen readers.'
}]
}
];
const save = () => {
setStatus('Document saved');
setTimeout(() => setStatus(''), 3000);
};
return (
<div>
{/* Main content */}
<header>
<h1>Document Editor</h1>
<p id="instructions">
Press "/" to insert blocks, Tab to navigate, Ctrl+S to save.
</p>
</header>
{/* Toolbar with accessible buttons */}
<nav aria-label="Editor controls">
<button onClick={save} aria-label="Save document">
Save
</button>
<button
onClick={() => editorRef.current?.focusIn()}
aria-label="Focus on editor"
>
Edit
</button>
</nav>
{/* Main editor */}
<BlockEditorComponent
id="editor"
ref={editorRef}
blocks={blocks}
role="main"
aria-label="Block-based document editor"
aria-describedby="instructions"
aria-live="polite"
/>
{/* Status announcements */}
<div
aria-live="assertive"
aria-atomic="true"
role="status"
className="sr-only"
>
{status}
</div>
</div>
);
}
export default FullyAccessibleEditor;Accessibility Resources
Advanced Features
Table of Contents
- Paste Cleanup and Sanitization
- Undo and Redo Functionality
- Keyboard Shortcuts
- Read-Only Mode
- XSS Protection and HTML Sanitization
- RTL (Right-to-Left) Support
- Internationalization and Localization
Paste Cleanup and Sanitization
Paste Events
The BlockEditor provides paste event handling to clean and sanitize pasted content:
import { BlockEditorComponent } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const editorRef = React.useRef<BlockEditorComponent>(null);
// Listen for paste events (conceptual - actual implementation depends on version)
const handlePaste = (event: ClipboardEvent) => {
event.preventDefault();
const pastedData = event.clipboardData?.getData('text/plain');
if (pastedData) {
console.log('Pasted content:', pastedData);
// Can process and clean before inserting
const cleanedData = sanitizeContent(pastedData);
console.log('Cleaned content:', cleanedData);
}
};
const sanitizeContent = (content: string): string => {
// Remove script tags and dangerous attributes
const temp = document.createElement('div');
temp.textContent = content; // textContent prevents HTML parsing
return temp.innerHTML;
};
React.useEffect(() => {
const editor = editorRef.current?.element;
editor?.addEventListener('paste', handlePaste);
return () => {
editor?.removeEventListener('paste', handlePaste);
};
}, []);
return <BlockEditorComponent id="editor" ref={editorRef} />;
}
export default App;Configure Paste Settings
The pasteCleanupSettings prop accepts a PasteCleanupSettingsModel with four specific properties:
import { BlockEditorComponent, PasteCleanupSettingsModel } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const pasteCleanupSettings: PasteCleanupSettingsModel = {
// Preserve formatting when pasting
keepFormat: false,
// Paste as plain text, stripping all formatting
plainText: false,
// HTML tags to strip from pasted content
deniedTags: ['script', 'style', 'iframe', 'object', 'embed'],
// CSS style properties to preserve during paste
allowedStyles: ['font-size', 'color', 'font-weight', 'font-style', 'text-decoration']
};
return (
<BlockEditorComponent
id="editor"
pasteCleanupSettings={pasteCleanupSettings}
/>
);
}
export default App;PasteCleanupSettingsModel Properties
| Property | Type | Description |
|---|---|---|
keepFormat | boolean | Whether to preserve formatting when pasting |
plainText | boolean | When true, pastes as plain text stripping all formatting |
deniedTags | string[] | HTML tags removed from pasted content (e.g., ['script', 'style']) |
allowedStyles | string[] | CSS style properties preserved during paste (e.g., ['font-size', 'color']) |
⚠️ The correct properties aredeniedTagsandallowedStyles— notdeniedElements,allowedElements, orallowedAttributes.
Paste Events
import {
BlockEditorComponent,
BeforePasteCleanupEventArgs,
AfterPasteCleanupEventArgs
} from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const handleBeforePasteCleanup = (args: BeforePasteCleanupEventArgs) => {
console.log('Content before cleanup:', args.content);
// args.cancel = true to skip cleanup entirely
};
const handleAfterPasteCleanup = (args: AfterPasteCleanupEventArgs) => {
console.log('Content after cleanup:', args.content);
};
return (
<BlockEditorComponent
id="editor"
beforePasteCleanup={handleBeforePasteCleanup}
afterPasteCleanup={handleAfterPasteCleanup}
pasteCleanupSettings={{ keepFormat: false, plainText: false }}
/>
);
}
export default App;BeforePasteCleanupEventArgs
| Property | Type | Description |
|---|---|---|
content | string | The pasted content before cleanup |
cancel | boolean | Set true to cancel the paste cleanup |
AfterPasteCleanupEventArgs
| Property | Type | Description |
|---|---|---|
content | string | The pasted content after cleanup has been applied |
Undo and Redo Functionality
Configure Undo/Redo Stack Size
Use undoRedoStack to control how many undo steps are maintained:
import { BlockEditorComponent } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
return (
// Default is 30; increase for longer history, decrease to save memory
<BlockEditorComponent
id="editor"
undoRedoStack={50}
/>
);
}
export default App;Default Undo/Redo
Undo and Redo are enabled by default. Users can use:
- Keyboard: Ctrl+Z (undo), Ctrl+Y (redo)
- Context menu: Right-click → Undo / Redo
Programmatic Undo/Redo
import { BlockEditorComponent } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const editorRef = React.useRef<BlockEditorComponent>(null);
const undo = () => {
// Most Syncfusion components track undo/redo internally
editorRef.current?.focusIn();
document.execCommand('undo');
};
const redo = () => {
editorRef.current?.focusIn();
document.execCommand('redo');
};
return (
<div>
<div style={{ marginBottom: '10px' }}>
<button onClick={undo}>↶ Undo</button>
<button onClick={redo}>↷ Redo</button>
</div>
<BlockEditorComponent id="editor" ref={editorRef} />
</div>
);
}
export default App;Undo/Redo Limitations
The number of undo steps is typically limited (e.g., last 100 actions). This prevents excessive memory usage on long editing sessions.
Keyboard Shortcuts
Default Shortcuts
| Shortcut | Action |
|---|---|
| Ctrl+B / Cmd+B | Bold |
| Ctrl+I / Cmd+I | Italic |
| Ctrl+U / Cmd+U | Underline |
| Ctrl+Z / Cmd+Z | Undo |
| Ctrl+Y / Cmd+Y | Redo |
| Ctrl+A / Cmd+A | Select All |
| / | Open slash command menu |
| Shift+Enter | Soft line break |
| Backspace | Delete block (on empty line) |
| Tab | Indent block |
| Shift+Tab | Outdent block |
Customize Keyboard Shortcuts
Use the keyConfig prop — a plain key-value object where keys are action identifiers and values are shortcut strings. There is no KeyboardSettingsModel type in the BlockEditor API.
import { BlockEditorComponent } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
return (
<BlockEditorComponent
id="editor"
keyConfig={{
bold: 'ctrl+b',
italic: 'ctrl+i',
underline: 'ctrl+u',
undo: 'ctrl+z',
redo: 'ctrl+y'
}}
/>
);
}
export default App;`keyConfig` type: { [key: string]: string } — keys are action names, values are shortcut combinations.
⚠️ There is noKeyboardSettingsModelin the BlockEditor API. UsekeyConfigdirectly as shown above.
Handle Custom Keyboard Events
function App() {
const editorRef = React.useRef<BlockEditorComponent>(null);
const handleKeyDown = (event: KeyboardEvent) => {
// Ctrl+S: Save
if (event.ctrlKey && event.key === 's') {
event.preventDefault();
console.log('Saving content...');
const content = editorRef.current?.getDataAsJson();
// Save to server or local storage
}
// Ctrl+P: Print
if (event.ctrlKey && event.key === 'p') {
event.preventDefault();
editorRef.current?.print();
}
};
React.useEffect(() => {
const editor = editorRef.current?.element;
editor?.addEventListener('keydown', handleKeyDown);
return () => {
editor?.removeEventListener('keydown', handleKeyDown);
};
}, []);
return <BlockEditorComponent id="editor" ref={editorRef} />;
}Read-Only Mode
Enable Read-Only Mode
import { BlockEditorComponent, BlockModel, ContentType } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const [isReadOnly, setIsReadOnly] = React.useState(true);
const blocks: BlockModel[] = [
{
id: 'block-1',
blockType: 'Paragraph',
content: [{
contentType: ContentType.Text,
content: 'This content is in read-only mode'
}]
}
];
const toggleReadOnly = () => {
setIsReadOnly(!isReadOnly);
};
return (
<div>
<button onClick={toggleReadOnly}>
{isReadOnly ? 'Enable Editing' : 'Disable Editing'}
</button>
<BlockEditorComponent
id="editor"
blocks={blocks}
readonly={isReadOnly}
/>
</div>
);
}
export default App;Read-Only Effects
When read-only is enabled:
- ✓ Content is visible and selectable
- ✓ Links are clickable
- ✗ No editing allowed
- ✗ Toolbars hidden
- ✗ Menus disabled
- ✗ Drag-drop disabled
- ✗ Context menu hidden (copy/link options may still work)
Partial Read-Only (Specific Blocks)
const blocksWithPermissions: BlockModel[] = [
{
id: 'editable-1',
blockType: 'Paragraph',
properties: { readonly: false },
content: [{ contentType: ContentType.Text, content: 'Editable block' }]
},
{
id: 'readonly-1',
blockType: 'Paragraph',
properties: { readonly: true },
content: [{ contentType: ContentType.Text, content: 'Read-only block' }]
}
];XSS Protection and HTML Sanitization
Built-in XSS Protection
The BlockEditor provides automatic protection against XSS attacks through content sanitization.
Safe HTML Parsing
import { BlockEditorComponent } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const editorRef = React.useRef<BlockEditorComponent>(null);
const importUnsafeHtml = (htmlString: string) => {
// The component sanitizes automatically
const sanitized = sanitizeHtml(htmlString);
const blocks = editorRef.current?.parseHtmlToBlocks(sanitized);
console.log('Imported blocks:', blocks);
};
// Manual sanitization helper
const sanitizeHtml = (html: string): string => {
const temp = document.createElement('div');
temp.innerHTML = html;
// Remove script tags and event handlers
const scripts = temp.querySelectorAll('script');
scripts.forEach(script => script.remove());
// Remove event attributes
temp.querySelectorAll('*').forEach(el => {
Array.from(el.attributes).forEach(attr => {
if (attr.name.startsWith('on')) {
el.removeAttribute(attr.name);
}
});
});
return temp.innerHTML;
};
const dangerousHtml = `
<p>Safe content</p>
<script>alert('XSS')</script>
<img src="x" onerror="alert('XSS')" />
`;
return (
<div>
<button onClick={() => importUnsafeHtml(dangerousHtml)}>
Import Potentially Unsafe HTML
</button>
<BlockEditorComponent id="editor" ref={editorRef} />
</div>
);
}
export default App;Custom HTML Sanitizer
import DOMPurify from 'dompurify';
function App() {
const editorRef = React.useRef<BlockEditorComponent>(null);
const importWithDOMPurify = (htmlString: string) => {
// Use DOMPurify library for advanced sanitization
const sanitized = DOMPurify.sanitize(htmlString, {
ALLOWED_TAGS: [
'p', 'div', 'span', 'strong', 'em', 'u', 'br',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'ul', 'ol', 'li', 'a', 'img', 'table', 'tr', 'td', 'th'
],
ALLOWED_ATTR: ['href', 'src', 'alt', 'title']
});
const blocks = editorRef.current?.parseHtmlToBlocks(sanitized);
console.log('Safe blocks:', blocks);
};
return (
<BlockEditorComponent id="editor" ref={editorRef} />
);
}RTL (Right-to-Left) Support
Enable RTL Mode
import { BlockEditorComponent } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const [isRtl, setIsRtl] = React.useState(false);
return (
<div dir={isRtl ? 'rtl' : 'ltr'}>
<button onClick={() => setIsRtl(!isRtl)}>
Toggle RTL
</button>
<BlockEditorComponent
id="editor"
enableRtl={isRtl}
/>
</div>
);
}
export default App;RTL CSS
/* RTL-specific styles */
[dir="rtl"] .e-block-editor {
text-align: right;
direction: rtl;
}
[dir="rtl"] .e-block-handle {
right: 0;
left: auto;
}
[dir="rtl"] .e-toolbar {
flex-direction: row-reverse;
}Internationalization and Localization
Set Locale
import { BlockEditorComponent } from '@syncfusion/ej2-react-blockeditor';
import { setCulture } from '@syncfusion/ej2-base';
import * as React from 'react';
function App() {
React.useEffect(() => {
// Set locale (e.g., 'de', 'fr', 'es', 'ar', 'zh')
setCulture('de'); // German
}, []);
return <BlockEditorComponent id="editor" locale="de" />;
}
export default App;Supported Locales
Common locale codes:
'en'- English'de'- German'fr'- French'es'- Spanish'pt'- Portuguese'ar'- Arabic'zh'- Chinese (Simplified)'ja'- Japanese'ko'- Korean'ru'- Russian
Custom Translations
import { BlockEditorComponent } from '@syncfusion/ej2-react-blockeditor';
import { L10n } from '@syncfusion/ej2-base';
import * as React from 'react';
function App() {
React.useEffect(() => {
// Define custom translations
L10n.load({
'de': {
'blockeditor': {
'insertParagraph': 'Absatz einfügen',
'insertHeading': 'Überschrift einfügen',
'insertBulletList': 'Aufzählung einfügen'
}
}
});
}, []);
return <BlockEditorComponent id="editor" locale="de" />;
}
export default App;Date and Number Formatting
import { BlockEditorComponent } from '@syncfusion/ej2-react-blockeditor';
import { Internationalization } from '@syncfusion/ej2-base';
import * as React from 'react';
function App() {
const intl = new Internationalization('de-DE');
const formatDate = (date: Date) => {
return intl.formatDate(date, { type: 'date', format: 'long' });
};
const formatNumber = (num: number) => {
return intl.formatNumber(num, { useGrouping: true });
};
return (
<div>
<p>Formatted date: {formatDate(new Date())}</p>
<p>Formatted number: {formatNumber(1000.5)}</p>
<BlockEditorComponent id="editor" locale="de" />
</div>
);
}
export default App;Content Change and Focus Events
blockChanged Event
Fires whenever blocks are added, deleted, moved, or updated. This is the primary event for tracking content changes.
import {
BlockEditorComponent,
BlockChangedEventArgs,
BlockAction
} from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const handleBlockChanged = (args: BlockChangedEventArgs) => {
args.changes.forEach(change => {
console.log('Action:', change.action); // 'Insertion' | 'Deletion' | 'Moved' | 'Update'
console.log('Block:', change.data.block);
console.log('Previous block:', change.data.prevBlock);
console.log('Current parent:', change.data.currentParent);
console.log('Previous parent:', change.data.prevParent);
});
};
return (
<BlockEditorComponent
id="editor"
blockChanged={handleBlockChanged}
/>
);
}
export default App;BlockChangedEventArgs
| Property | Type | Description |
|---|---|---|
changes | BlockChange[] | Array of change operations |
BlockChange
| Property | Type | Description |
|---|---|---|
action | BlockAction | Type of action performed |
data | BlockData | Data associated with the change |
BlockAction (Enum)
| Member | Description |
|---|---|
Insertion | A new block was inserted |
Deletion | A block was deleted |
Moved | A block was moved |
Update | A block was updated |
BlockData
| Property | Type | Description |
|---|---|---|
block | BlockModel | The current block after the change |
prevBlock | BlockModel | The block before the change |
currentParent | BlockModel | Current parent block (for nested blocks) |
prevParent | BlockModel | Previous parent block (if parent changed) |
---
focus and blur Events
import {
BlockEditorComponent,
FocusEventArgs,
BlurEventArgs
} from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const handleFocus = (args: FocusEventArgs) => {
console.log('Editor focused on block:', args.blockId);
console.log('Native event:', args.event);
};
const handleBlur = (args: BlurEventArgs) => {
console.log('Editor lost focus from block:', args.blockId);
console.log('Native event:', args.event);
};
return (
<BlockEditorComponent
id="editor"
focus={handleFocus}
blur={handleBlur}
/>
);
}
export default App;FocusEventArgs
| Property | Type | Description |
|---|---|---|
blockId | string | ID of the block that received focus |
event | Event | Native focus event |
BlurEventArgs
| Property | Type | Description |
|---|---|---|
blockId | string | ID of the block that lost focus |
event | Event | Native blur event |
---
selectionChanged Event
Fires when the user's text selection within a block changes.
import { BlockEditorComponent, SelectionChangedEventArgs } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const handleSelectionChanged = (args: SelectionChangedEventArgs) => {
console.log('Selection changed', args.event);
};
return (
<BlockEditorComponent
id="editor"
selectionChanged={handleSelectionChanged}
/>
);
}
export default App;SelectionChangedEventArgs
| Property | Type | Description |
|---|---|---|
event | Event | Native browser event that triggered the selection change |
---
created Event
Fires once when the BlockEditor component has fully initialized.
import { BlockEditorComponent } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const handleCreated = () => {
console.log('BlockEditor is ready');
};
return (
<BlockEditorComponent
id="editor"
created={handleCreated}
/>
);
}
export default App;---
File Upload Events
The BlockEditor fires these events during image block uploads:
import {
BlockEditorComponent,
BeforeUploadEventArgs,
UploadingEventArgs,
FileUploadSuccessEventArgs,
FailureEventArgs
} from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
return (
<BlockEditorComponent
id="editor"
imageBlockSettings={{ saveUrl: '/api/upload', allowedTypes: ['.jpg', '.png', '.gif'] }}
beforeFileUpload={(args: BeforeUploadEventArgs) => {
console.log('Before upload:', args);
// args.cancel = true to abort the upload
}}
fileUploading={(args: UploadingEventArgs) => {
console.log('Uploading...', args);
}}
fileUploadSuccess={(args: FileUploadSuccessEventArgs) => {
console.log('Upload succeeded. File URL:', args.fileUrl);
console.log('File info:', args.file);
console.log('Status:', args.statusText);
}}
fileUploadFailed={(args: FailureEventArgs) => {
console.log('Upload failed:', args);
}}
/>
);
}
export default App;FileUploadSuccessEventArgs
| Property | Type | Description |
|---|---|---|
file | FileInfo | Details about the uploaded file |
fileUrl | string | URL of the uploaded file on the server |
statusText | string | Upload status description |
response | ResponseEventArgs | Server response |
operation | string | Upload operation type |
chunkIndex | number | Chunk index (for chunked uploads) |
chunkSize | number | Size of the upload chunk |
totalChunk | number | Total number of chunks |
e | object | Original event arguments |
---
Complete Advanced Example
import { BlockEditorComponent, BlockModel, ContentType } from '@syncfusion/ej2-react-blockeditor';
import { setCulture } from '@syncfusion/ej2-base';
import * as React from 'react';
function AdvancedEditor() {
const editorRef = React.useRef<BlockEditorComponent>(null);
const [isReadOnly, setIsReadOnly] = React.useState(false);
const [locale, setLocale] = React.useState('en');
React.useEffect(() => {
setCulture(locale);
}, [locale]);
const blocks: BlockModel[] = [
{
id: 'block-1',
blockType: 'Paragraph',
content: [{
contentType: ContentType.Text,
content: 'Advanced BlockEditor with internationalization, RTL, and security features'
}]
}
];
const exportSecurely = () => {
const json = editorRef.current?.getDataAsJson();
console.log('Exported safely:', json);
};
return (
<div>
<div style={{ marginBottom: '20px' }}>
<button onClick={() => setIsReadOnly(!isReadOnly)}>
{isReadOnly ? 'Edit' : 'Read-Only'}
</button>
<select value={locale} onChange={(e) => setLocale(e.target.value)}>
<option value="en">English</option>
<option value="de">Deutsch</option>
<option value="fr">Français</option>
<option value="ar">العربية</option>
</select>
<button onClick={exportSecurely}>Export</button>
</div>
<BlockEditorComponent
id="editor"
ref={editorRef}
blocks={blocks}
readOnly={isReadOnly}
locale={locale}
enableRtl={locale === 'ar'}
undoRedoStack={50}
keyConfig={{ bold: 'ctrl+b', italic: 'ctrl+i', underline: 'ctrl+u' }}
pasteCleanupSettings={{ keepFormat: false, plainText: false, deniedTags: ['script', 'style'] }}
/>
</div>
);
}
export default AdvancedEditor;Block Editor Menus and Commands
Table of Contents
- Overview
- Slash Command Menu
- Context Menu
- Inline Toolbar
- Block Actions Menu
- Menu Customization
- Events and Callbacks
Overview
The BlockEditor provides multiple intuitive menus for content creation and editing:
| Menu Type | Prop | Trigger | Use Case |
|---|---|---|---|
| Slash Commands | commandMenuSettings | Type / in editor | Insert or transform blocks |
| Context Menu | contextMenuSettings | Right-click on block | Cut, copy, paste, indent, custom actions |
| Inline Toolbar | inlineToolbarSettings | Select text | Bold, italic, underline, links, formatting |
| Block Actions | blockActionMenuSettings | Click block action icon | Move, delete, custom per-block actions |
| Block Transform | transformSettings | Click transform icon | Change a block's type |
Slash Command Menu
The Slash Command menu provides keyboard-driven access to insert or transform blocks. Press "/" to open it.
Built-in Commands
Default commands available in the Slash menu:
| Command | Type | Description |
|---|---|---|
| Heading 1-4 | Structure | Insert heading blocks |
| Paragraph | Text | Insert paragraph block |
| Bullet List | List | Unordered list |
| Numbered List | List | Ordered numbered list |
| Checklist | List | To-do list with checkboxes |
| Quote | Structure | Blockquote with attribution |
| Callout | Structure | Highlighted info box |
| Code | Structure | Code block with syntax highlighting |
| Divider | Utility | Horizontal separator |
| Table | Data | Tabular structure |
| Image | Media | Image insertion |
| Toggle | Structure | Collapsible content block |
Customize Slash Command Menu
import { BlockEditorComponent, CommandMenuSettingsModel, CommandFilteringEventArgs, CommandItemSelectEventArgs } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const commandMenuSettings: CommandMenuSettingsModel = {
popupWidth: '350px',
popupHeight: '400px',
commands: [
{
id: 'timestamp-cmd',
label: 'Insert Timestamp',
groupBy: 'Custom',
iconCss: 'e-icons e-schedule',
tooltip: 'Insert current date and time',
type: 'Paragraph'
},
{
id: 'separator-cmd',
label: 'Insert Divider',
groupBy: 'Utility',
iconCss: 'e-icons e-divider',
type: 'Divider',
disabled: false
}
],
// Fires when user types to filter commands — use args.text (not args.searchText)
filtering: (args: CommandFilteringEventArgs) => {
console.log('Filter query:', args.text);
console.log('Filtered commands:', args.commands);
// Set args.cancel = true to prevent default filtering
},
// Fires when user selects a command — use args.command for the selected item
itemSelect: (args: CommandItemSelectEventArgs) => {
console.log('Selected command id:', args.command.id);
console.log('Selected command label:', args.command.label);
console.log('Native event:', args.event);
// Set args.cancel = true to prevent default block insertion
}
};
return (
<BlockEditorComponent
id="block-editor"
commandMenuSettings={commandMenuSettings}
/>
);
}
export default App;CommandMenuSettingsModel Properties
| Property | Type | Description |
|---|---|---|
commands | CommandItemModel[] | Array of command items in the slash menu |
popupHeight | string | CSS height of the popup |
popupWidth | string | CSS width of the popup |
CommandMenuSettingsModel Events
| Event | Args Type | Description |
|---|---|---|
filtering | CommandFilteringEventArgs | Fires when user types to filter commands |
itemSelect | CommandItemSelectEventArgs | Fires when a command item is clicked |
CommandItemModel Properties
| Property | Type | Description |
|---|---|---|
id | string | Unique identifier |
label | string | Display label in the menu |
type | `string \ | BlockType` |
iconCss | string | CSS class for the item icon |
groupBy | string | Group header text |
tooltip | string | Tooltip shown on hover |
shortcut | string | Keyboard shortcut string |
disabled | boolean | Whether the item is disabled |
CommandFilteringEventArgs Properties
| Property | Type | Description |
|---|---|---|
text | string | The query typed by the user |
commands | CommandItemModel[] | Filtered command list |
cancel | boolean | Set true to prevent filtering |
event | Event | Native browser event |
CommandItemSelectEventArgs Properties
| Property | Type | Description |
|---|---|---|
command | CommandItemModel | The command item that was selected |
cancel | boolean | Set true to prevent block insertion |
element | HTMLElement | The clicked HTML element |
event | Event | Native browser event |
isInteracted | boolean | true if triggered by user interaction |
Context Menu
The Context Menu appears on right-click and provides block-specific actions.
Built-in Context Menu Items
Default options in the context menu:
- Undo - Reverse last action
- Redo - Re-apply last undone action
- Cut - Remove and copy to clipboard
- Copy - Copy to clipboard
- Paste - Insert from clipboard
- Indent - Increase block indentation
- Outdent - Decrease block indentation
- Link - Add or edit hyperlink
- Delete - Remove block
Customize Context Menu
import {
BlockEditorComponent,
ContextMenuSettingsModel,
ContextMenuBeforeOpenEventArgs,
ContextMenuBeforeCloseEventArgs,
ContextMenuItemSelectEventArgs
} from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const contextMenuSettings: ContextMenuSettingsModel = {
enable: true,
showItemOnClick: false,
// Fires BEFORE the context menu opens — use beforeOpen (not opening)
beforeOpen: (args: ContextMenuBeforeOpenEventArgs) => {
console.log('Context menu opening');
console.log('Items:', args.items);
console.log('Parent item:', args.parentItem);
// Set args.cancel = true to prevent the menu from opening
},
// Fires BEFORE the context menu closes — use beforeClose (not closing)
beforeClose: (args: ContextMenuBeforeCloseEventArgs) => {
console.log('Context menu closing');
// Set args.cancel = true to keep the menu open
},
// Fires when a menu item is clicked — use args.item (not args.label)
itemSelect: (args: ContextMenuItemSelectEventArgs) => {
console.log('Selected item:', args.item.text);
console.log('Selected item id:', args.item.id);
// Set args.cancel = true to prevent the default action
},
// Define menu items
items: [
{ id: 'undo', text: 'Undo', iconCss: 'e-icons e-undo' },
{ id: 'redo', text: 'Redo', iconCss: 'e-icons e-redo' },
{ separator: true },
{ id: 'cut', text: 'Cut', iconCss: 'e-icons e-cut' },
{ id: 'copy', text: 'Copy', iconCss: 'e-icons e-copy' },
{ id: 'paste', text: 'Paste', iconCss: 'e-icons e-paste' },
{ separator: true },
{
id: 'more',
text: 'More Options',
iconCss: 'e-icons e-settings',
// Nested sub-menu via items array
items: [
{ id: 'custom-action', text: 'Custom Action' }
]
}
]
};
return (
<BlockEditorComponent
id="block-editor"
contextMenuSettings={contextMenuSettings}
/>
);
}
export default App;ContextMenuSettingsModel Properties
| Property | Type | Description |
|---|---|---|
enable | boolean | Whether the context menu is enabled |
itemTemplate | `string \ | Function` |
items | ContextMenuItemModel[] | Array of context menu items |
showItemOnClick | boolean | Show sub-items on click instead of hover |
ContextMenuSettingsModel Events
| Event | Args Type | Description |
|---|---|---|
beforeOpen | ContextMenuBeforeOpenEventArgs | Fires before the menu opens |
beforeClose | ContextMenuBeforeCloseEventArgs | Fires before the menu closes |
itemSelect | ContextMenuItemSelectEventArgs | Fires when a menu item is clicked |
⚠️ The events are namedbeforeOpenandbeforeClose— notopeningorclosing.
ContextMenuItemModel Properties
| Property | Type | Description |
|---|---|---|
id | string | Unique identifier |
text | string | Display text |
iconCss | string | CSS class for icon |
separator | boolean | Renders as a divider line |
shortcut | string | Keyboard shortcut label |
items | ContextMenuItemModel[] | Nested sub-menu items |
ContextMenuBeforeOpenEventArgs Properties
| Property | Type | Description |
|---|---|---|
cancel | boolean | Set true to prevent the menu from opening |
event | Event | Native browser event |
items | ContextMenuItemModel[] | Current menu items |
parentItem | ContextMenuItemModel | Parent item (for sub-menus) |
ContextMenuBeforeCloseEventArgs Properties
| Property | Type | Description |
|---|---|---|
cancel | boolean | Set true to prevent the menu from closing |
event | Event | Native browser event |
items | ContextMenuItemModel[] | Current menu items |
ContextMenuItemSelectEventArgs Properties
| Property | Type | Description |
|---|---|---|
item | ContextMenuItemModel | The item that was clicked |
cancel | boolean | Set true to cancel the action |
event | Event | Native browser event |
Inline Toolbar
The inline toolbar appears when text is selected within a block. Configure it using the inlineToolbarSettings prop.
Default Toolbar Behavior
When text is selected, the inline toolbar floats above the selection offering common formatting actions like Bold, Italic, Underline, StrikeThrough, and hyperlink insertion.
Configure Inline Toolbar
import { BlockEditorComponent, InlineToolbarSettingsModel, ToolbarItemClickEventArgs } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const inlineToolbarSettings: InlineToolbarSettingsModel = {
enable: true,
popupWidth: '100%',
items: ['Bold', 'Italic', 'Underline', 'StrikeThrough', 'InlineCode', 'Link', 'FontColor', 'BackgroundColor'],
itemClick: (args: ToolbarItemClickEventArgs) => {
console.log('Toolbar item clicked:', args.item.text);
console.log('Is user interaction:', args.isInteracted);
// Set args.cancel = true to prevent default formatting
}
};
return (
<BlockEditorComponent
id="block-editor"
inlineToolbarSettings={inlineToolbarSettings}
/>
);
}
export default App;InlineToolbarSettingsModel Properties
| Property | Type | Default | Description |
|---|---|---|---|
enable | boolean | — | Whether the inline toolbar is shown on text selection |
items | string[] | — | Array of toolbar item identifiers to display |
popupWidth | `string \ | number` | '100%' |
InlineToolbarSettingsModel Events
| Event | Args Type | Description |
|---|---|---|
itemClick | ToolbarItemClickEventArgs | Fires when an inline toolbar item is clicked |
ToolbarItemClickEventArgs Properties
| Property | Type | Description |
|---|---|---|
item | IToolbarItemModel | The toolbar item that was clicked |
cancel | boolean | Set true to prevent the formatting action |
event | Event | Native browser event |
isInteracted | boolean | true if triggered by user interaction |
⚠️ The correct prop isinlineToolbarSettings— nottoolbarSettings. There is noToolbarSettingsModelin the BlockEditor API.
Block Actions Menu
The block action menu is a per-block popup triggered by the block action icon. Configure it using blockActionMenuSettings.
Configure Block Actions Menu
import {
BlockEditorComponent,
BlockActionMenuSettingsModel,
BlockActionMenuBeforeOpenEventArgs,
BlockActionMenuBeforeCloseEventArgs,
BlockActionItemSelectEventArgs,
BlockModel,
ContentType
} from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const blockActionMenuSettings: BlockActionMenuSettingsModel = {
enable: true,
enableTooltip: true,
popupWidth: '200px',
popupHeight: 'auto',
items: [
{
id: 'delete',
label: 'Delete Block',
iconCss: 'e-icons e-delete',
tooltip: 'Remove this block'
},
{
id: 'duplicate',
label: 'Duplicate',
iconCss: 'e-icons e-copy',
tooltip: 'Copy this block'
},
{
id: 'move-up',
label: 'Move Up',
iconCss: 'e-icons e-chevron-up',
shortcut: 'Ctrl+Shift+Up'
}
],
beforeOpen: (args: BlockActionMenuBeforeOpenEventArgs) => {
console.log('Block action menu opening');
console.log('Available items:', args.items);
// args.cancel = true to prevent opening
},
beforeClose: (args: BlockActionMenuBeforeCloseEventArgs) => {
console.log('Block action menu closing');
},
itemSelect: (args: BlockActionItemSelectEventArgs) => {
console.log('Action selected:', args.item.id);
console.log('Action label:', args.item.label);
console.log('Is user interaction:', args.isInteracted);
// args.cancel = true to prevent default action
}
};
const blocks: BlockModel[] = [
{
id: 'block-1',
blockType: 'Paragraph',
content: [{ contentType: ContentType.Text, content: 'Hover to see the block action menu icon' }]
}
];
return (
<BlockEditorComponent
id="block-editor"
blocks={blocks}
blockActionMenuSettings={blockActionMenuSettings}
/>
);
}
export default App;BlockActionMenuSettingsModel Properties
| Property | Type | Description |
|---|---|---|
enable | boolean | Whether the block action menu is enabled |
enableTooltip | boolean | Whether tooltips are shown on action items |
items | BlockActionItemModel[] | Array of action items in the menu |
popupHeight | string | CSS height of the action menu popup |
popupWidth | string | CSS width of the action menu popup |
BlockActionMenuSettingsModel Events
| Event | Args Type | Description |
|---|---|---|
beforeOpen | BlockActionMenuBeforeOpenEventArgs | Fires before the menu opens |
beforeClose | BlockActionMenuBeforeCloseEventArgs | Fires before the menu closes |
itemSelect | BlockActionItemSelectEventArgs | Fires when an action item is clicked |
BlockActionItemModel Properties
| Property | Type | Description |
|---|---|---|
id | string | Unique identifier |
label | string | Display label |
iconCss | string | CSS class for the icon |
tooltip | string | Tooltip text shown on hover |
shortcut | string | Keyboard shortcut string |
disabled | boolean | Whether the item is disabled |
BlockActionMenuBeforeOpenEventArgs Properties
| Property | Type | Description |
|---|---|---|
cancel | boolean | Set true to prevent the menu from opening |
event | Event | Native browser event |
items | BlockActionItemModel[] | Menu items |
BlockActionMenuBeforeCloseEventArgs Properties
| Property | Type | Description |
|---|---|---|
cancel | boolean | Set true to prevent the menu from closing |
event | Event | Native browser event |
items | BlockActionItemModel[] | Menu items |
BlockActionItemSelectEventArgs Properties
| Property | Type | Description |
|---|---|---|
item | BlockActionItemModel | The action item that was clicked |
cancel | boolean | Set true to cancel the action |
element | HTMLElement | The HTML element that triggered the click |
isInteracted | boolean | true if triggered by user interaction |
Block Transform Menu
The transform menu allows users to change a block's type. Configure it using transformSettings.
Configure Transform Settings
import {
BlockEditorComponent,
TransformSettingsModel,
TransformItemSelectEventArgs
} from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const transformSettings: TransformSettingsModel = {
popupWidth: '250px',
popupHeight: 'auto',
items: ['Paragraph', 'Heading', 'BulletList', 'NumberedList', 'Checklist', 'Quote', 'Code', 'Callout'],
itemSelect: (args: TransformItemSelectEventArgs) => {
console.log('Transform selected:', args.command.id);
console.log('Native event:', args.event);
// args.cancel = true to prevent the transform
}
};
return (
<BlockEditorComponent
id="block-editor"
transformSettings={transformSettings}
/>
);
}
export default App;TransformSettingsModel Properties
| Property | Type | Description |
|---|---|---|
items | string[] | Block types available for transformation |
popupHeight | string | CSS height of the transform popup |
popupWidth | string | CSS width of the transform popup |
TransformSettingsModel Events
| Event | Args Type | Description |
|---|---|---|
itemSelect | TransformItemSelectEventArgs | Fires when a transform option is selected |
TransformItemSelectEventArgs Properties
| Property | Type | Description |
|---|---|---|
command | TransformItemModel | The selected transform item |
cancel | boolean | Set true to prevent the transform |
element | HTMLElement | The HTML element associated with the selection |
event | Event | Native browser event |
Menu Customization
Disable a Menu Entirely
<BlockEditorComponent
id="block-editor"
contextMenuSettings={{ enable: false }}
blockActionMenuSettings={{ enable: false }}
/>Filter Slash Commands Dynamically
import { BlockEditorComponent, CommandMenuSettingsModel, CommandFilteringEventArgs } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const commandMenuSettings: CommandMenuSettingsModel = {
// Use args.text — not args.searchText
filtering: (args: CommandFilteringEventArgs) => {
const allowedTypes = ['Paragraph', 'Heading', 'BulletList'];
// Filter commands to only show allowed types
args.commands = args.commands.filter(cmd =>
allowedTypes.includes(cmd.type as string)
);
}
};
return (
<BlockEditorComponent
id="block-editor"
commandMenuSettings={commandMenuSettings}
/>
);
}
export default App;Complete Example with All Menus
import {
BlockEditorComponent,
BlockModel,
ContentType,
CommandMenuSettingsModel,
CommandFilteringEventArgs,
CommandItemSelectEventArgs,
ContextMenuSettingsModel,
ContextMenuBeforeOpenEventArgs,
ContextMenuItemSelectEventArgs,
InlineToolbarSettingsModel,
ToolbarItemClickEventArgs,
BlockActionMenuSettingsModel,
BlockActionItemSelectEventArgs,
TransformSettingsModel,
TransformItemSelectEventArgs
} from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const editorRef = React.useRef<BlockEditorComponent>(null);
const commandMenuSettings: CommandMenuSettingsModel = {
popupWidth: '300px',
// Use args.text — not args.searchText
filtering: (args: CommandFilteringEventArgs) => console.log('Filter query:', args.text),
// Use args.command — not args.label/args.id directly
itemSelect: (args: CommandItemSelectEventArgs) => console.log('Command selected:', args.command.label)
};
const contextMenuSettings: ContextMenuSettingsModel = {
enable: true,
// Use beforeOpen — not opening
beforeOpen: (args: ContextMenuBeforeOpenEventArgs) => console.log('Context menu opening', args.items),
// Use args.item — not args.label
itemSelect: (args: ContextMenuItemSelectEventArgs) => console.log('Context action:', args.item.text)
};
const inlineToolbarSettings: InlineToolbarSettingsModel = {
enable: true,
items: ['Bold', 'Italic', 'Underline', 'Link', 'FontColor'],
itemClick: (args: ToolbarItemClickEventArgs) => console.log('Toolbar clicked:', args.item.text)
};
const blockActionMenuSettings: BlockActionMenuSettingsModel = {
enable: true,
items: [
{ id: 'delete', label: 'Delete', iconCss: 'e-icons e-delete' },
{ id: 'duplicate', label: 'Duplicate', iconCss: 'e-icons e-copy' }
],
itemSelect: (args: BlockActionItemSelectEventArgs) => console.log('Action:', args.item.id)
};
const transformSettings: TransformSettingsModel = {
items: ['Paragraph', 'Heading', 'BulletList', 'Code'],
itemSelect: (args: TransformItemSelectEventArgs) => console.log('Transform to:', args.command.id)
};
const blocks: BlockModel[] = [
{
id: 'block-1',
blockType: 'Heading',
properties: { level: 1 },
content: [{ contentType: ContentType.Text, content: 'Block Editor with All Menus' }]
},
{
id: 'block-2',
blockType: 'Paragraph',
content: [{ contentType: ContentType.Text, content: 'Try "/" for commands, right-click for context menu, or select text for the inline toolbar.' }]
}
];
return (
<BlockEditorComponent
id="block-editor"
ref={editorRef}
blocks={blocks}
enableDragAndDrop={true}
commandMenuSettings={commandMenuSettings}
contextMenuSettings={contextMenuSettings}
inlineToolbarSettings={inlineToolbarSettings}
blockActionMenuSettings={blockActionMenuSettings}
transformSettings={transformSettings}
/>
);
}
export default App;Built-in Block Types
Table of Contents
- Block Type Overview
- Text Blocks (Paragraph and Headings)
- List Block Types
- Code Blocks
- Table Blocks
- Embed Blocks
- Nested Block Types
- Block Indentation
- CSS Class Styling
Block Type Overview
The BlockEditorComponent supports multiple built-in block types to handle different content scenarios. Each block type is optimized for specific use cases:
| Block Type | Use Case | Supports Children |
|---|---|---|
| Paragraph | Regular text content | No |
| Heading 1-4 | Section headers | No |
| BulletList | Unordered lists | No |
| NumberedList | Ordered lists | No |
| Checklist | To-do items | No |
| Code | Code snippets | No |
| Table | Tabular data | No |
| Quote | Blockquotes | Yes (children content) |
| Callout | Highlighted information | Yes (children content) |
| CollapsibleParagraph | Expandable text | Yes (children content) |
| CollapsibleHeading 1-4 | Expandable headers | Yes (children content) |
| Image | Image display | No |
| Divider | Horizontal separator | No |
| Template | Custom block rendering | No |
Text Blocks (Paragraph and Headings)
Paragraph Block
The paragraph block is the default block type for regular text content:
import { BlockEditorComponent, BlockModel, ContentType } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const blocks: BlockModel[] = [
{
id: 'para-1',
blockType: 'Paragraph',
content: [
{
contentType: ContentType.Text,
content: 'This is a standard paragraph block.'
}
]
}
];
return <BlockEditorComponent id="editor" blocks={blocks} />;
}
export default App;Heading Blocks
Create headings with different levels (1-4):
const headingBlocks: BlockModel[] = [
{
id: 'h1',
blockType: 'Heading',
properties: { level: 1 },
content: [
{
contentType: ContentType.Text,
content: 'Main Title (Level 1)'
}
]
},
{
id: 'h2',
blockType: 'Heading',
properties: { level: 2 },
content: [
{
contentType: ContentType.Text,
content: 'Subtitle (Level 2)'
}
]
},
{
id: 'h3',
blockType: 'Heading',
properties: { level: 3 },
content: [
{
contentType: ContentType.Text,
content: 'Section (Level 3)'
}
]
},
{
id: 'h4',
blockType: 'Heading',
properties: { level: 4 },
content: [
{
contentType: ContentType.Text,
content: 'Subsection (Level 4)'
}
]
}
];List Block Types
Bullet List
Create unordered lists with bullet points:
const bulletListBlocks: BlockModel[] = [
{
id: 'bullet-1',
blockType: 'BulletList',
content: [
{
contentType: ContentType.Text,
content: 'First item'
}
]
},
{
id: 'bullet-2',
blockType: 'BulletList',
content: [
{
contentType: ContentType.Text,
content: 'Second item'
}
]
},
{
id: 'bullet-3',
blockType: 'BulletList',
content: [
{
contentType: ContentType.Text,
content: 'Third item'
}
]
}
];Numbered List
Create ordered lists with sequential numbering:
const numberedListBlocks: BlockModel[] = [
{
id: 'num-1',
blockType: 'NumberedList',
content: [
{
contentType: ContentType.Text,
content: 'First step'
}
]
},
{
id: 'num-2',
blockType: 'NumberedList',
content: [
{
contentType: ContentType.Text,
content: 'Second step'
}
]
},
{
id: 'num-3',
blockType: 'NumberedList',
content: [
{
contentType: ContentType.Text,
content: 'Third step'
}
]
}
];Checklist Block
Create interactive to-do lists with checkboxes:
const checklistBlocks: BlockModel[] = [
{
id: 'check-1',
blockType: 'Checklist',
properties: { isChecked: false },
content: [
{
contentType: ContentType.Text,
content: 'Task one - not started'
}
]
},
{
id: 'check-2',
blockType: 'Checklist',
properties: { isChecked: true },
content: [
{
contentType: ContentType.Text,
content: 'Task two - completed'
}
]
},
{
id: 'check-3',
blockType: 'Checklist',
properties: { isChecked: false },
content: [
{
contentType: ContentType.Text,
content: 'Task three - pending'
}
]
}
];Code Blocks
Code blocks use ContentType.Text for their content — not ContentType.Code (which does not exist). The language is set via properties.language (ICodeBlockSettings).
import { BlockEditorComponent, BlockModel, ContentType } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const codeBlocks: BlockModel[] = [
{
id: 'code-1',
blockType: 'Code',
properties: { language: 'javascript' },
content: [
{
contentType: ContentType.Text, // Always ContentType.Text
content: 'function greet(name) {\n console.log(`Hello, ${name}!`);\n}'
}
]
},
{
id: 'code-2',
blockType: 'Code',
properties: { language: 'python' },
content: [
{
contentType: ContentType.Text,
content: 'def greet(name):\n print(f"Hello, {name}!")'
}
]
},
{
id: 'code-3',
blockType: 'Code',
properties: { language: 'tsx' },
content: [
{
contentType: ContentType.Text,
content: 'const Greeting = ({ name }) => (\n <div>Hello, {name}!</div>\n);'
}
]
}
];
return <BlockEditorComponent id="editor" blocks={codeBlocks} />;
}
export default App;Configure Available Languages (codeBlockSettings)
Use the codeBlockSettings component prop to control which languages appear in the language picker:
import { BlockEditorComponent } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
return (
<BlockEditorComponent
id="editor"
codeBlockSettings={{
defaultLanguage: 'javascript',
languages: [
{ label: 'JavaScript', language: 'javascript' },
{ label: 'TypeScript', language: 'typescript' },
{ label: 'Python', language: 'python' },
{ label: 'HTML', language: 'html' },
{ label: 'CSS', language: 'css' },
{ label: 'JSON', language: 'json' },
{ label: 'SQL', language: 'sql' },
{ label: 'Bash', language: 'bash' }
]
}}
/>
);
}
export default App;CodeBlockSettingsModel Properties
| Property | Type | Description |
|---|---|---|
defaultLanguage | string | Default syntax highlighting language for new code blocks |
languages | CodeLanguageModel[] | Languages shown in the language selector dropdown |
CodeLanguageModel Properties
| Property | Type | Description |
|---|---|---|
label | string | Display name in the dropdown (e.g., 'JavaScript') |
language | string | Language identifier for syntax highlighting (e.g., 'javascript') |
Supported Language Values
Common language values: javascript, typescript, jsx, tsx, python, java, csharp, cpp, c, html, css, scss, xml, json, sql, yaml, markdown, bash, shell
Table Blocks
Table blocks use ITableBlockSettings in properties. Columns and rows are defined as typed model arrays — not as simple rows: 3, columns: 3 numbers.
import { BlockEditorComponent, BlockModel, ContentType, TableColumnType } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const tableBlock: BlockModel = {
id: 'table-1',
blockType: 'Table',
properties: {
enableHeader: true,
enableRowNumbers: false,
readOnly: false,
width: '100%',
columns: [
{ id: 'col-name', headerText: 'Name', type: TableColumnType.Text, width: '40%' },
{ id: 'col-due', headerText: 'Due Date', type: TableColumnType.Date, width: '30%' },
{ id: 'col-owner', headerText: 'Owner', type: TableColumnType.Mention, width: '30%' }
],
rows: [
{
id: 'row-1',
height: 'auto',
cells: [
{
id: 'cell-1-1',
columnId: 'col-name',
blocks: [{ blockType: 'Paragraph', content: [{ contentType: ContentType.Text, content: 'Task A' }] }]
},
{ id: 'cell-1-2', columnId: 'col-due', blocks: [] },
{ id: 'cell-1-3', columnId: 'col-owner', blocks: [] }
]
}
]
},
content: []
};
return <BlockEditorComponent id="editor" blocks={[tableBlock]} />;
}
export default App;ITableBlockSettings Properties
| Property | Type | Description |
|---|---|---|
columns | TableColumnModel[] | Column definitions (type, header, width) |
rows | TableRowModel[] | Row definitions containing cells |
enableHeader | boolean | Whether to show a header row |
enableRowNumbers | boolean | Whether to show row numbers |
readOnly | boolean | Whether the table is in read-only mode |
width | `string \ | number` |
TableColumnModel Properties
| Property | Type | Description |
|---|---|---|
id | string | Unique column identifier |
headerText | string | Column header text |
type | TableColumnType | Content type for this column |
width | `string \ | number` |
TableColumnType (Enum)
| Member | Description |
|---|---|
Text | Plain text content |
Date | Date values |
Mention | User mention content |
Label | Label content |
Link | Hyperlink content |
TableRowModel Properties
| Property | Type | Description |
|---|---|---|
id | string | Unique row identifier |
height | `string \ | number` |
cells | TableCellModel[] | Cells in this row |
TableCellModel Properties
| Property | Type | Description |
|---|---|---|
id | string | Unique cell identifier |
columnId | string | The column this cell belongs to |
blocks | BlockModel[] | Content of the cell |
Image Blocks
Image blocks use IImageBlockSettings in properties. Configure global image upload settings via the imageBlockSettings component prop.
import { BlockEditorComponent, BlockModel, ContentType } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const imageBlocks: BlockModel[] = [
{
id: 'image-1',
blockType: 'Image',
// IImageBlockSettings — per-block image data
properties: {
src: 'https://example.com/photo.jpg',
altText: 'A descriptive alt text',
width: '100%',
height: 'auto'
},
content: []
}
];
return (
<BlockEditorComponent
id="editor"
blocks={imageBlocks}
// Global image upload configuration
imageBlockSettings={{
saveUrl: '/api/upload-image',
allowedTypes: ['.jpg', '.jpeg', '.png', '.gif', '.webp'],
maxFileSize: 5000000, // 5 MB
enableResize: true,
minWidth: '100px',
maxWidth: '100%',
minHeight: '50px',
maxHeight: '800px',
saveFormat: 'Blob' // or 'Base64'
}}
/>
);
}
export default App;IImageBlockSettings Properties (per-block)
| Property | Type | Description |
|---|---|---|
src | string | Image source URL or path |
altText | string | Alternative text for accessibility |
width | `string \ | number` |
height | `string \ | number` |
ImageBlockSettingsModel Properties (component-level)
| Property | Type | Default | Description |
|---|---|---|---|
saveUrl | string | — | Server endpoint for image uploads |
path | string | — | Base server path for storing uploaded images |
allowedTypes | string[] | — | Allowed file extensions (e.g., ['.jpg', '.png']) |
maxFileSize | number | 30000000 | Max file size in bytes (default 30 MB) |
enableResize | boolean | — | Enable resize handles on images |
width | `string \ | number` | — |
height | `string \ | number` | — |
minWidth | `string \ | number` | — |
maxWidth | `string \ | number` | — |
minHeight | `string \ | number` | — |
maxHeight | `string \ | number` | — |
saveFormat | SaveFormat | — | 'Base64' or 'Blob' |
SaveFormat (Enum)
| Member | Description |
|---|---|
Base64 | Saves image as a Base64-encoded string |
Blob | Saves image as a Blob object |
Nested Block Types
Nested blocks allow children content to be expanded or collapsed. These blocks support parent-child relationships for creating hierarchical document structures.
Quote Block with Children
For Quote blocks, nested blocks are placed in properties.children (part of IQuoteBlockSettings), not as a top-level children key on BlockModel.
const quoteBlock: BlockModel = {
id: 'quote-1',
blockType: 'Quote',
content: [
{ contentType: ContentType.Text, content: 'To be or not to be, that is the question.' }
],
// children and placeholder live inside properties (IQuoteBlockSettings)
properties: {
placeholder: 'Add a quote...',
children: [
{
id: 'quote-attr',
blockType: 'Paragraph',
content: [{ contentType: ContentType.Text, content: '— William Shakespeare' }]
}
]
}
};Callout Block
const calloutBlock: BlockModel = {
id: 'callout-1',
blockType: 'Callout',
content: [
{ contentType: ContentType.Text, content: 'Important Information' }
]
// ICalloutBlockSettings has no additional documented properties
};Collapsible Heading (Levels 1–4)
children, isExpanded, and placeholder are all inside properties (ICollapsibleHeadingBlockSettings):
const collapsibleHeading: BlockModel = {
id: 'collapsible-h1',
blockType: 'CollapsibleHeading',
content: [
{ contentType: ContentType.Text, content: 'Expandable Section' }
],
// ICollapsibleHeadingBlockSettings
properties: {
isExpanded: true,
placeholder: 'Heading text...',
children: [
{
id: 'child-para',
blockType: 'Paragraph',
content: [{ contentType: ContentType.Text, content: 'Content shown when expanded.' }]
},
{
id: 'child-list',
blockType: 'BulletList',
content: [{ contentType: ContentType.Text, content: 'Nested list item' }]
}
]
}
};Other collapsible heading levels:
const collapsibleH2: BlockModel = {
blockType: 'CollapsibleHeading',
content: [{ contentType: ContentType.Text, content: 'Level 2' }],
properties: { isExpanded: true, children: [] }
};Collapsible Paragraph
const collapsiblePara: BlockModel = {
id: 'collapsible-para',
blockType: 'CollapsibleParagraph',
content: [
{ contentType: ContentType.Text, content: 'Click to expand' }
],
// ICollapsibleBlockSettings
properties: {
isExpanded: false,
placeholder: 'Summary text...',
children: [
{
id: 'para-detail',
blockType: 'Paragraph',
content: [{ contentType: ContentType.Text, content: 'Hidden content revealed on expansion.' }]
}
]
}
};Nested Block Properties Summary
⚠️childrenandisExpandedare not top-levelBlockModelproperties. They live insidepropertiesfor each applicable block type:
| Block Type | properties Interface | Nested Fields |
|---|---|---|
Quote | IQuoteBlockSettings | children, placeholder |
CollapsibleHeading | ICollapsibleHeadingBlockSettings | children, isExpanded, placeholder |
CollapsibleParagraph | ICollapsibleBlockSettings | children, isExpanded, placeholder |
Heading | IHeadingBlockSettings | level, placeholder |
Checklist | IChecklistBlockSettings | isChecked, placeholder |
Code | ICodeBlockSettings | language |
Image | IImageBlockSettings | src, altText, width, height |
Table | ITableBlockSettings | columns, rows, enableHeader, enableRowNumbers, readOnly, width |
Block Indentation
Control the indentation level of blocks for creating nested structures:
const indentedBlocks: BlockModel[] = [
{
id: 'level-0',
blockType: 'Paragraph',
indent: 0,
content: [{ contentType: ContentType.Text, content: 'No indentation' }]
},
{
id: 'level-1',
blockType: 'Paragraph',
indent: 1,
content: [{ contentType: ContentType.Text, content: 'One level indent' }]
},
{
id: 'level-2',
blockType: 'Paragraph',
indent: 2,
content: [{ contentType: ContentType.Text, content: 'Two levels indent' }]
},
{
id: 'level-1b',
blockType: 'Paragraph',
indent: 1,
content: [{ contentType: ContentType.Text, content: 'Back to one level' }]
}
];
export function App() {
return <BlockEditorComponent id="editor" blocks={indentedBlocks} />;
}Indentation is useful for:
- Creating hierarchical lists
- Organizing nested paragraphs
- Visual content structure
- Outline-style documents
CSS Class Styling
Apply custom CSS classes to individual blocks for specialized styling:
const styledBlocks: BlockModel[] = [
{
id: 'default',
blockType: 'Paragraph',
content: [{ contentType: ContentType.Text, content: 'Default paragraph' }]
},
{
id: 'info-block',
blockType: 'Paragraph',
cssClass: 'info-block highlight',
content: [{ contentType: ContentType.Text, content: 'This is an info block' }]
},
{
id: 'warning-block',
blockType: 'Paragraph',
cssClass: 'warning-block',
content: [{ contentType: ContentType.Text, content: 'This is a warning' }]
},
{
id: 'success-block',
blockType: 'Paragraph',
cssClass: 'success-block',
content: [{ contentType: ContentType.Text, content: 'Operation successful!' }]
}
];Add corresponding styles in your CSS file:
.info-block {
background-color: #e3f2fd;
border-left: 4px solid #2196f3;
padding: 10px 15px;
}
.warning-block {
background-color: #fff3e0;
border-left: 4px solid #ff9800;
padding: 10px 15px;
}
.success-block {
background-color: #e8f5e9;
border-left: 4px solid #4caf50;
padding: 10px 15px;
}
.highlight {
font-weight: bold;
}Drag-Drop and Content Management
Table of Contents
- Overview
- Enable Drag-and-Drop
- Block Reordering
- Content Insertion
- Block Nesting
- Handle Positioning
- Drag-Drop Events
- Programmatic Block Movement
Overview
The BlockEditor supports intuitive drag-and-drop functionality for reordering blocks, inserting new content, and managing nested structures. Users can:
- Drag blocks to reorder content within the editor
- Move blocks between different nesting levels
- Drop content from external sources
- Handle visual indicators showing drop targets
Enable Drag-and-Drop
Use the enableDragAndDrop prop to enable or disable drag-and-drop reordering. The default is true.
import { BlockEditorComponent, BlockModel, ContentType } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const blocks: BlockModel[] = [
{
id: 'block-1',
blockType: 'Heading',
properties: { level: 1 },
content: [{ contentType: ContentType.Text, content: 'Section 1' }]
},
{
id: 'block-2',
blockType: 'Paragraph',
content: [{ contentType: ContentType.Text, content: 'Drag this block using the handle on the left' }]
},
{
id: 'block-3',
blockType: 'Paragraph',
content: [{ contentType: ContentType.Text, content: 'This block can also be dragged' }]
}
];
return (
<BlockEditorComponent
id="block-editor"
blocks={blocks}
enableDragAndDrop={true}
/>
);
}
export default App;Disable Drag-and-Drop
<BlockEditorComponent
id="block-editor"
blocks={blocks}
enableDragAndDrop={false}
/>⚠️ The correct prop isenableDragAndDrop— notshowBlockHandle. There is noshowBlockHandleprop in the BlockEditor API.
Block Reordering
Blocks can be reordered by dragging them to new positions.
User-Driven Reordering
1. Users hover over a block to see the drag handle (three dots or similar) 2. Click and hold the handle 3. Drag the block up or down 4. Release to drop at the new position
Visual Feedback
The editor provides visual indicators:
- Highlight on hover - Block is draggable
- Drop target line - Shows where block will be placed
- Opacity change - Indicates drag in progress
Constraints
- Blocks maintain their data integrity during moves
- Block IDs remain unchanged
- Child blocks move with their parent
- Read-only mode disables dragging
Content Insertion
Drop Zones
The editor creates drop zones between blocks:
[Block 1]
↓ ← Drop zone here
[Block 2]
↓ ← Drop zone here
[Block 3]External Content Drops
Users can drop external content (text, links, files):
import { BlockEditorComponent, BlockModel, ContentType } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const editorRef = React.useRef<BlockEditorComponent>(null);
const [droppedContent, setDroppedContent] = React.useState<string>('');
const handleBlockDrop = (event: any) => {
event.preventDefault();
// Get dropped data
const droppedText = event.dataTransfer.getData('text/plain');
const droppedHtml = event.dataTransfer.getData('text/html');
setDroppedContent(droppedText || droppedHtml || '');
};
const blocks: BlockModel[] = [
{
id: 'block-1',
blockType: 'Paragraph',
content: [
{
contentType: ContentType.Text,
content: 'Drop text or images here'
}
]
}
];
return (
<div
onDrop={handleBlockDrop}
onDragOver={(e) => e.preventDefault()}
>
<BlockEditorComponent
id="block-editor"
blocks={blocks}
ref={editorRef}
/>
{droppedContent && (
<div>
<p>Dropped content: {droppedContent}</p>
</div>
)}
</div>
);
}
export default App;Block Nesting
Nested blocks support parent-child relationships.
Create Nested Structure
For blocks that support nesting (e.g., Quote, Callout, CollapsibleHeading, CollapsibleParagraph), place children inside the properties object — it is not a top-level property of BlockModel.
const nestedBlocks: BlockModel[] = [
{
id: 'quote-1',
blockType: 'Quote',
content: [{ contentType: ContentType.Text, content: 'A wise saying' }],
properties: {
children: [
{
id: 'child-1',
blockType: 'Paragraph',
content: [{ contentType: ContentType.Text, content: 'Attribution line' }]
}
]
}
},
{
id: 'para-2',
blockType: 'Paragraph',
content: [{ contentType: ContentType.Text, content: 'Regular paragraph (not nested)' }]
}
];⚠️childrenis NOT a top-level property onBlockModel. It lives insidepropertiesfor block types that support nesting (IQuoteBlockSettings,ICalloutBlockSettings,ICollapsibleBlockSettings,ICollapsibleHeadingBlockSettings).
Drag Blocks Between Nesting Levels
Dragging a block to the right indents it (nests it). Dragging left outdents it:
Original: After dragging right: After dragging left:
[Block 1] [Block 1] [Block 1]
[Block 2] drag→ └─ [Block 2] [Block 2]
[Block 3] [Block 3] [Block 3]Collapsible Nested Blocks
For CollapsibleHeading and CollapsibleParagraph, both isExpanded and children belong inside properties:
const collapsibleBlock: BlockModel = {
id: 'collapsible-1',
blockType: 'CollapsibleHeading',
content: [{ contentType: ContentType.Text, content: 'Click to collapse/expand' }],
properties: {
level: 1,
isExpanded: true, // Initially expanded — lives in properties
children: [
{
id: 'child-1',
blockType: 'Paragraph',
content: [{ contentType: ContentType.Text, content: 'Hidden content' }]
}
]
}
};Handle Positioning
Drag Handle Location
The drag handle appears on the left side of each block. It's typically:
- Always visible - For easier access
- Hover to show - To reduce visual clutter
- Hidden - In read-only mode
Customize Handle Appearance
Use CSS to style the drag handle. Enable drag-and-drop with enableDragAndDrop={true}:
import { BlockEditorComponent } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
return (
<div>
<style>{`
.e-block-handle {
color: #2196f3;
cursor: grab;
font-size: 18px;
}
.e-block-handle:active {
cursor: grabbing;
}
`}</style>
<BlockEditorComponent
id="block-editor"
enableDragAndDrop={true}
/>
</div>
);
}
export default App;Drag-Drop Events
The BlockEditor provides three drag-related events: blockDragStart, blockDragging, and blockDropped.
BlockDragEventArgs Interface
Used by blockDragStart and blockDragging:
| Property | Type | Description |
|---|---|---|
blocks | BlockModel[] | The blocks being dragged |
cancel | boolean | Set to true to cancel the drag |
dropIndex | number | Current potential drop index |
event | `MouseEvent \ | TouchEvent` |
fromIndex | number | Original index of the dragged block |
target | HTMLElement | The current drag target element |
BlockDropEventArgs Interface
Used by blockDropped:
| Property | Type | Description |
|---|---|---|
blocks | BlockModel[] | The blocks that were dropped |
dropIndex | number | Index where the blocks were dropped |
event | `MouseEvent \ | TouchEvent` |
fromIndex | number | Original index before the drop |
target | HTMLElement | The element the block was dropped onto |
Block Drag Events Example
import {
BlockEditorComponent,
BlockModel,
BlockDragEventArgs,
BlockDropEventArgs,
ContentType
} from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const editorRef = React.useRef<BlockEditorComponent>(null);
const [draggedBlocks, setDraggedBlocks] = React.useState<BlockModel[]>([]);
const onBlockDragStart = (args: BlockDragEventArgs) => {
console.log('Drag started — from index:', args.fromIndex);
console.log('Dragging blocks:', args.blocks);
setDraggedBlocks(args.blocks);
// Set args.cancel = true to prevent dragging
};
const onBlockDragging = (args: BlockDragEventArgs) => {
console.log('Dragging — current drop index:', args.dropIndex);
};
const onBlockDropped = (args: BlockDropEventArgs) => {
console.log('Dropped — from index:', args.fromIndex, '→ drop index:', args.dropIndex);
console.log('Dropped blocks:', args.blocks);
setDraggedBlocks([]);
};
const blocks: BlockModel[] = [
{
id: 'block-1',
blockType: 'Paragraph',
content: [{ contentType: ContentType.Text, content: 'Drag-enabled block 1' }]
},
{
id: 'block-2',
blockType: 'Paragraph',
content: [{ contentType: ContentType.Text, content: 'Drag-enabled block 2' }]
}
];
return (
<BlockEditorComponent
id="block-editor"
ref={editorRef}
blocks={blocks}
enableDragAndDrop={true}
blockDragStart={onBlockDragStart}
blockDragging={onBlockDragging}
blockDropped={onBlockDropped}
/>
);
}
export default App;Programmatic Block Movement
Move Blocks via API
Use the moveBlock method to reorder blocks programmatically:
import { BlockEditorComponent, BlockModel, ContentType } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const editorRef = React.useRef<BlockEditorComponent>(null);
const blocks: BlockModel[] = [
{
id: 'block-1',
blockType: 'Paragraph',
content: [
{
contentType: ContentType.Text,
content: 'Block 1'
}
]
},
{
id: 'block-2',
blockType: 'Paragraph',
content: [
{
contentType: ContentType.Text,
content: 'Block 2'
}
]
},
{
id: 'block-3',
blockType: 'Paragraph',
content: [
{
contentType: ContentType.Text,
content: 'Block 3'
}
]
}
];
// Move block-1 after block-2
const moveBlockUp = () => {
editorRef.current?.moveBlock('block-1', 'block-2');
console.log('Moved block-1 after block-2');
};
// Move block-3 before block-1
const moveBlockDown = () => {
editorRef.current?.moveBlock('block-3', 'block-1');
console.log('Moved block-3 before block-1');
};
// Swap two blocks
const swapBlocks = () => {
editorRef.current?.moveBlock('block-1', 'block-3');
console.log('Swapped block-1 and block-3');
};
return (
<div>
<div style={{ marginBottom: '10px' }}>
<button onClick={moveBlockUp}>Move Block 1 Down</button>
<button onClick={moveBlockDown}>Move Block 3 Up</button>
<button onClick={swapBlocks}>Swap Blocks</button>
</div>
<BlockEditorComponent
id="block-editor"
ref={editorRef}
blocks={blocks}
enableDragAndDrop={true}
/>
</div>
);
}
export default App;Add Blocks at Specific Positions
const addBlockAfter = () => {
const newBlock: BlockModel = {
blockType: 'Paragraph',
content: [
{
contentType: ContentType.Text,
content: 'New paragraph inserted after block-2'
}
]
};
// Add after block-2
editorRef.current?.addBlock(newBlock, 'block-2', true);
};
const addBlockBefore = () => {
const newBlock: BlockModel = {
blockType: 'Paragraph',
content: [
{
contentType: ContentType.Text,
content: 'New paragraph inserted before block-2'
}
]
};
// Add before block-2
editorRef.current?.addBlock(newBlock, 'block-2', false);
};Complete Example
import { BlockEditorComponent, BlockModel, ContentType } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const editorRef = React.useRef<BlockEditorComponent>(null);
const [blocks, setBlocks] = React.useState<BlockModel[]>([
{
id: 'block-1',
blockType: 'Heading',
properties: { level: 1 },
content: [{ contentType: ContentType.Text, content: 'Drag-Drop Demo' }]
},
{
id: 'block-2',
blockType: 'Paragraph',
content: [{ contentType: ContentType.Text, content: 'Drag blocks using the handle' }]
},
{
id: 'block-3',
blockType: 'BulletList',
content: [{ contentType: ContentType.Text, content: 'Drag to reorder' }]
}
]);
const reorderBlocks = (fromId: string, toId: string) => {
editorRef.current?.moveBlock(fromId, toId);
};
return (
<div style={{ padding: '20px' }}>
<BlockEditorComponent
id="block-editor"
ref={editorRef}
blocks={blocks}
enableDragAndDrop={true}
/>
</div>
);
}
export default App;Getting Started with Block Editor
Table of Contents
- Installation
- Basic Implementation
- CSS Imports
- Creating Your First Document
- Setting Initial Content
- Running the Application
Installation
To install the Syncfusion BlockEditor component, use npm:
npm install @syncfusion/ej2-react-blockeditor --saveThis installs the core BlockEditor package for React applications.
Basic Implementation
Functional Component Example
import { BlockEditorComponent } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
return (
// Create a basic block editor component
<BlockEditorComponent id="block-editor"></BlockEditorComponent>
);
}
export default App;Class Component Example
import { BlockEditorComponent } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
export default class App extends React.Component<{}, {}> {
public render() {
return (
// Create a basic block editor component
<BlockEditorComponent id="block-editor"></BlockEditorComponent>
);
}
}Both functional and class components are supported. The id prop is required and serves as the unique identifier for the component instance.
CSS Imports
The BlockEditor requires CSS styles from multiple Syncfusion packages. Import these in your main CSS file or at the component level:
@import "../node_modules/@syncfusion/ej2-base/styles/material.css";
@import "../node_modules/@syncfusion/ej2-inputs/styles/material.css";
@import "../node_modules/@syncfusion/ej2-popups/styles/material.css";
@import "../node_modules/@syncfusion/ej2-buttons/styles/material.css";
@import "../node_modules/@syncfusion/ej2-splitbuttons/styles/material.css";
@import "../node_modules/@syncfusion/ej2-navigations/styles/material.css";
@import "../node_modules/@syncfusion/ej2-dropdowns/styles/material.css";
@import "../node_modules/@syncfusion/ej2-react-blockeditor/styles/material.css";Available Themes
Replace material with any of these supported theme names:
tailwind3- Tailwind CSS 3 designbootstrap5- Bootstrap 5 stylingfluent- Microsoft Fluent designmaterial-dark- Material dark themebootstrap5-dark- Bootstrap 5 dark theme
Example for Tailwind theme:
@import "../node_modules/@syncfusion/ej2-base/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-inputs/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-popups/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-buttons/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-splitbuttons/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-navigations/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-dropdowns/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-react-blockeditor/styles/tailwind3.css";Creating Your First Document
Create a simple React component with the BlockEditor:
import { BlockEditorComponent } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import '@syncfusion/ej2-react-blockeditor/styles/material.css';
function App() {
return (
<div style={{ padding: '20px' }}>
<h1>My First Block Editor</h1>
<BlockEditorComponent id="block-editor"></BlockEditorComponent>
</div>
);
}
export default App;
ReactDOM.render(<App />, document.getElementById('container'));This creates an empty block editor where users can start typing or use the "/" command to insert blocks.
Setting Initial Content
Using Block Data
To pre-populate the editor with content, use the blocks property with an array of BlockModel objects:
import { BlockEditorComponent, BlockModel, ContentType } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const initialBlocks: BlockModel[] = [
{
id: 'block-1',
blockType: 'Heading',
properties: { level: 1 },
content: [
{
contentType: ContentType.Text,
content: 'Welcome to Block Editor'
}
]
},
{
id: 'block-2',
blockType: 'Paragraph',
content: [
{
contentType: ContentType.Text,
content: 'This is a sample paragraph with initial content.'
}
]
},
{
id: 'block-3',
blockType: 'BulletList',
content: [
{
contentType: ContentType.Text,
content: 'First list item'
}
]
},
{
id: 'block-4',
blockType: 'BulletList',
content: [
{
contentType: ContentType.Text,
content: 'Second list item'
}
]
}
];
return (
<BlockEditorComponent
id="block-editor"
blocks={initialBlocks}
/>
);
}
export default App;Block Properties Explained
Each block requires:
- blockType - The type of content (Paragraph, Heading, BulletList, etc.)
- content - Array of ContentModel objects containing the text and formatting
- id (optional) - Unique identifier for the block
- properties (optional) - Type-specific settings (e.g., heading level)
Content Type Options
The ContentType enum defines the four valid content types for any ContentModel.contentType field:
enum ContentType {
Text = 'Text', // Plain or inline-formatted text
Link = 'Link', // Hyperlink — requires ILinkContentSettings in properties
Mention = 'Mention', // User mention — requires IMentionContentSettings in properties
Label = 'Label' // Label/tag — requires ILabelContentSettings in properties
}⚠️Image,Code, andEmbedare not validContentTypevalues. Images and code are represented asblockTypevalues ('Image','Code'), not as content types.
Block Type Reference
All valid blockType values (from the BlockType enum):
| Value | Description |
|---|---|
'Paragraph' | Standard text paragraph |
'Heading' | Heading (configure level: 1–4 in properties) |
'BulletList' | Unordered bullet list |
'NumberedList' | Ordered numbered list |
'Checklist' | To-do list with checkboxes (use properties.isChecked) |
'Code' | Code block with syntax highlighting (use properties.language) |
'Table' | Data table |
'Quote' | Blockquote (supports nested properties.children) |
'Callout' | Highlighted callout box |
'Image' | Image block (configure via imageBlockSettings prop) |
'Divider' | Horizontal separator line |
'CollapsibleHeading' | Collapsible heading (use properties.isExpanded, properties.children) |
'CollapsibleParagraph' | Collapsible paragraph (use properties.isExpanded, properties.children) |
'Template' | Custom template block (use BlockModel.template) |
Component Sizing
Use width and height props to control the editor container dimensions. Both accept CSS string values or numeric pixel values.
import { BlockEditorComponent } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
return (
<BlockEditorComponent
id="block-editor"
width="800px"
height="600px"
/>
);
}
export default App;| Prop | Type | Default | Description |
|---|---|---|---|
width | `string \ | number` | '100%' |
height | `string \ | number` | 'auto' |
---
Running the Application
After setting up your component and CSS imports:
For Vite
npm run devThe development server typically starts at http://localhost:5173
For Create React App
npm startThe development server typically starts at http://localhost:3000
For Next.js
npm run devThe development server typically starts at http://localhost:3000
Your BlockEditor should now be ready to use! Users can:
- Type directly in the editor
- Press "/" to open the command menu
- Right-click for context menu options
- Drag blocks to reorder them
- Use keyboard shortcuts for formatting
````markdown
Mentions and Labels
Table of Contents
- Overview
- Mention Feature (`users`)
- Label Feature (`labelSettings`)
- Content Models for Mention and Label
- Complete Example
Overview
The BlockEditor supports two special inline content types:
| Feature | Trigger | Prop | Content Type |
|---|---|---|---|
| Mention | @ | users | ContentType.Mention |
| Label | Configurable (default $) | labelSettings | ContentType.Label |
Both features insert structured inline content into a block's content array using ContentType.Mention or ContentType.Label along with a typed properties object.
---
Mention Feature (users)
users Prop
Type: UserModel[] Default: []
Provide a list of users to enable the @mention feature. When the user types @ in the editor, a popup appears listing the available users.
import { BlockEditorComponent } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
return (
<BlockEditorComponent
id="block-editor"
users={[
{
id: 'user1',
user: 'Alice Johnson',
avatarUrl: 'https://example.com/alice.png',
avatarBgColor: '#4caf50'
},
{
id: 'user2',
user: 'Bob Smith',
avatarBgColor: '#2196f3'
},
{
id: 'user3',
user: 'Carol White',
avatarBgColor: '#ff9800',
cssClass: 'admin-user'
}
]}
/>
);
}
export default App;UserModel Interface
| Property | Type | Description |
|---|---|---|
id | string | Unique identifier for the user. Used in IMentionContentSettings.userId. |
user | string | Display name of the user shown in the mention popup. |
avatarUrl | string | URL of the user's avatar image. |
avatarBgColor | string | Background color for the user's avatar (used when no avatarUrl is provided). |
cssClass | string | CSS class applied to the user's block element. |
---
Label Feature (labelSettings)
labelSettings Prop
Type: LabelSettingsModel Default: {}
Configure available labels and the trigger character for the label picker.
import { BlockEditorComponent } from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
return (
<BlockEditorComponent
id="block-editor"
labelSettings={{
triggerChar: '$',
items: [
{ id: 'label1', text: 'Important', labelColor: '#e53935' },
{ id: 'label2', text: 'Review', labelColor: '#1e88e5' },
{ id: 'label3', text: 'Approved', labelColor: '#43a047', iconCss: 'e-icons e-check' },
{ id: 'label4', text: 'Bug', labelColor: '#fb8c00', groupBy: 'Issues' },
{ id: 'label5', text: 'Feature', labelColor: '#8e24aa', groupBy: 'Issues' }
]
}}
/>
);
}
export default App;LabelSettingsModel Interface
| Property | Type | Default | Description |
|---|---|---|---|
items | LabelItemModel[] | — | Array of label items available for selection. |
triggerChar | string | '$' | Character that triggers the label picker popup. |
LabelItemModel Interface
| Property | Type | Description |
|---|---|---|
id | string | Unique identifier for the label. Used in ILabelContentSettings.labelId. |
text | string | Display text for the label. |
labelColor | string | Color of the label for visual distinction (e.g., '#ff0000'). |
iconCss | string | CSS class for the label's icon. |
groupBy | string | Group header for categorizing labels in the picker popup. |
---
Content Models for Mention and Label
When a mention or label is inserted into a block's content array, each item uses the ContentModel structure with a typed properties object.
IMentionContentSettings
Used with contentType: ContentType.Mention.
| Property | Type | Description |
|---|---|---|
userId | string | The ID of the user being mentioned. Must match a UserModel.id from the users prop. |
ILabelContentSettings
Used with contentType: ContentType.Label.
| Property | Type | Description |
|---|---|---|
labelId | string | The ID of the label item being referenced. Must match a LabelItemModel.id from labelSettings.items. |
Inline Content Array Examples
import { BlockModel, ContentType } from '@syncfusion/ej2-react-blockeditor';
// Block with a mention embedded in text
const blockWithMention: BlockModel = {
id: 'block-1',
blockType: 'Paragraph',
content: [
{ contentType: ContentType.Text, content: 'Hello ' },
{
contentType: ContentType.Mention,
content: '@Alice Johnson', // display text
properties: { userId: 'user1' } // IMentionContentSettings
},
{ contentType: ContentType.Text, content: ', please review this.' }
]
};
// Block with a label embedded in text
const blockWithLabel: BlockModel = {
id: 'block-2',
blockType: 'Paragraph',
content: [
{ contentType: ContentType.Text, content: 'Status: ' },
{
contentType: ContentType.Label,
content: 'Important', // display text
properties: { labelId: 'label1' } // ILabelContentSettings
}
]
};⚠️ContentType.MentionandContentType.Labelare valid enum values. Do not useContentType.Code,ContentType.Image, orContentType.Embed— these do not exist in the API.
---
Complete Example
import {
BlockEditorComponent,
BlockModel,
ContentType,
UserModel,
LabelSettingsModel
} from '@syncfusion/ej2-react-blockeditor';
import * as React from 'react';
function App() {
const users: UserModel[] = [
{ id: 'user1', user: 'Alice Johnson', avatarUrl: 'https://example.com/alice.png', avatarBgColor: '#4caf50' },
{ id: 'user2', user: 'Bob Smith', avatarBgColor: '#2196f3' },
{ id: 'user3', user: 'Carol White', avatarBgColor: '#ff9800' }
];
const labelSettings: LabelSettingsModel = {
triggerChar: '$',
items: [
{ id: 'label1', text: 'Important', labelColor: '#e53935' },
{ id: 'label2', text: 'Review', labelColor: '#1e88e5' },
{ id: 'label3', text: 'Approved', labelColor: '#43a047' }
]
};
const initialBlocks: BlockModel[] = [
{
id: 'block-1',
blockType: 'Heading',
properties: { level: 1 },
content: [{ contentType: ContentType.Text, content: 'Project Notes' }]
},
{
id: 'block-2',
blockType: 'Paragraph',
content: [
{ contentType: ContentType.Text, content: 'Assigned to ' },
{
contentType: ContentType.Mention,
content: '@Alice Johnson',
properties: { userId: 'user1' }
},
{ contentType: ContentType.Text, content: ' — status: ' },
{
contentType: ContentType.Label,
content: 'Review',
properties: { labelId: 'label2' }
}
]
},
{
id: 'block-3',
blockType: 'Paragraph',
content: [
{ contentType: ContentType.Text, content: 'Type @ to mention a user, or $ to add a label.' }
]
}
];
return (
<BlockEditorComponent
id="block-editor"
blocks={initialBlocks}
users={users}
labelSettings={labelSettings}
/>
);
}
export default App;Summary
| Task | How |
|---|---|
| Enable mentions | Pass users={UserModel[]} to the component |
| Enable labels | Pass labelSettings={{ triggerChar, items }} |
| Trigger mention popup | Type @ in the editor |
| Trigger label popup | Type the triggerChar (default $) |
| Reference a mention in block data | contentType: ContentType.Mention, properties: { userId } |
| Reference a label in block data | contentType: ContentType.Label, properties: { labelId } |
````
Related skills
How it compares
Choose syncfusion-react-blockeditor when you are committed to Syncfusion EJ2; open-source block editors suit teams avoiding licensed component suites.
FAQ
Which npm package does syncfusion-react-blockeditor use?
syncfusion-react-blockeditor centers on BlockEditorComponent from @syncfusion/ej2-react-blockeditor with Material CSS imports. The skill metadata lists Syncfusion version 33.1.44 and nine linked reference guides for setup through accessibility.
What export formats does Syncfusion BlockEditor support?
Syncfusion BlockEditorComponent exports block content as JSON, HTML, or plain text per syncfusion-react-blockeditor API guidance. Methods-and-api reference documents programmatic add, remove, update, and move block operations plus import/export flows.