Documentation Index
Fetch the complete documentation index at: https://mintlify.com/ueberdosis/tiptap/llms.txt
Use this file to discover all available pages before exploring further.
The Placeholder extension adds placeholder text to empty nodes in your editor. It’s useful for providing hints about what content should go in different parts of the document. The placeholder can be static text or a dynamic function that returns different text based on the node context.
Installation
The Placeholder extension is included in the @tiptap/extensions package.
npm install @tiptap/extensions
Basic Usage
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import Placeholder from '@tiptap/extensions/placeholder'
const editor = new Editor({
extensions: [
StarterKit,
Placeholder.configure({
placeholder: 'Write something …',
}),
],
})
Styling the Placeholder
The extension adds CSS classes that you can style:
/* Style the placeholder text */
.is-empty::before {
content: attr(data-placeholder);
float: left;
color: #adb5bd;
pointer-events: none;
height: 0;
}
/* Style when the entire editor is empty */
.is-editor-empty::before {
content: attr(data-placeholder);
float: left;
color: #adb5bd;
pointer-events: none;
height: 0;
}
Configuration Options
The placeholder content. Can be a static string or a function that returns different text based on the node context.Default: 'Write something …'As a string:Placeholder.configure({
placeholder: 'Start typing here…',
})
As a function:Placeholder.configure({
placeholder: ({ editor, node, pos, hasAnchor }) => {
if (node.type.name === 'heading') {
return 'What's the title?'
}
return 'Can you add some further context?'
},
})
Function parameters:
editor: The editor instance
node: The ProseMirror node
pos: Position in the document
hasAnchor: Whether the cursor is in this node
The CSS class added to empty nodes when the entire editor is empty.Default: 'is-editor-empty'Placeholder.configure({
emptyEditorClass: 'my-editor-empty-class',
})
The CSS class added to individual empty nodes.Default: 'is-empty'Placeholder.configure({
emptyNodeClass: 'my-empty-node-class',
})
The data attribute name used for the placeholder text. Will be prepended with data- and converted to kebab-case.Default: 'placeholder'Placeholder.configure({
dataAttribute: 'custom-placeholder',
})
// Results in: data-custom-placeholder="..."
Whether the placeholder should only be shown when the editor is editable.Default: truePlaceholder.configure({
showOnlyWhenEditable: false, // Show even in read-only mode
})
Whether the placeholder should only be shown for the node containing the cursor.Default: truePlaceholder.configure({
showOnlyCurrent: false, // Show for all empty nodes
})
Whether to show placeholders for all descendant nodes or just direct children.Default: falsePlaceholder.configure({
includeChildren: true, // Show for nested empty nodes
})
Advanced Examples
Different Placeholders for Different Nodes
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import Placeholder from '@tiptap/extensions/placeholder'
const editor = new Editor({
extensions: [
StarterKit,
Placeholder.configure({
placeholder: ({ node }) => {
if (node.type.name === 'heading') {
return 'Enter a heading…'
}
if (node.type.name === 'paragraph') {
return 'Write your content here…'
}
if (node.type.name === 'codeBlock') {
return '// Write your code here'
}
return 'Start typing…'
},
}),
],
})
Context-Aware Placeholders
Placeholder.configure({
placeholder: ({ node, pos, editor }) => {
// Get the parent node
const parent = editor.state.doc.resolve(pos).parent
if (parent.type.name === 'listItem') {
return 'List item…'
}
if (parent.type.name === 'blockquote') {
return 'Quote…'
}
return 'Type something…'
},
})
Show All Empty Nodes
Placeholder.configure({
placeholder: 'Empty block',
showOnlyCurrent: false, // Show placeholder in all empty nodes
includeChildren: true, // Include nested empty nodes
})
Custom Styling
/* Different styles based on node depth */
.is-empty::before {
content: attr(data-placeholder);
float: left;
color: #ced4da;
pointer-events: none;
height: 0;
}
.is-editor-empty::before {
content: attr(data-placeholder);
float: left;
color: #6c757d;
pointer-events: none;
height: 0;
font-size: 1.2em;
}
/* Style headings differently */
h1.is-empty::before {
font-size: 2em;
font-weight: bold;
color: #adb5bd;
}
h2.is-empty::before {
font-size: 1.5em;
font-weight: bold;
color: #adb5bd;
}
/* Style code blocks differently */
pre.is-empty::before {
font-family: monospace;
color: #495057;
}
Read-Only Editor with Placeholders
const editor = new Editor({
editable: false,
extensions: [
StarterKit,
Placeholder.configure({
placeholder: 'No content yet',
showOnlyWhenEditable: false, // Show in read-only mode
}),
],
})
Dynamic Placeholder Based on Editor State
Placeholder.configure({
placeholder: ({ editor, node }) => {
const wordCount = editor.storage.characterCount?.words() || 0
if (wordCount === 0) {
return 'Start writing your first draft…'
}
if (wordCount < 100) {
return 'Keep going…'
}
return 'Add more details…'
},
})
Position-Based Placeholders
Placeholder.configure({
placeholder: ({ pos, editor }) => {
const doc = editor.state.doc
const nodesBefore = doc.childCount
if (pos < 10) {
return 'This is the introduction…'
}
if (pos > doc.content.size - 10) {
return 'Add a conclusion…'
}
return 'Continue writing…'
},
})
Source Code
View the source code on GitHub: