Skip to content

Theming

The @domternal/theme package provides ready-made light and dark themes for the editor, toolbar, bubble menu, and all UI components. Every visual property is a CSS custom property, so you can customize anything by overriding a single variable. The same package carries the print stylesheet, so what the document looks like on paper is a theming concern too.

Customize Theming when you need:

  • Brand colors, fonts, or spacing that match a design system
  • Dark mode that follows a custom toggle (not just prefers-color-scheme)
  • Theme overrides scoped to a specific editor instance (multiple editors with different themes on one page)
  • Control over the printed page: which tokens still reach the paper, and where the print partial sits in the cascade (see Printing)

Notes:

  • Domternal exposes 150+ CSS custom properties for fine-grained control without touching the SCSS source
  • Pair with @domternal/theme for the default styling, then override CSS variables in your own stylesheet
Terminal window
pnpm add @domternal/theme

Import the theme in your application entry point:

// JavaScript/TypeScript bundler (Vite, Webpack, esbuild)
import '@domternal/theme';
// SCSS
@use '@domternal/theme';
<!-- HTML link tag -->
<link rel="stylesheet" href="node_modules/@domternal/theme/dist/domternal-theme.css" />

The package exports multiple entry points:

ImportPathDescription
@domternal/themedist/domternal-theme.cssDefault: compiled CSS
@domternal/theme/cssdist/domternal-theme.cssExplicit CSS import
@domternal/theme/scsssrc/index.scssSCSS source (for Sass pipelines)

Light mode is the default. Dark mode is activated by adding a CSS class to the editor or any ancestor element.

ClassBehavior
(none)Light theme (default)
dm-theme-darkDark theme, always
dm-theme-lightLight theme, always (use to force light inside a dark context)
dm-theme-autoFollows the user’s system preference via prefers-color-scheme

.dm-theme-light is not identical to the untouched default. The base tokens on .dm-editor are the implicit light theme; the explicit .dm-theme-light class applies a self-contained mixin that diverges on five properties:

PropertyImplicit defaultUnder .dm-theme-light
--dm-border-color#e5e7eb#e0e0e0
--dm-hoverrgba(0,0,0,0.04)rgba(0,0,0,0.06)
--dm-activergba(0,0,0,0.1)rgba(0,0,0,0.12)
--dm-code-surface#f0f0f0#f5f5f5
--dm-code-colorinherit#d63384

The last one is the visible one: inline code renders pink under .dm-theme-light and inherits the surrounding text color without it.

<!-- Always dark -->
<div class="dm-theme-dark">
<div class="dm-toolbar">...</div>
<div class="dm-editor">...</div>
</div>
<!-- Follows system preference -->
<div class="dm-theme-auto">
<div class="dm-toolbar">...</div>
<div class="dm-editor">...</div>
</div>
<!-- Force light inside a dark page -->
<div class="dm-theme-light">
<div class="dm-toolbar">...</div>
<div class="dm-editor">...</div>
</div>
function toggleTheme() {
document.body.classList.toggle('dm-theme-dark');
}

Or use dm-theme-auto and let the OS handle it.

Override any CSS custom property on .dm-editor, .dm-toolbar, or any parent element:

/* Custom accent color */
.my-editor {
--dm-accent: #e11d48;
--dm-accent-hover: #be123c;
--dm-accent-surface: rgba(225, 29, 72, 0.1);
}
/* Custom editor appearance */
.my-editor .dm-editor {
--dm-editor-bg: #fefce8;
--dm-editor-border-radius: 0;
--dm-editor-shadow: none;
}
/* Custom toolbar */
.my-editor .dm-toolbar {
--dm-toolbar-bg: #fef9c3;
--dm-button-size: 2.25rem;
}

Since CSS custom properties cascade, you can set them on any ancestor and they flow down to all editor components.

All properties are defined on .dm-editor unless noted otherwise. Light values are the defaults on .dm-editor (see the note above for the five tokens .dm-theme-light sets differently). Dark values are applied when dm-theme-dark or dm-theme-auto (in dark mode) is active.

These are the core design tokens that other properties reference via var().

