-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Add a guide for root types #20149
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
gorzelinski
wants to merge
18
commits into
master
Choose a base branch
from
cc/9930
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Add a guide for root types #20149
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
3d0ad8e
Add the root types guide.
gorzelinski caf918d
Cross reference API docs.
gorzelinski 4781533
Format document.
gorzelinski 5e48bb9
Add links to external guides.
gorzelinski 1c38871
Link back to the guide.
gorzelinski e7a770b
Remove excessive code switchers.
gorzelinski 3015774
Clarify what the root is.
gorzelinski f0e6c36
Clarify the allowed content in the block root.
gorzelinski 29fadf2
Expand the Mixed root types section.
gorzelinski 99c35ee
Fix whitespace.
gorzelinski 79a971f
Add a reference in the multi root example.
gorzelinski 8bb9079
Add a section about dynamic roots.
gorzelinski 8d7c286
Add the RFC link.
gorzelinski a9fdcfa
Update the date. [skip ci]
gorzelinski b3ae27e
Small fixes.
gorzelinski 67bd3d9
Address review comments.
gorzelinski 024c3cb
Rename "block" to "standard" root.
gorzelinski e39260e
Add the styling section.
gorzelinski File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,223 @@ | ||
| --- | ||
| category: setup | ||
| menu-title: Root types | ||
| meta-title: Root types | CKEditor 5 Documentation | ||
| meta-description: Learn how to configure CKEditor 5 root types to control whether a root accepts block content, inline-only content, or a mix of both. | ||
| order: 27 | ||
| modified_at: 2026-05-15 | ||
| --- | ||
|
|
||
| # Root types | ||
|
|
||
| In CKEditor 5, a root is the top-level container element in the document model - every editable area has exactly one. The type of that root element determines what content is allowed in that area. By default, roots use the `$root` model element, which accepts block-level content such as paragraphs, headings, lists, and tables. | ||
|
|
||
| You can configure a root to use a different model element via the {@link module:core/editor/editorconfig~RootConfig#modelElement `config.root.modelElement`} option, and set initial root attributes via {@link module:core/editor/editorconfig~RootConfig#modelAttributes `config.root.modelAttributes`}. CKEditor 5 ships with a second built-in root type, `$inlineRoot`, which restricts the root to inline content only - text and inline formatting, but no block elements. This turns the root into a paragraph-like editing area, suitable for document titles, form labels, meta descriptions, and similar single-line fields. For the technical background behind this feature, see the [paragraph-like editor RFC](https://github.com/ckeditor/ckeditor5/issues/19921). | ||
|
|
||
| ## Standard root | ||
|
|
||
| The default root type is `$root`. It accepts the full range of block-level content: paragraphs, headings, lists, tables, block images, and any other block elements that the enabled plugins support. This is the standard editing experience for most use cases - articles, documents, comments, and similar rich-text areas. | ||
|
|
||
| ### Configuration | ||
|
|
||
| You do not need to set `modelElement` explicitly to get this behavior. The following two configurations are equivalent: | ||
|
|
||
| ```js | ||
| ClassicEditor | ||
| .create( { | ||
| attachTo: document.querySelector( '#editor' ), | ||
| root: { | ||
| initialData: '<p>Start writing here.</p>' | ||
| }, | ||
| licenseKey: '<YOUR_LICENSE_KEY>', | ||
| // ... | ||
| } ) | ||
| .then( /* ... */ ) | ||
| .catch( /* ... */ ); | ||
| ``` | ||
|
|
||
| ```js | ||
| ClassicEditor | ||
| .create( { | ||
| attachTo: document.querySelector( '#editor' ), | ||
| root: { | ||
| initialData: '<p>Start writing here.</p>', | ||
| modelElement: '$root' | ||
| }, | ||
| licenseKey: '<YOUR_LICENSE_KEY>', | ||
| // ... | ||
| } ) | ||
| .then( /* ... */ ) | ||
| .catch( /* ... */ ); | ||
| ``` | ||
|
|
||
| ### Allowed content in a standard root | ||
|
|
||
| A standard root accepts whatever block elements the enabled plugins register: paragraphs, headings, lists, tables, block images, code blocks, and similar. Inline content such as text and formatting must appear inside those block elements - it cannot be placed directly in the root. This reflects the standard document structure enforced by the {@link framework/deep-dive/schema#generic-items schema}: root → blocks → inline content. | ||
|
|
||
| ## Inline root | ||
|
|
||
| A root configured with `$inlineRoot` behaves like a single paragraph: pressing <kbd>Enter</kbd> has no effect, because inserting a new block is not allowed. | ||
|
|
||
| ### Configuration | ||
|
|
||
| To configure any single-root editor type as inline-only, set {@link module:core/editor/editorconfig~RootConfig#modelElement `modelElement`} to `'$inlineRoot'` in the `root` config: | ||
|
|
||
| <code-switcher> | ||
| ```js | ||
| import { ClassicEditor, Essentials, Bold, Italic } from 'ckeditor5'; | ||
|
|
||
| ClassicEditor | ||
| .create( { | ||
| attachTo: document.querySelector( '#editor' ), | ||
| root: { | ||
| initialData: 'My document title', | ||
| modelElement: '$inlineRoot' | ||
| }, | ||
| licenseKey: '<YOUR_LICENSE_KEY>', | ||
| plugins: [ Essentials, Bold, Italic ], | ||
| toolbar: [ 'bold', 'italic' ] | ||
| } ) | ||
| .then( /* ... */ ) | ||
| .catch( /* ... */ ); | ||
| ``` | ||
| </code-switcher> | ||
|
|
||
| The `modelElement` option works with all editor types: `ClassicEditor`, `InlineEditor`, `BalloonEditor`, `BalloonBlockEditor`, `DecoupledEditor`, and `MultiRootEditor`. | ||
|
|
||
| For non-classic editors, consider passing a semantically appropriate DOM element as `root.element` instead of relying on the default `div`. For example, if the inline root serves as a document title, an `h1` element is a better fit: | ||
|
|
||
| ```js | ||
| InlineEditor | ||
| .create( { | ||
| root: { | ||
| element: document.querySelector( 'h1#title' ), | ||
| modelElement: '$inlineRoot' | ||
| }, | ||
| licenseKey: '<YOUR_LICENSE_KEY>', | ||
| // ... | ||
| } ) | ||
| .then( /* ... */ ) | ||
| .catch( /* ... */ ); | ||
| ``` | ||
|
|
||
| ### Allowed content in an inline root | ||
|
|
||
| The `$inlineRoot` model element allows the same content as a paragraph: text nodes and inline objects. Block elements are not permitted. Where a standard root follows the root → blocks → inline content structure, an inline root skips the block layer entirely: root → inline content. For a deeper look at how CKEditor 5 schema controls content rules, see the {@link framework/deep-dive/schema#generic-items Schema deep dive} guide. | ||
|
|
||
| | Content type | Allowed | | ||
| |-----------------------------------------------------------|---------| | ||
| | Plain text | Yes | | ||
| | Inline formatting (bold, italic, underline, and similar) | Yes | | ||
| | Links | Yes | | ||
| | Mentions | Yes | | ||
| | Inline images | Yes | | ||
| | Paragraphs and headings | No | | ||
| | Lists | No | | ||
| | Tables | No | | ||
| | Block images | No | | ||
|
|
||
| Plugins that only produce block-level output will have no effect inside an `$inlineRoot` root. You can still include such plugins in your editor setup - they will be inactive when the cursor is inside an inline root, and toolbar items for block-only features will be disabled. | ||
|
|
||
| ## Mixed root types in multi-root editor | ||
|
|
||
| Mixing root types lets different parts of the same document use different content models. In a multi-root editor, you can configure each root independently. For example, a common pattern is to use an inline root for the title and a standard root for the body: | ||
|
|
||
| <code-switcher> | ||
| ```js | ||
| import { MultiRootEditor, Essentials, Bold, Italic, Paragraph, Heading } from 'ckeditor5'; | ||
|
|
||
| MultiRootEditor | ||
| .create( { | ||
| roots: { | ||
| title: { | ||
| element: document.querySelector( '#title' ), | ||
| initialData: 'My document title', | ||
| modelElement: '$inlineRoot' | ||
| }, | ||
| body: { | ||
| element: document.querySelector( '#body' ), | ||
| initialData: '<p>Main content goes here.</p>' | ||
| } | ||
| }, | ||
| licenseKey: '<YOUR_LICENSE_KEY>', | ||
| plugins: [ Essentials, Bold, Italic, Paragraph, Heading ], | ||
| toolbar: [ 'heading', '|', 'bold', 'italic' ] | ||
| } ) | ||
| .then( /* ... */ ) | ||
| .catch( /* ... */ ); | ||
| ``` | ||
| </code-switcher> | ||
|
|
||
| The `title` root only accepts inline content, while the `body` root accepts the full range of block elements. The toolbar and undo stack are shared between both roots. See {@link getting-started/setup/editor-types#multi-root-editor Editor types} for a broader overview of the multi-root editor. | ||
|
|
||
| ### Adding roots dynamically | ||
|
|
||
| In a multi-root editor, you can add roots at runtime using {@link module:editor-multi-root/multirooteditor~MultiRootEditor#addRoot `editor.addRoot()`}. The `modelElement` option sets the root type, the same way as in the static configuration: | ||
|
|
||
| ```js | ||
| editor.on( 'addRoot', ( evt, root ) => { | ||
| const editableElement = editor.createEditable( root ); | ||
|
|
||
| document.querySelector( '#editors' ).appendChild( editableElement ); | ||
| } ); | ||
|
|
||
| // Add a standard root. | ||
| editor.addRoot( 'section', { | ||
| initialData: '<p>Section content.</p>' | ||
| } ); | ||
|
|
||
| // Add an inline root. | ||
| editor.addRoot( 'sectionTitle', { | ||
| modelElement: '$inlineRoot', | ||
| initialData: 'Section title' | ||
| } ); | ||
| ``` | ||
|
|
||
| The root type is fixed at creation time and cannot be changed afterward. | ||
|
|
||
| ## Styling the editable area | ||
|
|
||
| ### Styling the host element | ||
|
|
||
| When you mount an inline root on a non-block HTML element such as a `<span>`, the browser may render the editable area with unexpected line breaks or sizing. This happens because block-filler mechanisms used by the editor can interact poorly with inline host elements. | ||
|
|
||
| To avoid this, apply the following CSS to the editable element: | ||
|
|
||
| ```css | ||
| .ck-editor__editable { | ||
| display: inline-block; | ||
| max-width: fit-content; | ||
| } | ||
| ``` | ||
|
|
||
| This ensures the editing area does not collapse or stretch beyond its content. Mounting on a block element like a `<div>` does not require any extra CSS. | ||
|
|
||
| <info-box> | ||
| When using a `<span>` as the host element, also set `display: inline-block` on the `<span>` itself, since block-level children are not valid inside an inline element. | ||
| </info-box> | ||
|
|
||
| ### Applying classes and styles | ||
|
|
||
| Instead of targeting the editable element with a global CSS selector, you can apply CSS classes and inline styles directly from the editor configuration via `root.element`. The example below adds a custom class and sets a minimum and maximum height on the editable area: | ||
|
|
||
| ```js | ||
| InlineEditor | ||
| .create( { | ||
| root: { | ||
| element: { | ||
| classes: [ 'my-editor' ], | ||
| styles: { | ||
| 'min-height': '300px', | ||
| 'max-height': '500px' // Adds a scrollbar when content overflows. | ||
| } | ||
| }, | ||
| initialData: '<p>Start writing here.</p>' | ||
| }, | ||
| licenseKey: '<YOUR_LICENSE_KEY>', | ||
| // ... | ||
| } ) | ||
| .then( /* ... */ ) | ||
| .catch( /* ... */ ); | ||
| ``` | ||
|
|
||
| The `classes` array and `styles` object are applied to the editable element. This is useful for controlling dimensions, scoping CSS rules, or integrating with a class-based styling system. For broader CSS customization options, see the {@link getting-started/setup/css Editor and content styles} guide. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.