NodeClass Extension¶
The NodeClass extension allows you to apply arbitrary CSS classes to both block-level nodes (paragraphs, tables, table cells, etc.) and marks (bold, italic, links, etc.) using global attributes. This provides a clean, semantic way to style elements without requiring individual node or mark type extensions.
Unlike TextClass which applies to inline text using <span> tags, NodeClass works with both block-level elements and marks by adding CSS classes directly to their HTML tags (e.g., <p class="highlight">, <table class="bordered">, <strong class="emphasis">).
Note
Despite its name, the NodeClass extension supports both nodes and marks. The name is kept for backward compatibility with existing configurations.
Basic Usage¶
To use the NodeClass extension, configure it with CSS classes organized by node or mark type. Each class can be specified as:
A string (class name and display title will be the same)
An object with
classNameandtitleproperties for custom display names
from django_prose_editor.fields import ProseEditorField
class Article(models.Model):
content = ProseEditorField(
extensions={
"Bold": True,
"Italic": True,
"Table": True,
"NodeClass": {
"cssClasses": {
# Node types
"paragraph": {
"title": "Paragraph",
"cssClasses": [
"highlight",
"callout",
{"className": "centered", "title": "Centered Text"}
]
},
"table": {
"title": "Table",
"cssClasses": [
"bordered",
"striped",
{"className": "compact", "title": "Compact Table"}
]
},
"tableCell": {
"title": "Cell",
"cssClasses": [
"centered",
"right-aligned",
{"className": "numeric", "title": "Numeric Cell"}
]
},
"heading": {
"title": "Heading",
"cssClasses": [
"section-title",
{"className": "accent", "title": "Accent Heading"}
]
},
# Mark types
"bold": {
"title": "Bold",
"cssClasses": [
"emphasis",
{"className": "important", "title": "Important"}
]
},
"link": {
"title": "Link",
"cssClasses": [
"external",
"download",
{"className": "button", "title": "Button Link"}
]
}
}
}
}
)
Alternative Simple Configuration¶
For simpler use cases, you can still use the array format without custom titles:
class Article(models.Model):
content = ProseEditorField(
extensions={
"NodeClass": {
"cssClasses": {
# Node types
"paragraph": ["highlight", "callout", "centered"],
"table": ["bordered", "striped", "compact"],
"tableCell": ["centered", "right-aligned", "numeric"],
"bulletList": ["checklist", "no-bullets", "spaced"],
# Mark types
"bold": ["emphasis", "important"],
"link": ["external", "download", "button"]
}
}
}
)
JavaScript Configuration¶
When creating custom presets, you can configure the NodeClass extension in JavaScript:
import { NodeClass } from "django-prose-editor/editor"
// Configuration for nodes and marks
NodeClass.configure({
cssClasses: {
// Node types
paragraph: ["highlight", "callout", "centered"],
table: ["bordered", "striped", "compact"],
tableCell: ["centered", "right-aligned", "numeric"],
heading: ["section-title", "accent"],
// Mark types
bold: ["emphasis", "important"],
link: ["external", "download", "button"]
}
})
// Mixed configuration with custom titles
NodeClass.configure({
cssClasses: {
paragraph: [
"highlight",
{ className: "callout", title: "Callout Box" }
],
table: [
{ className: "bordered", title: "Bordered Table" },
{ className: "striped", title: "Striped Rows" }
],
bold: [
{ className: "emphasis", title: "Emphasis" },
{ className: "important", title: "Important" }
]
}
})
Supported Types¶
The following node and mark types are supported for CSS class application:
Node Types:
paragraph: Paragraph elements (
<p>)table: Table elements (
<table>)tableCell: Table cells (
<td>,<th>)tableRow: Table rows (
<tr>)heading: Heading elements (
<h1>-<h6>)listItem: List items (
<li>)bulletList: Unordered lists (
<ul>)orderedList: Ordered lists (
<ol>)blockquote: Blockquote elements (
<blockquote>)codeBlock: Code block elements (
<pre>)
Mark Types:
bold: Bold text (
<strong>)italic: Italic text (
<em>)link: Links (
<a>)code: Inline code (
<code>)strike: Strikethrough text (
<s>)underline: Underlined text (
<u>)
More types may be supported but your mileage may vary.
Commands¶
The NodeClass extension provides two commands for programmatic control:
- setNodeClass
Set the CSS class for a specific node or mark type (replaces any existing class).
// Set class on a node editor.chain().focus().setNodeClass('paragraph', 'highlight').run() // Set class on a mark editor.chain().focus().setNodeClass('bold', 'emphasis').run() editor.chain().focus().setNodeClass('link', 'external').run()
- Parameters:
type(string): The node or mark type name (e.g., ‘paragraph’, ‘bold’, ‘link’)className(string): The CSS class to set (replaces existing class)
- unsetNodeClass
Remove CSS classes from a specific type or all types.
// Clear classes from a specific type editor.chain().focus().unsetNodeClass('paragraph').run() editor.chain().focus().unsetNodeClass('bold').run() // Clear classes from all configured types editor.chain().focus().unsetNodeClass().run()
- Parameters:
type(string, optional): The node or mark type name. If not provided, clears classes from all applicable types.
Checking Active Classes
Use the standard
isActive()method to check if a class is applied:// Check if a node has a class editor.isActive('paragraph', { class: 'highlight' }) // Check if a mark has a class editor.isActive('bold', { class: 'emphasis' }) editor.isActive('link', { class: 'external' })
HTML Output¶
The extension adds CSS classes directly to both block-level elements and marks:
<!-- Node classes -->
<p class="highlight">This paragraph has highlighting applied.</p>
<table class="bordered striped">
<tr>
<th class="centered">Header</th>
<td class="numeric">123.45</td>
</tr>
</table>
<h2 class="section-title">Section Heading</h2>
<blockquote class="callout">
<p>Important quote or callout text.</p>
</blockquote>
<!-- Mark classes -->
<p>This is <strong class="emphasis">emphasized bold text</strong>.</p>
<p>Visit our <a href="https://example.com" class="external">website</a>.</p>
<p>This is <strong class="important">very important</strong> information.</p>
Sanitization¶
When using server-side sanitization, the NodeClass extension automatically configures the sanitizer to allow class attributes on all supported block-level elements and marks.
Styling Examples¶
Define CSS rules in your stylesheet to style the configured classes for both nodes and marks:
/* Paragraph classes */
.ProseMirror p.highlight {
background-color: #fff3cd;
padding: 1rem;
border-radius: 4px;
border-left: 4px solid #ffc107;
}
.ProseMirror p.callout {
background-color: #e3f2fd;
padding: 1rem;
border-radius: 4px;
border-left: 4px solid #2196f3;
font-weight: 500;
}
.ProseMirror p.centered {
text-align: center;
}
/* Table classes */
.ProseMirror table.bordered {
border: 2px solid #dee2e6;
border-collapse: collapse;
}
.ProseMirror table.bordered td,
.ProseMirror table.bordered th {
border: 1px solid #dee2e6;
}
.ProseMirror table.striped tr:nth-child(even) {
background-color: #f8f9fa;
}
.ProseMirror table.compact {
font-size: 0.875rem;
}
.ProseMirror table.compact td,
.ProseMirror table.compact th {
padding: 0.25rem 0.5rem;
}
/* Table cell classes */
.ProseMirror td.centered,
.ProseMirror th.centered {
text-align: center;
}
.ProseMirror td.right-aligned {
text-align: right;
}
.ProseMirror td.numeric,
.ProseMirror th.numeric {
text-align: right;
font-family: 'Monaco', 'Menlo', monospace;
}
/* Heading classes */
.ProseMirror h1.section-title,
.ProseMirror h2.section-title {
border-bottom: 2px solid #e9ecef;
padding-bottom: 0.5rem;
margin-bottom: 1rem;
}
.ProseMirror .accent {
color: #6f42c1;
border-left: 4px solid #6f42c1;
padding-left: 1rem;
}
/* Mark classes */
.ProseMirror strong.emphasis {
color: #d32f2f;
font-weight: 700;
}
.ProseMirror strong.important {
background-color: #fff176;
padding: 0 0.25rem;
font-weight: 900;
}
.ProseMirror a.external::after {
content: " ↗";
font-size: 0.8em;
}
.ProseMirror a.button {
display: inline-block;
padding: 0.5rem 1rem;
background-color: #2196f3;
color: white;
text-decoration: none;
border-radius: 4px;
}
.ProseMirror a.button:hover {
background-color: #1976d2;
}
/* List classes */
.ProseMirror ul.checklist {
list-style: none;
padding-left: 1.5rem;
}
.ProseMirror ul.checklist li:before {
content: "☐ ";
margin-right: 0.5rem;
}
.ProseMirror ul.no-bullets {
list-style: none;
padding-left: 1rem;
}
.ProseMirror ol.alpha {
list-style-type: lower-alpha;
}
.ProseMirror ol.roman {
list-style-type: lower-roman;
}
.ProseMirror .spaced li,
.ProseMirror .outline li {
margin-bottom: 0.5rem;
}
Example Use Cases¶
- Table Styling
Apply consistent styling to tables with node-specific classes:
Tables:
bordered,striped,compactCells:
centered,right-aligned,numeric
- Content Organization
Use different classes for different content types:
Paragraphs:
highlight,callout,summaryHeadings:
section-title,chapter-heading
- Layout Control
Apply layout modifications per node type:
Paragraphs:
centered,justifiedTables:
full-width,auto-width
- Semantic Styling
Use semantic classes that make sense for specific elements:
Code blocks:
language-python,terminalBlockquotes:
testimonial,definition
Best Practices¶
Node-Specific Classes: Define classes that make sense for each node type rather than applying all classes globally
Semantic Naming: Use class names that describe purpose (
numeric-cell) rather than appearance (right-aligned)Consistent Patterns: Use consistent naming patterns across node types (
table-compact,paragraph-compact)Limit Options: Don’t overwhelm users with too many class options per node type
Test Combinations: Verify that multiple classes work well together on the same node
Document Usage: Provide clear guidelines on when to use each class
Configuration Patterns¶
- Content-Focused Pattern
Organize classes by content purpose:
"cssClasses": {
"paragraph": ["intro", "summary", "highlight", "note"],
"heading": ["chapter", "section", "subsection"],
"table": ["data", "comparison", "summary"]
}
- Layout-Focused Pattern
Organize classes by visual layout:
"cssClasses": {
"paragraph": ["centered", "justified", "indented"],
"table": ["full-width", "compact", "bordered"],
"tableCell": ["centered", "right", "nowrap"]
}
- Mixed Pattern
Combine content and layout classes:
"cssClasses": {
"paragraph": [
# Content classes
"highlight", "note", "warning",
# Layout classes
"centered", "indented"
],
"table": [
# Style classes
"bordered", "striped",
# Layout classes
"compact", "full-width"
]
}
See also¶
ClassLoom Extension is a more flexible alternative that supports multiple independent
class groups, each targeting a different node or mark type and appearing as its own
menu dropdown. It requires explicit Menu configuration to surface the dropdowns,
but is a better fit when you need more than one group or want finer control over
where the buttons appear.
Comparison with TextClass¶
NodeClass complements TextClass by targeting different content levels:
TextClass: Applies to inline text spans using
<span>tags (<span class="...">)NodeClass: Applies to both:
Entire block-level elements (
<p class="...">,<table class="...">)Inline marks using their native tags (
<strong class="...">,<a class="...">)
Use TextClass when you need a generic <span> wrapper for styling arbitrary text, and NodeClass when you want to style specific nodes or marks with their semantic HTML tags. They can be used together for comprehensive styling control.
Note: If you use the same name for both a node type and a mark type in cssClasses, only the node type configuration will be recognized by the NodeClass extension.