Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/examples/builds/multi-root-editor.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The main difference between using a multi-root editor and using multiple separat

## Editor example configuration

Check out the {@link getting-started/setup/editor-types#multi-root-editor Editor types} guide to learn more about implementing this kind of editor. You will find implementation steps there. You can see this example editor's code below.
Check out the {@link getting-started/setup/editor-types#multi-root-editor Editor types} guide to learn more about implementing this kind of editor. You will find implementation steps there. To learn how to configure individual roots to accept different content — for example, an inline-only title alongside a block content body — see the {@link getting-started/setup/root-types Root types} guide. You can see this example editor's code below.

<details>
<summary>View editor configuration script</summary>
Expand Down
2 changes: 1 addition & 1 deletion docs/framework/contributing/code-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -1179,7 +1179,7 @@ This rule ensures that changelog entry files are populated with proper data and

### Disallow hardcoded `$root` literals: `ckeditor5-rules/no-literal-dollar-root`

This rule disallows the literal `'$root'` string anywhere it could be used as a schema context. Hardcoding `'$root'` is silently wrong when {@link module:core/editor/editorconfig~RootConfig#modelElement `config.root.modelElement`} is customized: the runtime root no longer matches the literal, and any schema check or upcast against it operates against the wrong element name.
This rule disallows the literal `'$root'` string anywhere it could be used as a schema context. Hardcoding `'$root'` is silently wrong when {@link module:core/editor/editorconfig~RootConfig#modelElement `config.root.modelElement`} is customized: the runtime root no longer matches the literal, and any schema check or upcast against it operates against the wrong element name. See the {@link getting-started/setup/root-types Root types} guide for an overview of the available root model element types.

The rule also reports two specific patterns that have a name-agnostic replacement and provides an auto-fix for them:

Expand Down
2 changes: 2 additions & 0 deletions docs/getting-started/setup/css.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ Here are some essential CSS variables for customizing the editor:

Knowing the variables, you can push the customization even further and create your own themes, as in this {@link examples/theme-customization dark theme example}.

To apply classes or styles directly to the editable area from the editor configuration, see the {@link getting-started/setup/root-types#styling-the-editable-area Styling the editable area} section of the Root types guide.

## Customizing the look of the features

Similarly to the customizable editor look, some features also provide an interface to change their styles via [native CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_cascading_variables/Using_CSS_custom_properties).
Expand Down
10 changes: 6 additions & 4 deletions docs/getting-started/setup/editor-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,11 +207,9 @@ The multi-root editor is an editor type that features multiple, separate editabl
See an {@link examples/builds/multi-root-editor example of the multi-root editor} in action.
</snippet-footer>

<info-box>
At this time, the multi-root editor is not yet available via the [Builder](https://ckeditor.com/ckeditor-5/builder/?redirect=docs).
</info-box>
At this time, the multi-root editor is not yet available via the [Builder](https://ckeditor.com/ckeditor-5/builder/?redirect=docs).

<info-box important>
<info-box>
The multi-root editor requires a more advanced configuration of the roots.
</info-box>

Expand Down Expand Up @@ -274,3 +272,7 @@ Then, use these roots to place editor windows in the document.
</div>
</div>
```

<info-box hint>
All editor types support configuring what content a root can hold. This is most useful in multi-root setups, but it works with any editor type. See the {@link getting-started/setup/root-types Root types} guide for details.
</info-box>
223 changes: 223 additions & 0 deletions docs/getting-started/setup/root-types.md
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&nbsp;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&nbsp;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
Comment thread
gorzelinski marked this conversation as resolved.

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&nbsp;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.
2 changes: 2 additions & 0 deletions docs/updating/update-to-48.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ If your integration reads configuration values directly, update access paths as
* `config.get( 'placeholder' )` -> `config.get( 'roots.main.placeholder' )`
* `config.get( 'label' )` -> `config.get( 'roots.main.label' )`

See the {@link getting-started/setup/root-types Root types} guide for a full overview of root configuration options.

#### Dynamic root management

The legacy signatures of `MultiRootEditor#addRoot()` and `MultiRootEditor#createEditable()` are deprecated and will be removed in a future release. They are replaced with new signatures that align with the way the editor root configuration is specified in `config.roots`.
Expand Down