Skip to content

Block Color

BlockColor adds block-level background and text colors as global attributes. The colors persist across turnIntoBlock transformations (the heading-to-paragraph swap keeps its tint), and integrate with BlockContextMenu’s Colors picker so the user can apply colors via the right-click menu.

This complements NotionColorPicker, which handles inline color (per-character tints via textStyle). Use BlockColor for whole-block tints (e.g. an entire paragraph or list item highlighted).

Not included in StarterKit.

import { Editor, StarterKit, BlockColor } from '@domternal/core';
import '@domternal/theme';
const editor = new Editor({
element: document.getElementById('editor')!,
extensions: [
StarterKit,
BlockColor,
],
});
// Apply a background color to the block at the cursor
editor.chain().focus().setBlockBgColor('blue').run();
BlockColor.configure({
types: DEFAULT_BLOCK_COLOR_TYPES,
bgColors: DEFAULT_BLOCK_COLORS,
textColors: DEFAULT_BLOCK_COLORS,
})
OptionTypeDefaultDescription
typesstring[]DEFAULT_BLOCK_COLOR_TYPESNode types that receive bgColor / textColor global attributes
bgColorsstring[]DEFAULT_BLOCK_COLORSPalette used by setBlockBgColor. Commands reject values outside this list (no-op + returns false) so host apps can curate what’s selectable.
textColorsstring[]DEFAULT_BLOCK_COLORSPalette used by setBlockTextColor
['paragraph', 'heading', 'blockquote', 'bulletList', 'orderedList', 'taskList', 'listItem', 'taskItem']

codeBlock is intentionally excluded - <pre><code> already has its own background that would clash visually. Details-content blocks similarly have their own affordance.

['gray', 'brown', 'orange', 'yellow', 'green', 'blue', 'purple', 'pink', 'red']

Same 9-color palette as NotionColorPicker, so inline and block tints share the same color set. 'default' is implicit - represented by null (no data-* attribute).

The extension adds two global attributes (parse/render on every type in types):

AttributeParsesRendersMeaning
bgColordata-bg-color="<token>"data-bg-color="<token>"Block background color
textColordata-text-color="<token>"data-text-color="<token>"Block text color

The theme stylesheet maps these data attributes to CSS custom properties:

[data-bg-color="blue"] { background-color: var(--dm-block-bg-blue); }
[data-text-color="red"] { color: var(--dm-block-text-red); }
declare module '@domternal/core' {
interface RawCommands {
setBlockBgColor: CommandSpec<[color: string | null]>;
setBlockTextColor: CommandSpec<[color: string | null]>;
unsetBlockColors: CommandSpec;
}
}
CommandSignatureBehavior
setBlockBgColor(color: string | null)Sets bgColor on every block in the selection range that has a type in types. null clears. Rejects values outside bgColors palette (returns false).
setBlockTextColor(color: string | null)Same for textColor, rejects values outside textColors palette
unsetBlockColors()Clears both bgColor and textColor on every block in the selection range
editor.chain().focus().setBlockBgColor('green').run(); // set
editor.chain().focus().setBlockTextColor(null).run(); // clear
editor.chain().focus().unsetBlockColors().run(); // clear both
editor.chain().focus().setBlockBgColor('notacolor').run(); // false - palette guard rejects

When the user changes a block’s type via “Turn into” (e.g. heading -> paragraph), BlockContextMenu’s turnIntoBlock helper transforms attrs through a custom function so global attrs survive. Without this preservation, every block-type change would silently reset the color.

// User has: <p data-bg-color="blue">text</p>
// User clicks "Turn into > Heading 1"
// Result: <h1 data-bg-color="blue">text</h1> <-- color persists

Conflict resolution: stripInlineColorConflicts

Section titled “Conflict resolution: stripInlineColorConflicts”

The extension exports a utility used by BlockContextMenu to ensure that applying a block color removes conflicting inline colors so the new block tint isn’t visually masked:

import { stripInlineColorConflicts } from '@domternal/core';
function stripInlineColorConflicts(
tr: Transaction,
state: EditorState,
from: number,
to: number,
which: 'text' | 'bg' | 'both',
): void;

Strips inline textStyle marks inside [from, to] that carry the inline counterpart of a block-level color attribute. Mutates the transaction in place. 'both' handles the unset case (unsetBlockColors).

When both BlockColor and BlockContextMenu are loaded, the context menu’s Colors section appears automatically for blocks of types in BlockColor.options.types. The Colors row shows two ribbons (background + text) with the full palette plus a “default” swatch (clear).

Toggle off via BlockContextMenu.configure({ blockColorEnabled: false }).

Block colors render only as data attributes; the theme stylesheet picks them up via attribute selectors. No new classes are added directly by this extension.

Defined in @domternal/theme for both light and dark modes:

VariablePurpose
--dm-block-bg-grayBackground for data-bg-color="gray"
--dm-block-bg-brownBackground for data-bg-color="brown"
--dm-block-bg-orange etc.Backgrounds for each palette token
--dm-block-text-grayText color for data-text-color="gray"
--dm-block-text-brown etc.Text colors for each palette token

Override these in your app’s CSS to customize the palette colors.

import {
BlockColor,
DEFAULT_BLOCK_COLORS,
DEFAULT_BLOCK_COLOR_TYPES,
stripInlineColorConflicts,
} from '@domternal/core';
import type { BlockColorOptions } from '@domternal/core';

@domternal/core - BlockColor.ts