Skip to content

Alignment Plugin

The AlignmentPlugin adds text alignment support for paragraphs, headings, and other alignable block types. It uses logical alignment values (start/end) instead of physical values (left/right) for correct behavior in both LTR and RTL text directions.

Text alignment options

import { AlignmentPlugin } from '@notectl/core/plugins/alignment';
new AlignmentPlugin()
// or restrict alignments:
new AlignmentPlugin({ alignments: ['start', 'center', 'end'] })
interface AlignmentConfig {
/** Enabled alignment options. Default: ['start', 'center', 'end', 'justify'] */
readonly alignments: readonly BlockAlignment[];
/** Block types that support alignment. Default: ['paragraph', 'heading', 'title', 'subtitle', 'table_cell', 'image'] */
readonly alignableTypes: readonly string[];
/** Per-type default alignment. E.g. { image: 'center' } */
readonly defaults: Readonly<Record<string, BlockAlignment>>;
/** Your own CSS class per alignment in HTML content. See "Custom CSS Classes". */
readonly styleClasses?: StyleClassNames<BlockAlignment>;
/** Custom locale strings. */
readonly locale?: AlignmentLocale;
}
type BlockAlignment = 'start' | 'center' | 'end' | 'justify';
type StyleClassNames<K> = Readonly<Partial<Record<K, string>>>;

The plugin uses CSS logical values instead of physical directions:

Logical Value In LTR In RTL
start Left-aligned Right-aligned
end Right-aligned Left-aligned
center Centered Centered
justify Justified Justified

This ensures alignment works correctly regardless of text direction. When combined with the TextDirectionPlugin, alignment automatically adapts to the block’s direction.

new AlignmentPlugin({
alignments: ['start', 'center', 'end'],
})
new AlignmentPlugin({
alignableTypes: ['paragraph', 'heading', 'blockquote'],
})

Available from notectl 2.4.0.

By default, class-based HTML export writes alignment as notectl-align-center and similar classes. If your application already has classes for alignment, for example from a CMS, a design system or a previous editor, configure them once with styleClasses. Colors, highlights, font sizes and fonts have the same option; see Custom CSS Classes for the whole picture.

import { AlignmentPlugin } from '@notectl/core/plugins/alignment';
new AlignmentPlugin({
styleClasses: {
center: 'align-center',
end: 'align-end',
justify: 'align-justify',
},
});
// With the full preset:
createFullPreset({ alignment: { styleClasses: { center: 'align-center' } } });

Every HTML boundary of the editor then uses your classes:

const { html, css, styleMap } = await editor.getContentHTML({
cssMode: 'classes', includeBlockIds: false,
});
// html: <p class="align-center">Centered text</p>
// css: .align-center { text-align: center; }
// styleMap: Map { 'align-center' => 'text-align: center' }
await editor.setContentHTML(html); // no styleMap needed: the alignment is restored
  • Export (getContentHTML({ cssMode: 'classes' })) writes your class instead of notectl-align-*. The returned css and styleMap contain the same class names as html, so the result renders on its own and re-imports anywhere through the styleMap.
  • Import and paste recognize your classes directly, including paragraphs, headings, images and table cells. A pasted paragraph’s explicit alignment applies to the destination when it supports alignment, preserving its block type and unrelated attributes such as text direction. Unformatted inline paste keeps the destination’s formatting. Content with notectl’s default classes still imports and is written with your classes on the next export, which makes switching an existing store over a matter of re-saving.
  • The document stays semantic. The editor stores align: 'center', never a class name. styleClasses is initialization configuration. Recreating the editor with another mapping changes the exported names without changing the semantic alignment stored in your JSON document.
  • Alignments without a class keep notectl’s default names, so you can map only the ones your stylesheet knows.
  • Inline export is unchanged. The default getContentHTML() keeps writing style="text-align: …", so self-contained HTML (for example for emails) works as before.

start is the default alignment for text blocks and normally needs no alignment markup. An explicit start still exports when it overrides an aligned container or a different block default, such as a centered image. Map start when your stylesheet needs a named class, for example because your content area is justified by default:

