Skip to content

Breadcrumbs ​

ElementBreadcrumbs shows the element path at the cursor, e.g. aside.note › p, as the breadcrumbs toolbar component.

The path is made of the elements of the editor data (as editor.getData() outputs it), not of the editing view, so it has no editing-only classes or wrappers:

CursorPath
In a list itemul › li
In a table cellfigure.table › table › tbody › tr › td
In an image captionfigure.image › figcaption
On a selected image (or other object)figure.image

Inline elements are part of the path, as any element: a bold of the element schema gives p › strong. CKEditor's own text attributes, such as its bold or a link, are not: they are marks on the text, not elements.

ts
import { ElementBreadcrumbs } from 'ckeditor5-elements';
import 'ckeditor5-elements/index.css';

The path is a list in a nav landmark ("Element path"), and the element with the cursor is marked with aria-current="location", so screen readers announce where the cursor is.

To build the path, the plugin converts the document to the data view. It does that again only when elements change, not when typing or moving the cursor. When converting takes long (in a large document), it happens at most every 250 ms, so the path can lag behind briefly. editor.plugins.get('ElementBreadcrumbs').refresh() updates it right away.

Labels ​

By default, the breadcrumbs are labels. CKEditor's - toolbar item (a line break, with shouldNotGroupWhenFull: true) puts them on their own toolbar line.

Source
ts
import { Bold, Essentials, List, Paragraph, SourceEditing, Table } from 'ckeditor5';
import { ElementBreadcrumbs } from 'ckeditor5-elements';
import { Note } from 'ckeditor5-elements-example';
import type { Demo } from './shared/demo';

export default {
	config: {
		plugins: [Essentials, SourceEditing, Paragraph, Bold, List, Table, Note, ElementBreadcrumbs],
		// `-` (CKEditor's line break) starts a new toolbar line; it requires `shouldNotGroupWhenFull`.
		toolbar: {
			items: [
				'undo',
				'redo',
				'sourceEditing',
				'|',
				'bold',
				'note',
				'bulletedList',
				'insertTable',
				'-',
				'breadcrumbs'
			],
			shouldNotGroupWhenFull: true
		}
		// No `breadcrumbs.menu`: the breadcrumbs are labels.
	},
	data:
		'<aside class="note"><p>Move the cursor here: the path is <code>aside.note › p</code>.</p></aside>' +
		"<p>CKEditor's own <strong>bold</strong> is a mark on the text, not an element: it is not part of the path.</p>" +
		'<ul><li>In a list item: <code>ul › li</code>.</li></ul>' +
		'<figure class="table"><table><tbody><tr><td>In a table cell</td><td>' +
		'<code>figure.table › table › tbody › tr › td</code></td></tr></tbody></table></figure>'
} satisfies Demo;

With a menu ​

The elementBreadcrumbs.menu option makes the breadcrumbs buttons that open a menu of their element. It takes the same items as elementContextMenu, so one callback can serve both. The parts of a structure open the menu of the structure (tbody opens the menu of the table). List items (ul, li) stay labels: lists are attributes of the blocks in them, not elements. The menu is shown by the ElementMenu plugin, which ElementBreadcrumbs loads, as the context menu and quick insert do: it is the same menu as the context menu's. Without the option, the breadcrumbs are labels.

Source
ts
import { Essentials, Paragraph, SourceEditing } from 'ckeditor5';
import {
	ElementBreadcrumbs,
	ElementActions,
	ElementClipboard,
	ElementContextMenu,
	type ElementMenuConfig
} from 'ckeditor5-elements';
import { Note } from 'ckeditor5-elements-example';
import type { Demo } from './shared/demo';

// One menu for both: right-clicking an element, and clicking its breadcrumb.
const menu: ElementMenuConfig = (element, editor) => [
	editor.commands.get('note'),
	editor.commands.get('duplicateElement'),
	'|',
	editor.commands.get('moveElementUp'),
	editor.commands.get('moveElementDown'),
	'|',
	editor.commands.get('copyElement'),
	editor.commands.get('cutElement'),
	editor.commands.get('pasteElementAfter'),
	'|',
	editor.commands.get('deleteElement')
];

export default {
	config: {
		plugins: [
			Essentials,
			SourceEditing,
			Paragraph,
			Note,
			ElementBreadcrumbs,
			ElementContextMenu,
			ElementClipboard,
			ElementActions
		],
		toolbar: {
			items: ['undo', 'redo', 'sourceEditing', '|', 'note', '-', 'breadcrumbs'],
			shouldNotGroupWhenFull: true
		},
		elementContextMenu: menu,
		// With a menu, the breadcrumbs are buttons that open it, with the `ElementMenu` plugin, as the context menu does.
		elementBreadcrumbs: { menu }
	},
	data:
		'<aside class="note"><p>Put the cursor here, then click <code>aside.note</code> in the breadcrumbs: ' +
		'<b>Move down</b> moves the whole note below the next paragraph.</p></aside>' +
		'<p>Clicking <code>p</code> opens the menu of this paragraph.</p>' +
		'<p><b>Duplicate</b> adds an empty block of the same kind below; <b>Delete</b> removes the block.</p>'
} satisfies Demo;

The buttons tell screen readers that they open a menu (aria-haspopup, aria-expanded), and Esc in the menu returns the focus to the button.

Keyboard ​

In the toolbar (Alt+F10), the arrow keys move through the breadcrumbs like through the other toolbar items, so every breadcrumb can be reached: labels too, so their text can be read. Coming from the previous toolbar item, the focus goes to the outermost breadcrumb; from the next one, to the innermost.

Anywhere on the page ​

editor.plugins.get('ElementBreadcrumbs').createView() returns breadcrumbs that follow the cursor, to place anywhere. Here, below the editor:

Source
ts
import { Essentials, Heading, Paragraph, SourceEditing } from 'ckeditor5';
import { ElementBreadcrumbs } from 'ckeditor5-elements';
import { Note } from 'ckeditor5-elements-example';
import type { Demo } from './shared/demo';

export default {
	config: {
		plugins: [Essentials, SourceEditing, Paragraph, Heading, Note, ElementBreadcrumbs],
		toolbar: ['undo', 'redo', 'sourceEditing', '|', 'heading', 'note']
	},
	data: '<h2>A heading</h2><aside class="note"><p>A paragraph in a note.</p></aside>',
	// The breadcrumbs can be placed anywhere: here, below the editor.
	ready(editor, container) {
		const view = editor.plugins.get('ElementBreadcrumbs').createView();
		const below = document.createElement('div');

		view.render();

		if (view.element) {
			below.className = 'editor-demo__below';
			below.append(view.element);
			container.append(below);
		}

		editor.on('destroy', () => {
			view.destroy();
		});
	}
} satisfies Demo;