Text Color
TextColor adds text color styling via the TextStyle carrier mark. It provides a 25-color palette (5x5 grid), setTextColor and unsetTextColor commands, and a toolbar dropdown with color swatches and a color indicator bar. The palette is fully customizable.
Not included in StarterKit. Add it separately.
Requires the TextStyle mark, which is automatically included as a dependency.
Live Playground
Section titled “Live Playground”Select some text and pick a color from the palette dropdown. The indicator bar under the icon shows the active color. Click “Default” to reset to the browser default color.
With the default theme. The toolbar shows a color palette dropdown with 25 swatches and 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 color buttons below apply colors programmatically.
import { Editor, Document, Paragraph, Text, TextColor } from '@domternal/core';import '@domternal/theme';
const editor = new Editor({ element: document.getElementById('editor')!, extensions: [Document, Paragraph, Text, TextColor], content: '<p>Select text and pick a color from the toolbar palette.</p>',});With the full theme and ToolbarController, TextColor renders as a dropdown with a 5x5 color palette grid, a “Default” reset button, and a color indicator bar on the trigger icon.
import { Component, signal } from '@angular/core';import { DomternalEditorComponent, DomternalToolbarComponent } from '@domternal/angular';import { Editor, Document, Paragraph, Text, TextColor } 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, TextColor]; content = '<p>Select text and pick a color from the toolbar palette.</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 color palette dropdown with swatches, active state checkmarks, and a color indicator bar on the trigger icon.
import { Domternal } from '@domternal/react';import { Document, Paragraph, Text, TextColor } from '@domternal/core';
export default function Editor() { return ( <Domternal extensions={[Document, Paragraph, Text, TextColor]} content="<p>Select text and pick a color from the toolbar palette.</p>" > <Domternal.Toolbar /> <Domternal.Content /> </Domternal> );}import { Editor, Document, Paragraph, Text, TextColor } from '@domternal/core';
const editor = new Editor({ element: document.getElementById('editor')!, extensions: [Document, Paragraph, Text, TextColor],});
// Set a specific coloreditor.commands.setTextColor('#e03131');
// Remove color (reset to default)editor.commands.unsetTextColor();
// Check if a specific color is activeeditor.isActive('textStyle', { color: '#e03131' }); // true/falseTextColor works identically without a theme. Build your own color picker UI and call the commands programmatically.
Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
colors | string[] | DEFAULT_TEXT_COLORS (25 colors) | Color values for the palette. Pass a custom array to use specific colors. |
columns | number | 5 | Number of columns in the palette grid layout |
TextColor.configure({ colors: ['#e03131', '#f08c00', '#2f9e44', '#1971c2', '#7048e8'], columns: 5,})Default palette
Section titled “Default palette”The default palette is a 5x5 grid organized by color family (columns) and intensity (rows):
| Row | Description | Colors |
|---|---|---|
| 1 | Neutrals | #000000 #595959 #a6a6a6 #d9d9d9 #ffffff |
| 2 | Pastel tints | #ffc9c9 #fff3bf #b2f2bb #a5d8ff #d0bfff |
| 3 | Vivid / saturated | #e03131 #f08c00 #2f9e44 #1971c2 #7048e8 |
| 4 | Medium shades | #ff6b6b #ffd43b #69db7c #4dabf7 #9775fa |
| 5 | Dark shades | #c92a2a #e67700 #2b8a3e #1864ab #6741d9 |
Columns are: Red, Orange/Yellow, Green, Blue, Purple.
You can import the default palette to extend it:
import { TextColor, DEFAULT_TEXT_COLORS } from '@domternal/core';
TextColor.configure({ colors: [...DEFAULT_TEXT_COLORS, '#ff69b4', '#00ced1'], // add pink and teal})Empty palette
Section titled “Empty palette”Passing an empty colors array disables the toolbar dropdown entirely. No toolbar items are registered.
TextColor.configure({ colors: [] }) // no toolbar, commands onlyCommands
Section titled “Commands”setTextColor
Section titled “setTextColor”Sets the text color on the current selection. Accepts any CSS color value as a string.
editor.commands.setTextColor('#e03131');
// With chainingeditor.chain().focus().setTextColor('#1971c2').run();Under the hood, this calls setMark('textStyle', { color }) to apply the color attribute on the TextStyle carrier mark.
unsetTextColor
Section titled “unsetTextColor”Removes the text color from the current selection, resetting it to the browser default. Also cleans up empty <span> wrappers via removeEmptyTextStyle().
editor.commands.unsetTextColor();The cleanup step is important: if you set a color and then unset it, the color attribute becomes null. If no other TextStyle attributes are active (no font-family, font-size, or background-color), the <span> wrapper is removed entirely to keep the HTML clean.
Keyboard shortcuts
Section titled “Keyboard shortcuts”TextColor does not register any keyboard shortcuts.
Input rules
Section titled “Input rules”TextColor does not register any input rules.
Toolbar items
Section titled “Toolbar items”TextColor registers a dropdown with grid layout in the toolbar.
| Dropdown | Icon | Group | Priority | Layout | Grid columns |
|---|---|---|---|---|---|
textColor | textAUnderline | textStyle | 200 | grid | 5 (configurable) |
The dropdown contains 26 items: 1 reset button + 25 color swatches.
Reset button
Section titled “Reset button”| Item | Command | Icon | Label |
|---|---|---|---|
unsetTextColor | unsetTextColor | prohibit | Default |
The reset button spans the full width of the grid and removes the text color.
Color swatches
Section titled “Color swatches”Each color in the colors array becomes a swatch button:
| Property | Value |
|---|---|
name | textColor-{hex} (e.g., textColor-#e03131) |
command | setTextColor |
commandArgs | [color] |
isActive | { name: 'textStyle', attributes: { color } } |
color | The hex color value (used for swatch rendering) |
Active swatches show a checkmark overlay with a ring border.
Color indicator bar
Section titled “Color indicator bar”The dropdown trigger shows a color indicator bar below the icon. This bar reflects the currently active text color. When no color is active, it shows the defaultIndicatorColor (#000000 / black). The indicator updates as the cursor moves through differently-colored text.
How it works
Section titled “How it works”Carrier mark pattern
Section titled “Carrier mark pattern”TextColor does not define its own mark in the ProseMirror schema. Instead, it injects a color attribute into the existing TextStyle mark using addGlobalAttributes():
addGlobalAttributes() { return [{ types: ['textStyle'], attributes: { color: { default: null, parseHTML: (element) => normalizeColor(element.style.color) || null, renderHTML: (attributes) => attributes.color ? { style: `color: ${attributes.color}` } : null, }, }, }];}This means TextColor, FontFamily, FontSize, and Highlight all share the same <span> wrapper. A span with multiple styles looks like:
<span style="color: #e03131; font-family: Georgia; font-size: 18px">styled text</span>Automatic dependency
Section titled “Automatic dependency”TextColor declares TextStyle as both a dependency (dependencies: ['textStyle']) and includes it via addExtensions(). You do not need to add TextStyle separately.
Color normalization
Section titled “Color normalization”When parsing HTML, browsers convert hex colors to rgb() format in element.style.color. The normalizeColor() helper converts these back to hex:
// Browser returns: "rgb(224, 49, 49)"// normalizeColor converts to: "#e03131"normalizeColor('rgb(224, 49, 49)') // → '#e03131'This normalization is critical for active state detection. Without it, the stored color (#e03131) would never match the parsed color (rgb(224, 49, 49)), and the swatch checkmark would not appear.
The helper extracts RGB values with a regex, converts each to a two-digit hex string, and returns #RRGGBB. Non-RGB values (hex strings, named colors) pass through unchanged. The alpha channel in rgba() is ignored.
Empty mark cleanup
Section titled “Empty mark cleanup”When you call unsetTextColor(), it:
- Sets the
colorattribute tonullviasetMark('textStyle', { color: null }) - Calls
removeEmptyTextStyle()to check if the TextStyle mark has any remaining non-null attributes - If all attributes are
null(no color, no font-family, no font-size, no background-color), removes the<span>wrapper entirely
This two-step process prevents empty <span> elements from accumulating in the document.
For empty selections (collapsed cursor), it checks stored marks instead of document marks, preventing future typed text from getting a meaningless <span> wrapper.
HTML rendering
Section titled “HTML rendering”Colors render as inline styles on <span> elements:
<!-- With color --><span style="color: #e03131">Red text</span>
<!-- No color (default) - no span wrapper -->Plain textWhen the color attribute is null, renderHTML returns null for the style, so no color property is added to the span.
Exports
Section titled “Exports”import { TextColor, DEFAULT_TEXT_COLORS } from '@domternal/core';import type { TextColorOptions } from '@domternal/core';| Export | Type | Description |
|---|---|---|
TextColor | Extension | The text color extension |
DEFAULT_TEXT_COLORS | string[] | The default 25-color palette array |
TextColorOptions | TypeScript type | Options for TextColor.configure() |
Source
Section titled “Source”@domternal/core - TextColor.ts