Line Height
LineHeight adds line height styling to paragraphs and headings. It provides a dropdown with 5 default values, setLineHeight and unsetLineHeight commands, a dynamic label that shows the current line height, and validation that rejects values not in the configured list. Unlike TextColor or FontFamily, LineHeight is a block-level attribute applied directly to node types, not a TextStyle mark attribute.
Not included in StarterKit. Add it separately.
Live Playground
Section titled “Live Playground”Click in a paragraph and pick a line height from the dropdown. The dropdown label shows the active line height. The second paragraph has double spacing for comparison.
With the default theme. The toolbar shows a line height dropdown with a dynamic label and 5 height options plus a reset button.
Vanilla JS preview - Angular components produce the same output
Vanilla JS preview - React components produce the same output
Plain editor without the theme. The buttons above apply line heights programmatically.
import { Editor, Document, Paragraph, Text, LineHeight } from '@domternal/core';import '@domternal/theme';
const editor = new Editor({ element: document.getElementById('editor')!, extensions: [Document, Paragraph, Text, LineHeight], content: '<p>Select a paragraph and pick a line height from the toolbar dropdown.</p>',});With the full theme and ToolbarController, LineHeight renders as a dropdown with a dynamic label that shows the current line height value.
import { Component, signal } from '@angular/core';import { DomternalEditorComponent, DomternalToolbarComponent } from '@domternal/angular';import { Editor, Document, Paragraph, Text, LineHeight } from '@domternal/core';
@Component({ selector: 'app-editor', imports: [DomternalEditorComponent, DomternalToolbarComponent], templateUrl: './editor.html',})export class EditorComponent { editor = signal<Editor | null>(null); extensions = [Document, Paragraph, Text, LineHeight]; content = '<p>Select a paragraph and pick a line height from the toolbar dropdown.</p>';}@if (editor(); as ed) { <domternal-toolbar [editor]="ed" />}<domternal-editor [extensions]="extensions" [content]="content" (editorCreated)="editor.set($event)"/>The Angular toolbar auto-renders the line height dropdown with a dynamic label and active state highlighting.
import { Domternal } from '@domternal/react';import { Document, Paragraph, Text, LineHeight } from '@domternal/core';
export default function Editor() { return ( <Domternal extensions={[Document, Paragraph, Text, LineHeight]} content="<p>Select a paragraph and pick a line height from the toolbar dropdown.</p>" > <Domternal.Toolbar /> <Domternal.Content /> </Domternal> );}import { Editor, Document, Paragraph, Text, LineHeight } from '@domternal/core';
const editor = new Editor({ element: document.getElementById('editor')!, extensions: [Document, Paragraph, Text, LineHeight],});
// Set line height on the current blockeditor.commands.setLineHeight('1.5');
// Reset to defaulteditor.commands.unsetLineHeight();
// Check if a specific line height is activeeditor.isActive('paragraph', { lineHeight: '1.5' }); // true/falseLineHeight works identically without a theme. Build your own line height picker UI and call the commands programmatically.
Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
types | string[] | ['paragraph', 'heading'] | Node types that support line height |
lineHeights | string[] | ['1', '1.15', '1.25', '1.5', '2'] | Allowed line height values. Values not in this list are rejected by both setLineHeight() and renderHTML. |
defaultLineHeight | string | null | null | Default line height value. Not rendered to HTML when active. |
LineHeight.configure({ types: ['paragraph', 'heading'], lineHeights: ['1', '1.25', '1.5', '1.75', '2', '2.5'], defaultLineHeight: '1.5',})Validation
Section titled “Validation”Unlike TextColor, FontFamily, and FontSize (which accept any value), LineHeight validates values against the configured lineHeights list:
setLineHeight()returnsfalseif the value is not in the listrenderHTMLreturnsnullfor values not in the list (prevents rendering)- Pasted HTML with disallowed values is silently dropped
If lineHeights is empty, all values are accepted (no validation).
Empty list
Section titled “Empty list”Passing an empty lineHeights array disables the toolbar dropdown entirely. No toolbar items are registered, but the commands still work and accept any value.
LineHeight.configure({ lineHeights: [] }) // no toolbar, no validation, commands onlyCommands
Section titled “Commands”setLineHeight
Section titled “setLineHeight”Sets the line height on all configured node types at the current selection. Returns false if the value is not in the lineHeights list.
editor.commands.setLineHeight('1.5');
// With chainingeditor.chain().focus().setLineHeight('2').run();Under the hood, this calls updateAttributes(type, { lineHeight }) on each configured type (paragraph, heading). Returns true if at least one type was updated.
unsetLineHeight
Section titled “unsetLineHeight”Resets the line height to the default value on all configured node types at the current selection.
editor.commands.unsetLineHeight();This calls resetAttributes(type, 'lineHeight') on each configured type.
Keyboard shortcuts
Section titled “Keyboard shortcuts”LineHeight does not register any keyboard shortcuts.
Input rules
Section titled “Input rules”LineHeight does not register any input rules.
Toolbar items
Section titled “Toolbar items”LineHeight registers a dropdown with dynamicLabel in the toolbar.
| Dropdown | Icon | Group | Priority | Display mode |
|---|---|---|---|---|
lineHeight | lineSpacing | textStyle | 50 | text |
The dropdown contains 6 items: 5 line height values + 1 “Default” reset button.
Dynamic label
Section titled “Dynamic label”The dropdown trigger uses dynamicLabel mode. It shows the current line height value when an item is active, and falls back to the lineSpacing icon when no line height is set.
Line height items
Section titled “Line height items”Each value in the lineHeights array becomes a dropdown item:
| Property | Value |
|---|---|
name | lineHeight-{value} (e.g., lineHeight-1.5) |
command | setLineHeight |
commandArgs | [value] |
isActive | Array: checks all configured types (paragraph, heading) |
icon | lineSpacing |
label | Value string (e.g., 1.5) |
priority | 200 - index (first value = 200, last = 196) |
The isActive property uses an array pattern. For the default types, each item checks:
isActive: [ { name: 'paragraph', attributes: { lineHeight: '1.5' } }, { name: 'heading', attributes: { lineHeight: '1.5' } },]This means the item shows as active if any of the configured node types at the cursor has the matching line height.
Reset button
Section titled “Reset button”The dropdown always includes a “Default” reset button at the end:
| Property | Value |
|---|---|
name | unsetLineHeight |
command | unsetLineHeight |
icon | lineSpacing |
label | Default |
How it works
Section titled “How it works”Block-level attribute (not a mark)
Section titled “Block-level attribute (not a mark)”LineHeight is fundamentally different from TextColor, FontFamily, and FontSize. Those extensions inject attributes into the TextStyle mark (an inline <span>). LineHeight injects attributes directly into block nodes (paragraph, heading) using addGlobalAttributes():
addGlobalAttributes() { return [{ types: this.options.types, // ['paragraph', 'heading'] attributes: { lineHeight: { default: this.options.defaultLineHeight, parseHTML: (element) => element.style.lineHeight || this.options.defaultLineHeight, renderHTML: (attributes) => { if (!attributes.lineHeight || attributes.lineHeight === this.options.defaultLineHeight) return null; if (this.options.lineHeights.length > 0 && !this.options.lineHeights.includes(attributes.lineHeight)) return null; return { style: `line-height: ${attributes.lineHeight}` }; }, }, }, }];}The style attribute is added to the block element itself, not a wrapper span:
<!-- Line height on the paragraph element --><p style="line-height: 2">Double-spaced paragraph</p>
<!-- Default - no style attribute --><p>Normal paragraph</p>No TextStyle dependency
Section titled “No TextStyle dependency”LineHeight does not depend on or include TextStyle. It works independently by adding attributes to the configured node types directly.
Command implementation
Section titled “Command implementation”Both commands operate on all configured types simultaneously:
setLineHeightvalidates the value, then callsupdateAttributes(type, { lineHeight })on each type. Returnstrueif at least one type was updated.unsetLineHeightcallsresetAttributes(type, 'lineHeight')on each type to reset to the default value.
This is the same pattern used by TextAlign.
Active state resolution
Section titled “Active state resolution”LineHeight’s isActive uses an array of objects to check multiple node types:
isActive: types.map((t) => ({ name: t, attributes: { lineHeight: lh } }))The ToolbarController resolves this with .some(), returning true if any node type at the cursor has the matching attribute. This ensures the dropdown correctly highlights the active line height whether the cursor is in a paragraph or a heading.
Exports
Section titled “Exports”import { LineHeight } from '@domternal/core';import type { LineHeightOptions } from '@domternal/core';| Export | Type | Description |
|---|---|---|
LineHeight | Extension | The line height extension |
LineHeightOptions | TypeScript type | Options for LineHeight.configure() |
Source
Section titled “Source”@domternal/core - LineHeight.ts