diff --git a/docs/examples/builds/multi-root-editor.md b/docs/examples/builds/multi-root-editor.md index 610efbfbaf1..a962fbfb35e 100644 --- a/docs/examples/builds/multi-root-editor.md +++ b/docs/examples/builds/multi-root-editor.md @@ -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.
View editor configuration script diff --git a/docs/framework/contributing/code-style.md b/docs/framework/contributing/code-style.md index 03a9f061b2a..91085cbe7cd 100644 --- a/docs/framework/contributing/code-style.md +++ b/docs/framework/contributing/code-style.md @@ -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: diff --git a/docs/getting-started/setup/editor-types.md b/docs/getting-started/setup/editor-types.md index 837d302a0d5..3a427692590 100644 --- a/docs/getting-started/setup/editor-types.md +++ b/docs/getting-started/setup/editor-types.md @@ -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. - - At this time, the multi-root editor is not yet available via the [Builder](https://ckeditor.com/ckeditor-5/builder/?redirect=docs). - +At this time, the multi-root editor is not yet available via the [Builder](https://ckeditor.com/ckeditor-5/builder/?redirect=docs). - + The multi-root editor requires a more advanced configuration of the roots. @@ -274,3 +272,7 @@ Then, use these roots to place editor windows in the document. ``` + + + 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. + diff --git a/docs/getting-started/setup/root-types.md b/docs/getting-started/setup/root-types.md new file mode 100644 index 00000000000..4067a62dee1 --- /dev/null +++ b/docs/getting-started/setup/root-types.md @@ -0,0 +1,195 @@ +--- +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). + +## Block 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: '

Start writing here.

' + }, + licenseKey: '', + // ... + } ) + .then( /* ... */ ) + .catch( /* ... */ ); +``` + +```js +ClassicEditor + .create( { + attachTo: document.querySelector( '#editor' ), + root: { + initialData: '

Start writing here.

', + modelElement: '$root' + }, + licenseKey: '', + // ... + } ) + .then( /* ... */ ) + .catch( /* ... */ ); +``` + +### Allowed content in a block root + +A block 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 Enter 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: + + +```js +import { ClassicEditor, Essentials, Bold, Italic } from 'ckeditor5'; + +ClassicEditor + .create( { + attachTo: document.querySelector( '#editor' ), + root: { + initialData: 'My document title', + modelElement: '$inlineRoot' + }, + licenseKey: '', + plugins: [ Essentials, Bold, Italic ], + toolbar: [ 'bold', 'italic' ] + } ) + .then( /* ... */ ) + .catch( /* ... */ ); +``` + + +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: '', + // ... + } ) + .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 block 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 block root for the body: + + +```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: '

Main content goes here.

' + } + }, + licenseKey: '', + plugins: [ Essentials, Bold, Italic, Paragraph, Heading ], + toolbar: [ 'heading', '|', 'bold', 'italic' ] + } ) + .then( /* ... */ ) + .catch( /* ... */ ); +``` +
+ +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 block root. +editor.addRoot( 'section', { + initialData: '

Section content.

' +} ); + +// 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 host element + +When you mount an inline root on a non-block HTML element such as a ``, 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 `
` does not require any extra CSS. See the {@link getting-started/setup/css Editor and content styles} guide for broader CSS customization options. + + + When using a `` as the host element, also set `display: inline-block` on the `` itself, since block-level children are not valid inside an inline element. + diff --git a/docs/updating/update-to-48.md b/docs/updating/update-to-48.md index 7f5708fab21..9dc77ed4c4d 100644 --- a/docs/updating/update-to-48.md +++ b/docs/updating/update-to-48.md @@ -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`.