PropertyLightDarkDescription
--dm-bg#ffffff#1e1e1eBase background
--dm-text#1a1a1a#e0e0e0Base text color
--dm-muted#6b7280#9ca3afSecondary/muted text
--dm-surface#f8f9fa#2a2a2aElevated surface background
--dm-border-color#e5e7eb#3a3a3aBorder color
--dm-hoverrgba(0,0,0,0.04)rgba(255,255,255,0.08)Hover state background
--dm-activergba(0,0,0,0.1)rgba(255,255,255,0.15)Active state background
--dm-accent#2563eb#60a5faPrimary accent (blue)
--dm-accent-hover#1d4ed8#93c5fdAccent on hover
--dm-accent-surfacergba(37,99,235,0.1)rgba(96,165,250,0.15)Accent tint background
--dm-focus-colorrgba(66,133,244,0.3)rgba(96,165,250,0.3)Focus ring color
--dm-selectionrgba(66,133,244,0.2)rgba(96,165,250,0.25)Text selection color
--dm-popover-shadow0 12px 28px -8px rgba(0,0,0,0.22), 0 4px 10px -2px rgba(0,0,0,0.12)0 10px 25px rgba(0,0,0,0.4), 0 4px 10px rgba(0,0,0,0.25)Shared drop shadow for every popover surface: floating menu, slash command menu, block context menu, link popover, image popover, math popover
--dm-scrollbar-thumbrgba(0,0,0,0.18)rgba(255,255,255,0.18)Thin-scrollbar thumb color shared by every scrollable popup: the ToC card, emoji suggestion list, slash command menu, block context menu, floating menu and mention suggestion list
--dm-scrollbar-thumb-hoverrgba(0,0,0,0.28)rgba(255,255,255,0.32)Thumb color on hover
--dm-code-surface#f0f0f0#2d2d2dCode background surface
--dm-code-colorinheritinheritCode text color
--dm-color-schemelightdarkFeeds the CSS color-scheme property on .dm-editor, so native form controls, scrollbars and the caret follow the theme

All three of --dm-popover-shadow, --dm-scrollbar-thumb and --dm-scrollbar-thumb-hover default to their light values on .dm-editor, and every consumer falls back to a light-toned value when the token is missing. A custom dark theme that overrides --dm-bg but skips these keeps a light shadow on dark surfaces, and gets near-invisible scrollbars in six popups at once.

PropertyDefaultDescription
--dm-editor-bgvar(--dm-bg)Editor background
--dm-editor-textvar(--dm-text)Editor text color
--dm-editor-font-familySystem font stackFont family
--dm-editor-font-size1remBase font size
--dm-editor-line-height1.6Line height
--dm-editor-padding1remContent padding (any padding shorthand)
--dm-editor-padding-topvar(--dm-editor-padding)Top padding as a single length. Set it whenever --dm-editor-padding uses a multi-value shorthand, because a shorthand cannot participate in the internal padding-top calc().
--dm-editor-padding-top-extra0.5remExtra length added on top of the resolved top padding (gives the first line breathing room). The effective top padding is calc(padding-top + padding-top-extra).
--dm-editor-border1px solid var(--dm-border-color)Border
--dm-editor-border-radius0.75remCorner radius
--dm-editor-focus-ringnoneFocus ring (override for custom, e.g., 0 0 0 2px var(--dm-focus-color))
--dm-editor-shadow0 1px 3px rgba(0,0,0,0.04), 0 1px 2px rgba(0,0,0,0.02)Box shadow
PropertyLightDarkDescription
--dm-z-handle25(same)Z-index for BlockHandle. Sits below popovers so opening a menu overlays the handle.
--dm-z-popover50(same)Z-index for the floating menu, the slash command menu, the block context menu and the math edit popover. Other floating surfaces in @domternal/theme hardcode their own value and ignore this token: bubble menu 50, toolbar dropdown panel 60 (deliberately above the bubble menu, like the link popover: at parity the bubble menu painted over it and swallowed clicks on its items), table controls dropdown 50 (table handles 10), link popover 60, image popover 60, Notion color picker 60, mention and emoji suggestion lists 100. The floating ToC uses its own --dm-toc-z-index (10). Note that the math popover falls back to 60, not 50, when the token is unset.
--dm-block-handle-gutter3rem(same)Gutter reserved on .ProseMirror when BlockHandle is active
--dm-block-handle-left0.25rem(same)Horizontal offset of the handle from the editor’s left edge. Negative values place it in the surrounding whitespace and require .dm-editor--has-block-handle { overflow: visible }. Notion mode replaces the constant with a calc derived from the reading measure, so the handle tracks a centred column.
--dm-block-inline-padding0.375rem(same)Inline padding pre-applied to colorable blocks. Paragraphs and headings get it on both sides via a zero-specificity :where() rule; blockquotes get it as their right padding (left stays 0.8em for the border bar); ul/ol get it as their reading-end padding. Pre-applying it means toggling a block background color only paints, it never shifts the text. Changing it moves content in all three places at once.
--dm-block-children-indentcalc(1.5 * var(--dm-editor-font-size, 1rem))(same)Indent for children-zone blocks under a list/task item label. Scales with the editor font size (was a fixed 1.5rem before v0.9.1).
--dm-block-selected-halorgba(112, 207, 248, 0.25)rgba(112, 207, 248, 0.35)Halo behind the block a drag handle is targeting
--dm-block-context-active-bgrgba(55, 53, 47, 0.06)rgba(255, 255, 255, 0.05)Tint applied to the target block while its context menu is open

