Schema examples
More schemas, from small to large. Each live example is a JSON file as it is, and the editor shows what the schema allows: right-click an element, then Insert › Insert after, and compare the lists at different positions. The breadcrumbs above the editor show the path to the element with the cursor, so you can see what holds what. The editors use the converters of the plugin, and CSS for the layout.
A table: parents that restrict their children
A table is a container of rows, a row is a container of cells. allowIn replaces the default places, so a row exists only in a table, and a cell only in a row: the same as CKEditor's own table.
The editor
import { Essentials, Paragraph, SourceEditing } from 'ckeditor5';
import {
ElementBreadcrumbs,
ElementContextMenu,
ElementSchema,
ElementSelectable,
ElementQuickInsert,
insertIcon
} from 'ckeditor5-elements';
import table from './shared/dataTable.json';
import type { Demo } from './shared/demo';
export default {
config: {
plugins: [
Essentials,
SourceEditing,
Paragraph,
ElementSchema,
// Selects the `empty` and `container` elements of the schema as a whole.
ElementSelectable,
// Lists what the schema allows at a position, in the context menu.
ElementQuickInsert,
ElementContextMenu,
// Shows the path to the cursor: what holds what.
ElementBreadcrumbs
],
// `-` starts a new toolbar line; it requires `shouldNotGroupWhenFull`.
toolbar: { items: ['undo', 'redo', 'sourceEditing', '-', 'breadcrumbs'], shouldNotGroupWhenFull: true },
elementSchema: table,
// The elements are `div` and `span` in the data, and in the editor.
elementSchemaConverters: true,
// Right-click an element: Insert › Insert before / after lists what the schema allows there.
elementContextMenu: (element, editor) => [
{
title: 'Insert',
icon: insertIcon,
items: [editor.commands.get('quickInsertBefore'), editor.commands.get('quickInsertAfter')]
}
],
elementQuickInsert: {
labels: {
paragraph: 'Paragraph',
dataTable: 'Table',
dataRow: 'Row',
dataCell: 'Cell',
dataHeader: 'Header cell'
}
}
},
data:
'<p>Right-click a cell, then Insert › Insert after: a cell and a header cell are offered, since they are ' +
'allowed only in a row. In the paragraph, they are not.</p>' +
'<div class="dataTable">' +
'<div class="dataRow"><div class="dataHeader">Element</div><div class="dataHeader">Type</div><div class="dataHeader">Holds</div></div>' +
'<div class="dataRow"><div class="dataCell">paragraph</div><div class="dataCell">block</div><div class="dataCell">text</div></div>' +
'<div class="dataRow"><div class="dataCell">note</div><div class="dataCell">container</div><div class="dataCell">blocks</div></div>' +
'</div>'
} satisfies Demo;dataTable.json
{
"dataTable": {
"type": "container"
},
"dataRow": {
"type": "container",
"allowIn": ["dataTable"]
},
"dataCell": {
"type": "block",
"allowIn": ["dataRow"]
},
"dataHeader": {
"type": "block",
"allowIn": ["dataRow"]
}
}The model of the first rows:
<dataTable>
<dataRow><dataHeader>Element</dataHeader><dataHeader>Type</dataHeader></dataRow>
<dataRow><dataCell>paragraph</dataCell><dataCell>block</dataCell></dataRow>
</dataTable>A cell is a block, so it holds text and inline elements. No cell can be outside a row, and no row outside a table. (A container still holds what CKEditor's containers hold, so a table accepts other blocks too, as a block quote does. allowIn restricts where an element goes, not what a container accepts.)
A gallery: several inline containers in a block
isInline is about the layout: an inline container sits in a line with the others. Inline elements are allowed only in blocks, so the row of figures is a block, the gallery, which holds several of them. Each figure is a container that holds a photo and a caption, and is not typable by itself.
The editor
import { Essentials, Paragraph, SourceEditing } from 'ckeditor5';
import {
ElementBreadcrumbs,
ElementContextMenu,
ElementSchema,
ElementSelectable,
ElementQuickInsert,
insertIcon
} from 'ckeditor5-elements';
import gallery from './shared/gallery.json';
import type { Demo } from './shared/demo';
const figure = (caption: string): string =>
`<div class="figure"><div class="photo"></div><div class="caption">${caption}</div></div>`;
export default {
config: {
plugins: [
Essentials,
SourceEditing,
Paragraph,
ElementSchema,
// Selects the `empty` and `container` elements of the schema as a whole.
ElementSelectable,
// Lists what the schema allows at a position, in the context menu.
ElementQuickInsert,
ElementContextMenu,
// Shows the path to the cursor: what holds what.
ElementBreadcrumbs
],
// `-` starts a new toolbar line; it requires `shouldNotGroupWhenFull`.
toolbar: { items: ['undo', 'redo', 'sourceEditing', '-', 'breadcrumbs'], shouldNotGroupWhenFull: true },
elementSchema: gallery,
// The elements are `div` and `span` in the data, and in the editor.
elementSchemaConverters: true,
// Right-click an element: Insert › Insert before / after lists what the schema allows there.
elementContextMenu: (element, editor) => [
{
title: 'Insert',
icon: insertIcon,
items: [editor.commands.get('quickInsertBefore'), editor.commands.get('quickInsertAfter')]
}
],
elementQuickInsert: {
labels: { paragraph: 'Paragraph', gallery: 'Gallery', figure: 'Figure', photo: 'Photo', caption: 'Caption' }
}
},
data:
'<p>A gallery is a block, so it holds inline figures, several in a line. Each figure is a container: a photo, then ' +
'a caption. Right-click the caption, then Insert › Insert after.</p>' +
`<div class="gallery">${figure('Morning')}${figure('Noon')}${figure('Evening')}</div>`
} satisfies Demo;gallery.json
{
"gallery": { "type": "block" },
"figure": { "type": "container", "isInline": true, "allowIn": ["gallery"], "editingTag": "figure" },
"photo": { "type": "empty", "allowIn": ["figure"] },
"caption": { "type": "block", "allowIn": ["figure"], "editingTag": "figcaption" }
}<gallery>
<figure><photo></photo><caption>Morning</caption></figure>
<figure><photo></photo><caption>Noon</caption></figure>
</gallery>photoisempty: an object that is selected as a whole, with no content. It is block-level, so it is a block object in the figure.captionis ablockthat is allowed only in a figure.- The figure is allowed only in a gallery. Without
allowIn, it would be allowed in every block, in a paragraph too.
Inline elements: a mention and an abbreviation
An empty inline element is like an inline image: an object in a line of text. A block inline element is an element in a line of text that holds text, as a bold or a link, with attributes of its own.
The editor
import { Essentials, Paragraph, SourceEditing } from 'ckeditor5';
import {
ElementBreadcrumbs,
ElementContextMenu,
ElementSchema,
ElementSelectable,
ElementQuickInsert,
insertIcon
} from 'ckeditor5-elements';
import marks from './shared/inlineMarks.json';
import type { Demo } from './shared/demo';
export default {
config: {
plugins: [
Essentials,
SourceEditing,
Paragraph,
ElementSchema,
// Selects the `empty` and `container` elements of the schema as a whole.
ElementSelectable,
// Lists what the schema allows at a position, in the context menu.
ElementQuickInsert,
ElementContextMenu,
// Shows the path to the cursor: what holds what.
ElementBreadcrumbs
],
// `-` starts a new toolbar line; it requires `shouldNotGroupWhenFull`.
toolbar: { items: ['undo', 'redo', 'sourceEditing', '-', 'breadcrumbs'], shouldNotGroupWhenFull: true },
elementSchema: marks,
// The elements are `div` and `span` in the data, and in the editor.
elementSchemaConverters: true,
// Right-click an element: Insert › Insert before / after lists what the schema allows there.
elementContextMenu: (element, editor) => [
{
title: 'Insert',
icon: insertIcon,
items: [editor.commands.get('quickInsertBefore'), editor.commands.get('quickInsertAfter')]
}
],
elementQuickInsert: { labels: { paragraph: 'Paragraph', mention: 'Mention', abbr: 'Abbreviation' } }
},
data:
'<p>Ask <span class="mention" data-user="ada"></span> about the <span class="abbr" data-title="Content Management ' +
'System">CMS</span>: it is an inline element with text, and an attribute of its own. Press Alt+Enter after ' +
'a word to insert a mention, which is an empty inline element, or an abbreviation, and type in it.</p>' +
'<p>Put the cursor in the abbreviation: the breadcrumbs show p › span.abbr, as for any element.</p>'
} satisfies Demo;inlineMarks.json
{
"mention": {
"type": "empty",
"isInline": true,
"allowAttributes": ["user"]
},
"abbr": {
"type": "block",
"isInline": true,
"allowAttributes": ["title"]
}
}<paragraph>Ask <mention user="ada"></mention> about the <abbr title="Content Management System">CMS</abbr></paragraph>Both are allowed where text is, so in the paragraph, which is the element of another plugin. Insert an abbreviation with quick insert, and type: the text goes in it, as in any empty inline element with text (see Typing in an empty inline element).