EDS4 logo
@eds/rich-text-editor
v7.0.0

RichTextEditor

The rich-text editor (aka. WYSIWYG) allows user to format text using an editing area. This component uses CKEditor under the hood.

The RichTextEditor allows user to add special formatting to the text. Using this editor, user can bold, underline, change colour and fonts, add hyperlinks, add lists and tables, insert video/images etc.

When users are allowed to add simple text that doesn't require formatting, use a TextareaInput instead.

Pass the licenseKey prop to override the default key. Premium features, including AI, require a valid CKEditor 5 commercial license.

To use AI-powered features, pass aiApiUrl and ensure your license includes them. Set isAiDisabled to disable AI. See the

CKEditor AI assistant integration guide

for details.

The placement of the toolbar can be either at the top or bottom; it depends on the context of your application. EDS recommends:

If it contains actions that users need to access frequently and formatting is the primary goal for the user. For example, it is used for creating job descriptions, editing documents, crafting certificate templates, and writing HTML/Markdown, etc.

By default, the RichTextEditor starts at 150 and grows up to 400. These props use numbers, and the values are treated as pixels.

Once the editor reaches maxHeight, the content area scrolls and the toolbar stays fixed at the top.

Use the minHeight prop to set the minimum height of the editor.

Use the maxHeight prop to set the maximum height before the content starts scrolling.

Use showWordCount to display the current word and character count below the editor.

Coming soon

Validation for max character count and max word count is coming soon!

The RichTextEditor supports React refs, allowing you to programmatically interact with the CKEditor instance. This is useful when you need to get or set editor content, focus the editor, or perform other imperative editor operations.

import { useRef } from 'react';
import { RichTextEditor, type RichTextEditorInstance } from '@eds/rich-text-editor';
const MyComponent = () => {
const editorRef = useRef<RichTextEditorInstance>(null);
const handleGetContent = () => {
if (editorRef.current) {
const content = editorRef.current.getData();
console.log('Editor content:', content);
}
};
const handleSetContent = () => {
if (editorRef.current) {
editorRef.current.setData('<p>New content</p>');
}
};
const handleFocus = () => {
if (editorRef.current) {
editorRef.current.editing.view.focus();
}
};
return (
<>
<RichTextEditor ref={editorRef} />
<button onClick={handleGetContent}>Get Content</button>
<button onClick={handleSetContent}>Set Content</button>
<button onClick={handleFocus}>Focus Editor</button>
</>
);
};

You can use either refs or the onReady callback to access the editor instance. Choose based on your preference:

  • Ref: Better for imperative operations or when you need access to the editor instance at specific times
  • onReady callback: Better for setup logic that should run when the editor initializes
// Using ref
const editorRef = useRef<RichTextEditorInstance>(null);
<RichTextEditor ref={editorRef} />;
// Using onReady callback
<RichTextEditor
onReady={(editor) => {
editor.editing.view.focus();
}}
/>;

Both approaches can be used together in the same component.

The RichTextEditor supports standard focus and blur event handlers, allowing you to track when the editor receives or loses focus. This is useful for form validation, tracking user interactions, or triggering side effects.

The onFocus callback is called when the editor receives focus:

import { RichTextEditor } from '@eds/rich-text-editor';
const MyComponent = () => {
const handleFocus = () => {
console.log('Editor focused');
};
return <RichTextEditor onFocus={handleFocus} />;
};

The onBlur callback is called when the editor loses focus:

import { RichTextEditor } from '@eds/rich-text-editor';
const MyComponent = () => {
const handleBlur = () => {
console.log('Editor blurred');
};
return <RichTextEditor onBlur={handleBlur} />;
};

The RichTextEditor component supports loading custom CKEditor 5 plugins, allowing you to extend its functionality with your own features. This is particularly useful when you need application-specific functionality that isn't provided by the default plugins.

Note: Custom plugins should be used as a last resort. Before creating a custom plugin, check if the existing toolbar configuration and built-in features can meet your needs. Custom plugins add complexity and maintenance overhead, so use them sparingly and only when truly necessary.

To add custom plugins, use the extraPlugins prop along with additionalConfig to customize the toolbar and plugin settings:

import { RichTextEditor } from '@eds/rich-text-editor';
import { MyCustomPlugin } from './MyCustomPlugin';
const MyComponent = () => {
return (
<RichTextEditor
extraPlugins={[MyCustomPlugin]}
additionalConfig={{
toolbar: {
items: ['bold', 'italic', '|', 'myCustomButton'],
},
}}
/>
);
};

Here's an example of creating a simple custom plugin that inserts timestamps:

// InsertTimestampPlugin.ts
import { Plugin, ButtonView, Command } from '@eds/rich-text-editor/plugin-api';
import type { Editor } from '@eds/rich-text-editor/plugin-api';
// The plugin API entrypoint is client-only.
// Import it only from plugin files that are dynamically loaded on the client.
class InsertTimestampCommand extends Command {
execute() {
const editor = this.editor;
const timestamp = new Date().toLocaleString();
editor.model.change((writer) => {
const position = editor.model.document.selection.getFirstPosition();
if (position) {
editor.model.insertContent(writer.createText(timestamp), position);
}
});
}
refresh() {
this.isEnabled = this.editor.model.document.selection.getFirstPosition() !== null;
}
}
export class InsertTimestampPlugin extends Plugin {
static get pluginName() {
return 'InsertTimestamp';
}
init() {
const editor = this.editor;
// Register command
editor.commands.add('insertTimestamp', new InsertTimestampCommand(editor as Editor));
// Add toolbar button
editor.ui.componentFactory.add('insertTimestamp', (locale) => {
const view = new ButtonView(locale);
const command = editor.commands.get('insertTimestamp');
view.set({
label: 'Insert Timestamp',
withText: true,
tooltip: true,
});
if (command) {
view.bind('isEnabled').to(command, 'isEnabled');
}
view.on('execute', () => {
editor.execute('insertTimestamp');
editor.editing.view.focus();
});
return view;
});
}
}

If you need to keep the plugin client-only in an SSR environment, load the plugin file dynamically and then pass it into extraPlugins.

You can load multiple custom plugins at once:

import { RichTextEditor } from '@eds/rich-text-editor';
import { InsertTimestampPlugin } from './plugins/InsertTimestampPlugin';
import { CustomHighlightPlugin } from './plugins/CustomHighlightPlugin';
const MyComponent = () => {
return (
<RichTextEditor
extraPlugins={[InsertTimestampPlugin, CustomHighlightPlugin]}
additionalConfig={{
toolbar: {
items: ['bold', 'italic', 'underline', '|', 'insertTimestamp', 'customHighlight'],
},
}}
/>
);
};

The additionalConfig prop allows you to pass plugin-specific configuration:

<RichTextEditor
extraPlugins={[MentionPlugin]}
additionalConfig={{
mention: {
feeds: [
{
marker: '@',
feed: ['@john', '@jane', '@admin'],
minimumCharacters: 1,
},
],
},
}}
/>