Skip to content

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.

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.

Click to try it out
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.

OptionTypeDefaultDescription
typesstring[]['paragraph', 'heading']Node types that support line height
lineHeightsstring[]['1', '1.15', '1.25', '1.5', '2']Allowed line height values. Values not in this list are rejected by both setLineHeight() and renderHTML.
defaultLineHeightstring | nullnullDefault 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',
})

Unlike TextColor, FontFamily, and FontSize (which accept any value), LineHeight validates values against the configured lineHeights list:

  • setLineHeight() returns false if the value is not in the list
  • renderHTML returns null for 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).

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 only

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 chaining
editor.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.

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.

LineHeight does not register any keyboard shortcuts.

LineHeight does not register any input rules.

LineHeight registers a dropdown with dynamicLabel in the toolbar.

DropdownIconGroupPriorityDisplay mode
lineHeightlineSpacingtextStyle50text

The dropdown contains 6 items: 5 line height values + 1 “Default” reset button.

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.

Each value in the lineHeights array becomes a dropdown item:

PropertyValue
namelineHeight-{value} (e.g., lineHeight-1.5)
commandsetLineHeight
commandArgs[value]
isActiveArray: checks all configured types (paragraph, heading)
iconlineSpacing
labelValue string (e.g., 1.5)
priority200 - 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.

The dropdown always includes a “Default” reset button at the end:

PropertyValue
nameunsetLineHeight
commandunsetLineHeight
iconlineSpacing
labelDefault

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>

LineHeight does not depend on or include TextStyle. It works independently by adding attributes to the configured node types directly.

Both commands operate on all configured types simultaneously:

  • setLineHeight validates the value, then calls updateAttributes(type, { lineHeight }) on each type. Returns true if at least one type was updated.
  • unsetLineHeight calls resetAttributes(type, 'lineHeight') on each type to reset to the default value.

This is the same pattern used by TextAlign.

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.

import { LineHeight } from '@domternal/core';
import type { LineHeightOptions } from '@domternal/core';
ExportTypeDescription
LineHeightExtensionThe line height extension
LineHeightOptionsTypeScript typeOptions for LineHeight.configure()

@domternal/core - LineHeight.ts