new AlignmentPlugin({
styleClasses: { start: 'align-start', center: 'align-center', end: 'align-end' },
});

With a start class, start-aligned blocks outside an aligned container get that class, whether they were aligned to start explicitly or never aligned at all. Setting a centered block back to start replaces align-center with align-start.

Inside an aligned container, such as a centered table cell, blocks without their own alignment inherit the container’s alignment in both the editor and the exported HTML. An explicit start, whether imported or set with alignStart, overrides the container and keeps its class on export. Without a configured start class, export writes notectl-align-start in class mode or an inline text-align: start in inline mode so that the override also survives re-import.

<td class="… align-center">
<p>Centered by the cell</p>
<p class="align-start">Explicitly start-aligned</p>
</td>

Alignment in notectl is logical: start and end follow the text direction. Write the CSS for your classes with logical values, as notectl’s generated CSS does:

.align-start { text-align: start; } /* left in LTR, right in RTL */
.align-center { text-align: center; }
.align-end { text-align: end; } /* right in LTR, left in RTL */
.align-justify { text-align: justify; }

A class with physical CSS such as .align-left { text-align: left; } matches start only in left-to-right text. If you map start to it, start-aligned right-to-left blocks render on the wrong side. For the same reason the keys are always logical: { left: 'align-left' } is rejected with a hint to use start.

The class goes on the element that carries the alignment in exported HTML:

Block Element with the class
Paragraph, heading, title, subtitle The block element itself (<p>, <h2>, …)
Image The <figure> wrapper around the <img>
Video The <figure> of the exported video
Table cell The <td>; import also reads <th>

Images and videos are centered by default. One aligned to the start is therefore exported with the start class, or with notectl-align-start when start has no class, so that importing the HTML restores it. Videos are aligned with the VideoPlugin’s own controls and use the same classes.

The classes describe your exported content. They do not change how the editor renders: inside its Shadow DOM the editor applies alignment itself, CSP-safe and without your stylesheet. Load your stylesheet, or the returned css, where you display the exported HTML. See Using Your Own Class Names for a complete example.

The editor checks styleClasses when it initializes. An invalid configuration makes createEditor() (or init()) reject with a TypeError that names the problem and the fix:

  • Keys are start, center, end and justify.
  • Each value is one CSS class name of letters, digits, - and _ that does not start with a digit. Selectors that need escaping (md:text-center) and lists of classes ('a b') are not supported.
  • The prefix notectl- is reserved for the classes notectl generates.
  • Each alignment needs its own class; otherwise import could not tell them apart.

The Custom CSS Classes guide describes how import resolves conflicting classes and inline styles.

Command Description Returns
alignStart Align text to start (left in LTR, right in RTL) boolean
alignCenter Center text boolean
alignEnd Align text to end (right in LTR, left in RTL) boolean
alignJustify Justify text boolean
editor.executeCommand('alignCenter');
editor.executeCommand('alignStart');
Shortcut Action
Ctrl+Shift+L / Cmd+Shift+L Align start
Ctrl+Shift+E / Cmd+Shift+E Align center
Ctrl+Shift+R / Cmd+Shift+R Align end
Ctrl+Shift+J / Cmd+Shift+J Justify

The alignment plugin renders as a dropdown button with alignment icons. The currently active alignment is highlighted. Only alignments listed in the alignments config appear in the dropdown.

The plugin registers transaction middleware that preserves the align attribute when a block’s type changes (e.g., paragraph to heading). This ensures alignment survives block type transformations.

The plugin patches existing node specs to add an align attribute:

Attribute Type Default Exported As
align string 'start' style="text-align: center", or in class mode class="notectl-align-center" / your class

When alignment is 'start', the export normally omits alignment markup. It still writes an alignment when the block’s own default differs (images are centered), an explicit start overrides an aligned container, or start has a custom class outside an aligned container. Blocks without their own alignment continue to inherit aligned containers.