Geometry tokens (--dm-block-handle-gutter, --dm-block-handle-left, --dm-block-inline-padding) are deliberately not overridden by the dark theme: geometry is identical in light and dark.

PropertyDefaultDescription
--dm-placeholder-colorvar(--dm-muted)Placeholder text color
PropertyDefaultDescription
--dm-link-colorvar(--dm-accent)Link color
--dm-link-hover-colorvar(--dm-accent-hover)Link hover color
PropertyDefaultDescription
--dm-code-bgvar(--dm-code-surface)Inline code background
--dm-code-textvar(--dm-code-color)Inline code text color
--dm-code-font"SF Mono", "Fira Code", Consolas, "Liberation Mono", Menlo, monospaceMonospace font
--dm-code-border-radius0.25remInline code corner radius
PropertyDefaultDescription
--dm-code-block-bgvar(--dm-code-surface)Code block background
--dm-code-block-textvar(--dm-text)Code block text color

Used by Code Block Lowlight for highlight.js token colors.

PropertyLightDarkDescription
--dm-syntax-keyword#c72031#ff7b72Keywords, types
--dm-syntax-entity#6f42c1#d2a8ffFunctions, class names
--dm-syntax-constant#005cc5#79c0ffConstants, numbers
--dm-syntax-string#032f62#a5d6ffStrings, regex
--dm-syntax-variable#d35400#ffa657Variables, built-ins
--dm-syntax-comment#57606a#8b949eComments
--dm-syntax-tag#22863a#7ee787HTML/XML tags
--dm-syntax-addition#22863a#aff5b4Diff additions
--dm-syntax-addition-bg#f0fff4#033a16Diff addition background
--dm-syntax-deletion#b31d28#ffdcd7Diff deletions
--dm-syntax-deletion-bg#ffeef0#67060cDiff deletion background
PropertyLightDarkDescription
--dm-blockquote-border3px solid #6a6a6a3px solid #555555Left border
--dm-blockquote-color#6a6a6a#a0a0a0Text color
PropertyDefaultDescription
--dm-hr-colorvar(--dm-border-color)Rule color
PropertyDefaultDescription
--dm-table-border1px solid var(--dm-border-color)Cell border
--dm-table-header-bgvar(--dm-surface)Header cell background
--dm-table-selected-bgrgba(66, 133, 244, 0.15)Selected cell background

Highlight has no dedicated CSS variable. There are two paths, depending on how the highlight was applied:

  • Hex highlights (the ==text== input rule, the keyboard shortcut, and the hex swatches in the highlight palette) are written as an inline style="background-color: ..." on the textStyle mark, so no theme override can reach them. Change the color used by the shortcut and the input rule with the extension option instead:

    Highlight.configure({ defaultColor: '#fef08a' })

    See Highlight for defaultColor, colors and the default 25-color palette.

  • Named-token highlights (applied through the Notion Color Picker) render as data-bg-color="yellow" and resolve through the --dm-block-bg-* variables. Override those to restyle them: see Named color tokens below.

