The placeholder extension displays hint text when blocks or the entire document is empty, helping guide users on what to type.
Basic Usage
import { definePlaceholder } from '@prosekit/extensions/placeholder'
const extension = definePlaceholder ({
placeholder: 'Start typing...' ,
})
API Reference
placeholder
string | ((state: EditorState) => string)
required
The placeholder text to display. Can be a static string or a function that returns text based on the current editor state. // Static
placeholder : 'Type something...'
// Dynamic
placeholder : ( state ) => {
const nodeType = state . selection . $from . parent . type . name
return nodeType === 'heading' ? 'Enter heading...' : 'Type here...'
}
strategy
'doc' | 'block' | ((state: EditorState) => boolean)
default: "block"
Controls when the placeholder is shown:
'doc': Show only when the entire document is empty
'block': Show when the current block is empty (default)
Custom function: Return true to show the placeholder
// Only show when document is empty
strategy : 'doc'
// Show for empty blocks (default)
strategy : 'block'
// Custom logic
strategy : ( state ) => {
return state . doc . textContent . length === 0
}
Styling
The placeholder is added as a node decoration with:
Class: prosekit-placeholder
Attribute: data-placeholder (contains the placeholder text)
CSS Examples
/* Basic styling */
.prosekit-placeholder::before {
content : attr ( data-placeholder );
color : #9ca3af ;
pointer-events : none ;
}
/* Italic placeholder */
.prosekit-placeholder::before {
content : attr ( data-placeholder );
color : #9ca3af ;
font-style : italic ;
pointer-events : none ;
}
/* Fade in animation */
.prosekit-placeholder::before {
content : attr ( data-placeholder );
color : #9ca3af ;
opacity : 0 ;
animation : fadeIn 0.2 s ease-in forwards ;
pointer-events : none ;
}
@keyframes fadeIn {
to {
opacity : 1 ;
}
}
Strategy Examples
Document-Level Placeholder
Show placeholder only when the entire document is empty:
import { definePlaceholder } from '@prosekit/extensions/placeholder'
const extension = definePlaceholder ({
placeholder: 'Start writing your document...' ,
strategy: 'doc' ,
})
Block-Level Placeholder
Show placeholder in each empty block (default behavior):
import { definePlaceholder } from '@prosekit/extensions/placeholder'
const extension = definePlaceholder ({
placeholder: 'Type "/" for commands' ,
strategy: 'block' ,
})
Dynamic Placeholder Text
Change placeholder text based on context:
import { definePlaceholder } from '@prosekit/extensions/placeholder'
const extension = definePlaceholder ({
placeholder : ( state ) => {
const { $from } = state . selection
const nodeType = $from . parent . type . name
switch ( nodeType ) {
case 'heading' :
const level = $from . parent . attrs . level
return `Heading ${ level } `
case 'paragraph' :
return 'Start typing...'
case 'codeBlock' :
return '// Write code here'
default :
return 'Type something...'
}
},
})
Conditional Display
Show placeholder based on custom logic:
import { definePlaceholder } from '@prosekit/extensions/placeholder'
const extension = definePlaceholder ({
placeholder: 'Add a description...' ,
strategy : ( state ) => {
const { $from } = state . selection
const parent = $from . parent
// Only show in empty paragraphs that are first children
return (
parent . type . name === 'paragraph' &&
parent . content . size === 0 &&
$from . index () === 0
)
},
})
Complete Examples
Basic Editor with Placeholder
import { createEditor } from '@prosekit/core'
import { defineBasicExtension } from '@prosekit/extensions/basic'
import { definePlaceholder } from '@prosekit/extensions/placeholder'
import { union } from '@prosekit/core'
const extension = union ([
defineBasicExtension (),
definePlaceholder ({
placeholder: 'Write something amazing...' ,
}),
])
const editor = createEditor ({ extension })
Multi-Level Placeholders
import { definePlaceholder } from '@prosekit/extensions/placeholder'
const extension = definePlaceholder ({
placeholder : ( state ) => {
const { doc , selection } = state
const { $from } = selection
// Document-level placeholder
if ( doc . textContent . length === 0 ) {
return 'Start your story...'
}
// Block-level placeholders
const nodeType = $from . parent . type . name
const nodeLevel = $from . parent . attrs . level
if ( nodeType === 'heading' && nodeLevel === 1 ) {
return 'Document title'
}
if ( nodeType === 'heading' ) {
return 'Section heading'
}
return 'Continue writing...'
},
})
Use Cases
User Guidance Guide users on what to type with contextual hints.
Command Hints Show available slash commands or keyboard shortcuts.
Form Fields Provide field descriptions in form-like editors.
Empty State Improve the empty document experience with helpful text.
Accessibility
Make sure your placeholder text is accessible:
.prosekit-placeholder::before {
content : attr ( data-placeholder );
color : #9ca3af ;
pointer-events : none ;
/* Ensure sufficient contrast */
/* WCAG AA requires 4.5:1 for normal text */
}
/* Dark mode */
.dark .prosekit-placeholder::before {
color : #6b7280 ;
}
Behavior Notes
By default, placeholders are hidden when the cursor is:
Inside a code block
Inside a table cell
In any non-empty block
Use the block strategy for most use cases. It provides helpful hints throughout the document without being intrusive.
Dynamic placeholder functions run on every selection change, so keep them lightweight for better performance.
React Example
import { createEditor } from '@prosekit/core'
import { ProseKit } from '@prosekit/react'
import { definePlaceholder } from '@prosekit/extensions/placeholder'
import { useMemo } from 'react'
function Editor () {
const editor = useMemo (() => {
return createEditor ({
extension: definePlaceholder ({
placeholder: 'Type "/" for commands, or start writing...' ,
}),
})
}, [])
return (
< ProseKit editor = { editor } >
< div className = "editor" >
{ /* Editor content */ }
</ div >
</ ProseKit >
)
}
/* Add to your CSS */
.prosekit-placeholder::before {
content : attr ( data-placeholder );
color : #9ca3af ;
float : left ;
pointer-events : none ;
height : 0 ;
}