

Marks annotate ranges of text: bold, links, colors, comments, mentions. A mark has a type and optional serializable attrs. Plugins decide how marks render.
const underlinePlugin = defineNgsHeadlessEditorPlugin({
id: 'underline',
marks: [{ type: 'underline', tagName: 'u' }],
commands: [toggleUnderline],
keymap: [{ key: 'Mod-u', command: toggleUnderline }]
});
const mentionMark: NgsHeadlessEditorMarkDefinition = {
type: 'mention',
tagName: 'span',
applyAttributes: (element, mark) => {
element.classList.add('mention');
element.dataset['userId'] = String(mark.attrs?.['userId'] ?? '');
},
readAttributes: element => ({ userId: element.dataset['userId'] ?? '' })
};| Member | Description |
|---|---|
type | Unique mark type stored in the document. |
tagName | Element used to wrap marked text. |
parseTags | Extra tag names recognized when browser-owned DOM (composition, drop) is read back. |
applyAttributes(element, mark) | Writes mark attributes to the element. Validate values here. |
readAttributes(element) | Reads attributes back from the DOM. |
A run with several marks is rendered as nested wrappers, one per mark, in the order of mark types. Each wrapper carries data-ngs-headless-editor-mark.
| Method | With a selection | At a caret |
|---|---|---|
toggleMark(type, attrs?) | Adds the mark to the range, or removes it if the whole range already has it. | Toggles the mark for the next typed text. |
setMark(type, attrs?) | Applies or replaces the mark and its attributes. | Arms the mark for the next typed text. |
unsetMark(type) | Removes the mark from the range. | Disarms the mark for the next typed text. |
isMarkActive(type) | True when the whole range has the mark. | True when the next typed text will have it. |
getActiveMark(type) | The mark with its attributes at the selection, for example the current link href. | |
By default, text typed at a caret inherits the marks of the text before it. Toggling a mark at a caret stores an explicit set of marks for the next insertion, exposed as the storedMarks signal. Stored marks are dropped when the caret moves, after text is inserted, and on undo or redo. Toggling bold off at the end of bold text therefore continues in plain text.
editor.toggleMark('bold'); // caret: arms bold
editor.isMarkActive('bold'); // true, toolbar shows Bold pressed
editor.storedMarks(); // [{ type: 'bold' }]
editor.insertText('Bold text'); // inserted with bold, stored marks cleared Formatting commands work wherever text is edited: in the document and in nested editors such as table cells. A nested editor registers itself as the editor's inlineTarget while it has focus; toggleMark(), setMark(), unsetMark(), isMarkActive(), getActiveMark(), storedMarks, selection, and insertText() of the document editor then act on it. Commands and toolbars written against the document editor need no changes.
canApplyMark(type) in a command's enabled predicate, so controls disable themselves where the mark is not allowed or the editor is read-only.canEditBlocks() for block commands; it is false while a nested editor is active.nested: false on a mark definition to keep it out of nested editors.tableEditorPlugin({ formatting: ['bold'] }).const toggleUnderline: NgsHeadlessEditorCommand = {
id: 'toggle-underline',
execute: editor => editor.toggleMark('underline'),
enabled: editor => editor.canApplyMark('underline'), // false in cells without underline
active: editor => editor.isMarkActive('underline')
};
const toggleHeading: NgsHeadlessEditorCommand = {
id: 'toggle-heading',
execute: editor => editor.toggleBlock('heading'),
enabled: editor => editor.canEditBlocks(), // false inside a table cell
active: editor => editor.isBlockActive('heading')
};
editor.inlineTarget(); // nested editor that receives formatting, or nullcolorEditorPlugin() adds the textColor and backgroundColor marks and four commands. Values are checked with normalizeNgsHeadlessEditorColor(), which accepts hex, named, functional (rgb, hsl, oklch, color), and var(--token) colors and rejects anything that could escape a style declaration.
provideNgsHeadlessEditor(
withHeadlessEditorPlugin(basicTextEditorPlugin()),
withHeadlessEditorPlugin(colorEditorPlugin())
);
editor.execute(NGS_HEADLESS_EDITOR_SET_TEXT_COLOR, 'var(--ngs-color-danger)');
editor.execute(NGS_HEADLESS_EDITOR_SET_BACKGROUND_COLOR, '#fef08a');
editor.getActiveMark(NGS_HEADLESS_EDITOR_TEXT_COLOR_MARK)?.attrs?.['color'];
normalizeNgsHeadlessEditorColor('red; display: none'); // null Mark attributes come from documents that may be stored or edited elsewhere. Validate them in applyAttributes before writing to the DOM, for example accept only http and https URLs for links, and validate again when you serialize to HTML.