PropertyDefaultDescription
--dm-mention-bgvar(--dm-accent-surface)Mention background
--dm-mention-colorvar(--dm-accent)Mention text color
--dm-mention-border-radius0.25remMention corner radius
PropertyDefaultDescription
--dm-details-border1px solid var(--dm-border-color)Details border
--dm-details-bgvar(--dm-surface)Summary background
--dm-details-summary-font-weight600Summary text weight
PropertyDefaultDescription
--dm-task-checkbox-left-1.15emHorizontal position of the absolutely-positioned checkbox label, hanging it in the bullet column
--dm-task-checkbox-top0.45emVertical position, tuned to the first text line’s x-height

Both are tuned against the default --dm-editor-line-height. Retune them whenever you override that token, so the checkbox stays aligned with the first label glyph.

Defined on .dm-toolbar:

PropertyDefaultDescription
--dm-toolbar-bgvar(--dm-bg, #ffffff)Toolbar background
--dm-toolbar-bordernoneToolbar border
--dm-toolbar-padding0.375rem 0.5remToolbar padding
--dm-toolbar-gap0.125remGap between items
--dm-toolbar-border-radius0.75rem 0.75rem 0 0Toolbar corner radius

Defined on .dm-toolbar:

PropertyDefaultDescription
--dm-button-size2remButton width and height
--dm-button-border-radius0.375remButton corner radius
--dm-button-colorvar(--dm-text, #374151)Button icon/text color
--dm-button-hover-bgvar(--dm-hover)Button hover background
--dm-button-active-bgvar(--dm-accent-surface)Active button background
--dm-button-active-colorvar(--dm-accent)Active button icon color
--dm-button-disabled-opacity0.35Disabled button opacity

Defined on .dm-toolbar:

PropertyDefaultDescription
--dm-separator-colorvar(--dm-border-color)Separator color
--dm-separator-margin0.375remSeparator vertical margin

Apply the .dm-notion-mode CSS class to the .dm-editor element itself (the theme rule is .dm-editor.dm-notion-mode, so the class does nothing on a parent) to switch into the Notion-style layout: a centered 44rem reading column inside a full-width editor host, no card frame, generous line-height, and the block handle pulled out into the side gutter. Body font-size is unchanged (--dm-editor-font-size stays 1rem), so toggling between modes shifts layout, not text scale.

<div class="dm-editor dm-notion-mode">...</div>

The class is shipped by @domternal/theme and combines with the Block Menu and Table of Contents extensions for the full Notion experience. See the Notion Mode guide for the complete setup.

The class overrides exactly five tokens:

PropertyGlobal defaultIn .dm-notion-modeNotes
--dm-editor-line-height1.61.7Generous reading rhythm
--dm-editor-padding1rem0Zeroed because the page wrapper supplies the white space around the column
--dm-block-handle-gutter3rem0Column reserved on .ProseMirror for BlockHandle. Notion mode reclaims it because the handle sits fully outside the content column.
--dm-notion-column-widthnot set44remThe reading measure. Notion mode reads it twice, as the column width and halved inside the handle offset, so overriding this one property moves both.
--dm-block-handle-left0.25remcalc(...) derived from the measureHorizontal offset of the 40px handle cluster. Walks in to the centred column’s edge and backs off, so the cluster ends 4px before the text at any host width, and follows a docked panel that slides the column.

The Notion Color Picker and Block Color extensions use a 9-color named-token palette. Each token has two CSS variables in the theme (text and background). Override these to customize the palette.

VariablePurpose
--dm-block-text-{gray,brown,orange,yellow,green,blue,purple,pink,red}Per-token TEXT color (used for inline colorToken and block textColor)
--dm-block-bg-{gray,brown,orange,yellow,green,blue,purple,pink,red}Per-token BACKGROUND color (used for inline backgroundColorToken and block bgColor)

The FloatingTocOutline and TableOfContentsBlock extensions are styled with these tokens:

PropertyPurpose
--dm-toc-mid-topSticky top (editor mode, middle state, default 50vh)
--dm-toc-editor-topSticky top (editor mode, frozen state, default 1rem)
--dm-toc-right-offsetRight margin (viewport mode, default 24px)
--dm-toc-card-bg/shadow/radiusExpanded card visual
--dm-toc-tick-{h1-h6}-widthPer-level tick width (h1 widest, h6 narrowest)
--dm-toc-tick-height/radius/gapTick visual

The theme styles these CSS classes. Use them when building custom UI or vanilla JS editors.

ClassDescription
.dm-editorMain editor wrapper. Defines all design tokens. Must have position: relative (set by theme).
.dm-editor .ProseMirrorThe contenteditable area. Inherits editor tokens.
.dm-toolbarToolbar container. Flex layout with grouped buttons.
.dm-toolbar-groupGroup of related toolbar buttons.
.dm-toolbar-buttonIndividual toolbar button.
.dm-toolbar-separatorVertical separator between button groups.
.dm-toolbar-dropdown-panelDropdown panel (headings, font family, colors).
.dm-toolbar-dropdown-itemItem inside a dropdown panel.
ClassDescription
.dm-bubble-menuInline formatting toolbar (appears on text selection). Compact button sizing.
.dm-floating-menuBlock-level menu (appears on empty lines).
.dm-link-popoverURL input popover for links.
.dm-image-popoverURL input popover for images.
.dm-slash-command-menuRoot popup for the / command menu.
.dm-slash-command-itemA single command row (-icon, -label, -description, -shortcut, -text sub-elements).
.dm-slash-command-groupGrouping wrapper, with .dm-slash-command-group-label as its heading.
.dm-slash-command-queryInline decoration on the active /query text in the document.
.dm-slash-command-emptyShown when the query matches nothing.
ClassDescription
.dm-notion-modeOpt-in Notion layout preset, applied to the .dm-editor element itself.

BlockHandle and BlockContextMenu classes (.dm-block-handle, .dm-block-handle-btn, .dm-block-handle-dragging, .dm-block-drop-indicator, .dm-block-context-menu and its -group, -group-label, -item, -item-icon, -item-label children, .dm-block-context-active, .dm-editor--has-block-handle) are listed on Block Controls. Math classes (.dm-math-popover and friends) are listed on Math, ToC classes on Table of Contents, and .dm-notion-color-picker / .dm-ncp-* on Notion Color Picker.

ClassDescription
.mentionInline mention node.
.dm-mention-suggestionMention autocomplete dropdown.
.dm-emoji-pickerEmoji picker panel.
.dm-emoji-suggestionEmoji autocomplete dropdown.
.dm-color-paletteColor grid for text color and highlight pickers.
.dm-image-resizableResizable image wrapper with handles.
.invisible-charInvisible character indicator (paragraph marks, spaces).
ClassDescription
.dm-table-containerWrapper for table handles and content.
.dm-table-col-handleColumn selection handle.
.dm-table-row-handleRow selection handle.
.dm-table-cell-handleCell corner handle (opens cell toolbar).
.dm-table-cell-toolbarFloating toolbar above selected cells.
.dm-table-controls-dropdownDropdown for table operations.
ClassDescription
.ProseMirror-selectednodeOutline on selected atom nodes (images, horizontal rules). Uses --dm-accent.
.ProseMirror-gapcursorAnimated blinking cursor in gap positions.
.selectedCellBackground overlay on selected table cells. Uses --dm-table-selected-bg.
.is-emptyAdded to empty nodes for placeholder display.
.is-editor-emptyAdded to the editor when the document is empty.
.has-focusAdded by the Focus extension to the focused node.
.is-openAdded to open details/accordion elements.
ClassDescription
.dm-printingOn <body> while printDocument() runs. Gates the isolation rules.
.dm-print-rootThe element being printed, .dm-editor by default.
.dm-print-ancestorEvery ancestor of that element up to the document element. Their other children are hidden.

All three are added and removed by the Print extension around one print, and the rules that key on them are documented there under isolation.

@domternal/theme ships a paper stylesheet in _print.scss, and everything in it applies to the reader’s own Ctrl/Cmd+P with no JavaScript in the loop. What it hides, reveals and neutralises is documented with the Print extension, together with the marking classes that extension adds to isolate the document from the surrounding application.

This section is the theme author’s half: where the partial sits in the cascade, and which tokens still reach the page.

@use 'print' is the last partial in index.scss, after themes/_light.scss and themes/_dark.scss and just before the closing prefers-reduced-motion block. The position is load-bearing. @media print adds no specificity of its own, so a print rule and a screen rule written with the same selector are separated by source order alone; importing print last means it wins that tie without having to escalate. A hand-assembled build that puts the partial earlier flips every one of those ties silently, because nothing errors.

The !important declarations that are in the file are aimed at something source order cannot reach: styles the node views write inline on the element (the table handles’ display, the stored column widths and the table’s min-width, the image handles). An inline style beats any rule that is not important, whatever its position.

Two rules are written against body rather than against a descendant selector, because no element is a descendant of itself: the ancestor isolation rules never reach body’s own box, so the page canvas has to be named on its own selector. The unconditional one drops the canvas background on every print. body.dm-printing additionally forces color: #000, gated to the command path, where everything but the document is already hidden and the declaration can reach nothing the host still shows.

The print pass does not touch typography, so these tokens still shape the page: --dm-editor-font-family, --dm-editor-font-size, --dm-editor-line-height, --dm-code-font, the --dm-syntax-* palette and the named --dm-block-text-* / --dm-block-bg-* colours.

These are overridden on paper, so setting the token alone changes nothing there:

TokenWhat print does instead
--dm-editor-bg, --dm-editor-border, --dm-editor-border-radius, --dm-editor-shadowDropped as literal declarations on .dm-editor. The sheet is already the frame.
--dm-editor-textForced to #000. The text colour has to come with the background: a dark theme sets it near-white, and dropping only the dark panel behind it leaves light grey on white paper. Colours an author applied are inline on their own spans and still win.
--dm-editor-padding, --dm-editor-padding-top, --dm-editor-padding-top-extra.ProseMirror padding is zeroed.
--dm-block-handle-gutterZeroed with the rest of that padding. It reserves room for a drag handle that is not printed, and costs about 48px on every page.
the page canvas (body background, whether a token of yours sets it or not)Dropped, always, not only under printDocument(). The document’s text is forced to #000, so a dark canvas left standing prints black on near-black. The rule kills body’s transition first, because a theme toggle animates the background and a running transition outranks !important.

No @page rule ships in the free theme, so paper size, orientation and margins come from the browser’s print dialog. Add your own when you need them fixed:

@page {
size: A4;
margin: 20mm;
}

To change something the print pass sets, write your own @media print block after the theme import, matching its selector so specificity ties and your rule wins on order:

@import '@domternal/theme';
@media print {
/* the theme declares `.dm-editor { color: #000 !important }` */
.dm-editor {
color: #1a1a1a !important;
}
}

While a CSS transition is running, the transitioned value comes from the transition origin of the cascade, which sits above every author declaration, !important included. A print rule that fights an animated property loses for exactly as long as the animation lasts, and a print is a snapshot that can land right inside that window.

There are two cases in the theme. The first is the block context menu. Opening it tints the target block through .dm-block-context-active, which transitions background-color and box-shadow over 0.12s, so a print taken just after the menu opened would carry a fading grey block behind the text however loudly the print rule declared background: none !important. The transition has to stop first, in the same rule:

@media print {
.dm-editor .ProseMirror .dm-block-context-active {
transition: none !important; /* first, or the rest of this block loses */
background: none !important;
border: none !important;
box-shadow: none !important;
}
}

The second is the page canvas. A theme toggle transitions body’s background, so a print taken while that toggle is still running would carry the outgoing canvas onto the sheet behind text the print layer has already forced to black. The print rule zeroes the transition in the same block, before dropping the background:

@media print {
body {
transition: none !important; /* first, or the drop below loses */
background: none !important;
}
}

The same applies to any print rule you write against your own animated UI. Killing the transition is not a nicety, it is the precondition for the declarations underneath it.

You don’t need @domternal/theme to use Domternal. The editor works without any styles. You can build everything from scratch.

At minimum, ProseMirror requires these styles to function correctly:

/* ProseMirror content area */
.dm-editor .ProseMirror {
position: relative;
outline: none;
word-wrap: break-word;
white-space: pre-wrap;
white-space: break-spaces;
}
/* Gapcursor (if using Gapcursor extension) */
.dm-editor .ProseMirror-gapcursor {
display: none;
pointer-events: none;
position: absolute;
}
.dm-editor .ProseMirror-gapcursor::after {
content: '';
display: block;
position: absolute;
top: -2px;
width: 20px;
border-top: 1px solid currentColor;
animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite;
}
@keyframes ProseMirror-cursor-blink {
to { visibility: hidden; }
}
.dm-editor .ProseMirror-focused .ProseMirror-gapcursor {
display: block;
}
/* Hide selection on drag */
.ProseMirror-hideselection *::selection {
background: transparent;
}
/* Table column resize handle (if using tables) */
.dm-editor .column-resize-handle {
position: absolute;
right: -1.5px;
top: 0;
bottom: 0;
width: 2px;
background-color: #2563eb;
pointer-events: none;
}

The fastest way to customize is to import the default theme and override specific variables:

@import '@domternal/theme';
.my-app .dm-editor {
--dm-accent: #e11d48;
--dm-editor-border-radius: 0;
--dm-editor-font-family: 'Inter', sans-serif;
}
.my-app .dm-toolbar {
--dm-toolbar-bg: #f1f5f9;
--dm-toolbar-border-radius: 0;
}

Import the SCSS source and use the theme’s structure:

// Import just the variables and base styles you need
@use '@domternal/theme/scss';

If building a custom dark theme, override the same semantic tokens:

.my-dark-theme .dm-editor {
--dm-color-scheme: dark;
--dm-bg: #0d1117;
--dm-text: #c9d1d9;
--dm-surface: #161b22;
--dm-border-color: #30363d;
--dm-accent: #58a6ff;
--dm-accent-hover: #79c0ff;
--dm-accent-surface: rgba(88, 166, 255, 0.15);
--dm-hover: rgba(255, 255, 255, 0.06);
--dm-active: rgba(255, 255, 255, 0.12);
--dm-code-surface: #161b22;
}
/* Apply the same tokens to floating UI that escapes the editor */
.my-dark-theme .dm-toolbar,
.my-dark-theme .dm-bubble-menu,
.my-dark-theme .dm-emoji-picker,
.my-dark-theme .dm-mention-suggestion,
.my-dark-theme .dm-table-controls-dropdown {
--dm-bg: #0d1117;
--dm-text: #c9d1d9;
--dm-surface: #161b22;
--dm-border-color: #30363d;
--dm-accent: #58a6ff;
--dm-hover: rgba(255, 255, 255, 0.06);
}

The theme is composed of SCSS partials:

FileDescription
_variables.scssAll CSS custom property definitions
_base.scss.dm-editor wrapper styles, fade-in animation
_content.scssContent typography (headings, lists, links, code, images)
_prosemirror.scssProseMirror required styles (gapcursor, selected nodes, tables)
_toolbar.scssToolbar, buttons, dropdowns, separators
_bubble-menu.scssCompact bubble menu
_floating-menu.scssFloating menu
_link-popover.scssLink URL input popover
_block-handle.scssHover gutter, drag handle, drop indicator
_slash-command.scss/ command menu popup
_context-menu.scssBlock context menu
_color-palette.scssColor grid for text color and highlight pickers
_mention.scssMention nodes and suggestion dropdown
_image.scssResizable images, handles, image popover
_details.scssAccordion/details with CSS grid toggle
_math.scssInline and block math plus the math edit popover
_placeholder.scssPlaceholder text
_task-list.scssTask list with checkboxes
_invisible-chars.scssInvisible character indicators
_syntax.scssSyntax highlighting for code blocks
_emoji-picker.scssEmoji picker and suggestions
_table-controls.scssTable handles, cell toolbar, dropdowns
_block-colors.scssBlock-level named-token colors
_inline-colors.scssInline named-token colors and the Notion color picker. Must load after _block-colors.scss: the inline override depends on cascade order to win over the block-level [data-bg-color] rule
_toc.scssFloating outline and the inline /toc block
_notion-mode.scssThe opt-in .dm-notion-mode preset
themes/_light.scssLight theme class and mixin
themes/_dark.scssDark theme class, auto mode, and mixin
_print.scssThe whole @media print layer. Imported last of all the partials, after both themes, because @media print adds no specificity of its own and only source order breaks a tie. See Printing

index.scss ends with a @media (prefers-reduced-motion: reduce) block that strips animations and transitions from the menu, popover, dropdown and button classes it lists. A theme built from scratch that reuses the same class names has to reimplement it, and the same goes for _print.scss.

  • Configuration - editor and extension configuration
  • Toolbar - toolbar configuration and custom items
  • Text Color - inline text color via TextStyle
  • Print - the printDocument command, the toolbar button and the isolation that pairs with the print stylesheet