From zbeyens. The source code is available on GitHub.

Plate
PlatePliteEditorsTemplates
GitHub16kGitHub
DiscordDiscord
    • Stream
    • Copilot
  • Discussion
    • Comments
    • Suggestion
    • Basic Blocks
      • Blockquote
      • Heading
      • Horizontal Rule
    • Callout
    • Code Block
    • Column
    • Date
    • Equation
    • Link
    • List Classic
    • Media
    • MentionElement
    • Table
    • Table of Contents
    • Footnote
    • Toggle
  • Marks
    • Bold
    • Italic
    • Underline
    • Code
    • Highlight
    • Keyboard Input
    • Strikethrough
    • Subscript
    • Superscript
      • Font
      • Line Height
      • Text Align
    • Indent
    • List
      • Exit Break
      • Single Block
      • Trailing Block
    • Autoformat
    • Block Menu
    • Block Placeholder
    • Combobox
      • Emoji
      • MentionElement
      • Slash Command
    • Cursor Overlay
    • Drag & Drop
    • Navigation Feedback
    • Tabbable
    • Toolbar
    • Yjs
    • Multi SelectEditor
    • CSV
    • DOCX
    • HTML
    • Markdown

Block Placeholder

PreviousNext

Placeholder text for the active empty block.

Demo

Block Placeholder injects a placeholder prop into the active empty block. It is block-level UI state, not stored document content. Use the editor-level placeholder prop for the globally empty editor state.

Loading…
Block MenuCombobox

On This Page

FeaturesFast PathAdd The KitStyle The PlaceholderOwnershipManual SetupAdd The PluginAdd Type-Specific CopyVisibility RulesStylingAPI Reference
Build your editor
Production-ready AI template and reusable components.
Get all-access

Features

  • Active-block placeholder text.
  • Per-type placeholder map through placeholders.
  • Root-level filtering through query.
  • Custom placeholder styling through className.
  • Focus, read-only, composition, selection, and empty-editor guards.
Report an issue

Fast Path

Add The Kit

BlockPlaceholderKit configures BlockPlaceholderPlugin for paragraph blocks.

'use client';
 
import { BlockPlaceholderPlugin } from 'platejs/react';
 
export const BlockPlaceholderKit = [
  BlockPlaceholderPlugin.configure({
    initialState: {
      className:
        'before:absolute before:cursor-text before:text-muted-foreground/80 before:content-[attr(placeholder)]',
      placeholders: {
        paragraph: 'Type something...',
      },
      query: ({ path }) => path.length === 1,
    },
  }),
];
'use client';
 
import { BlockPlaceholderPlugin } from 'platejs/react';
 
export const BlockPlaceholderKit = [
  BlockPlaceholderPlugin.configure({
    initialState: {
      className:
        'before:absolute before:cursor-text before:text-muted-foreground/80 before:content-[attr(placeholder)]',
      placeholders: {
        paragraph: 'Type something...',
      },
      query: ({ path }) => path.length === 1,
    },
  }),
];
import { createPlateEditor } from 'platejs/react';
 
import { BlockPlaceholderKit } from '@/components/editor/block-placeholder';
 
export const editor = createPlateEditor({
  plugins: BlockPlaceholderKit,
});
import { createPlateEditor } from 'platejs/react';
 
import { BlockPlaceholderKit } from '@/components/editor/block-placeholder';
 
export const editor = createPlateEditor({

Style The Placeholder

The registry kit uses a before: pseudo-element that reads the injected placeholder attribute.

BlockPlaceholderPlugin.configure({
  initialState: {
    className:
      'before:absolute before:cursor-text before:text-muted-foreground/80 before:content-[attr(placeholder)]',
  },
});
BlockPlaceholderPlugin.configure({
  initialState: {
    className:
      'before:absolute before:cursor-text before:text-muted-foreground/80 before:content-[attr(placeholder)]',
  },
});

Ownership

SurfaceOwnerWhat It Does
BlockPlaceholderPluginplatejs/react / @platejs/utils/reactTracks the current placeholder target and injects block node props.
BlockPlaceholderKitRegistryConfigures the default paragraph placeholder and styling.
block-placeholder-demoRegistry exampleShows the placeholder on an empty paragraph inside a non-empty editor.
Editor placeholder propplatejs/reactCovers the globally empty editor state.

The plugin stores its current target in _target. That option is runtime state for rendering; do not serialize it.

Manual Setup

Add The Plugin

BlockPlaceholderPlugin is available from platejs/react.

import { PLUGINS } from 'platejs';
import { BlockPlaceholderPlugin, createPlateEditor } from 'platejs/react';
 
export const editor = createPlateEditor({
  plugins: [
    BlockPlaceholderPlugin.configure({
      initialState: {
        className:
          'before:absolute before:cursor-text before:text-muted-foreground/80 before:content-[attr(placeholder)]',
        placeholders: {
          [PLUGINS.paragraph]: 'Type something...',
        },
        query: ({ path }) => path.length === 1,
      },
    }),
  ],
});
import { PLUGINS } from 'platejs';
import { BlockPlaceholderPlugin, createPlateEditor } from 'platejs/react';
 
export const editor = createPlateEditor({
  plugins: [
    BlockPlaceholderPlugin.configure({
      initialState: {
        className:
          'before:absolute before:cursor-text before:text-muted-foreground/80 before:content-[attr(placeholder)]',
        placeholders: {
          [PLUGINS.paragraph]: 'Type something...',
        },
        query: ({ path }) => path.length === 1,
      },
    }),
  ],
});

Add Type-Specific Copy

Keys in placeholders are plugin names. The plugin resolves each name through editor.plugin(name).schema.type before matching the active block type.

BlockPlaceholderPlugin.configure({
  initialState: {
    placeholders: {
      [PLUGINS.paragraph]: 'Type something...',
      [PLUGINS.heading]: 'Untitled',
      [PLUGINS.blockquote]: 'Quote',
      [PLUGINS.codeBlock]: 'Code',
    },
  },
});
BlockPlaceholderPlugin.configure({
  initialState: {
    placeholders: {
      [





Visibility Rules

The plugin shows a placeholder only when every gate passes.

GateRequirement
Editor modeNot read-only and not composing.
FocusEditor is focused and has a selection.
SelectionSelection is collapsed.
Active blockeditor.read.nodes.block() returns an empty block.
Whole editorThe editor is not in its pristine single-empty-block state. Empty blocks with visible structural state, such as list metadata, still qualify.
Placeholder mapThe block type matches one entry in placeholders.
Queryquery({ editor, node, path, ...ctx }) returns true.

The default query returns true for root blocks only. The whole-editor guard uses editor.plugin(ElementStatePlugin).api.isEmpty, so only type and compiled element properties declared with role: "metadata" are treated as pristine metadata.

query: ({ path }) => path.length === 1
query: ({ path }) => path.length === 1

Use query when placeholders should skip nested content, tables, columns, or app-specific containers.

Styling

The plugin injects two props on the target block:

PropSource
placeholderResolved string from placeholders.
classNameinitialState.className.

Use CSS that reads attr(placeholder). Tailwind arbitrary content works well for this because the placeholder text stays in the DOM attribute instead of document data.

className:
  'before:absolute before:pointer-events-none before:text-muted-foreground/80 before:content-[attr(placeholder)]'
className:
  'before:absolute before:pointer-events-none before:text-muted-foreground/80 before:content-[attr(placeholder)]'

API Reference

APIPackageUse
BlockPlaceholderPluginplatejs/react / @platejs/utils/reactAdds block placeholders through injected node props.
initialState.placeholdersRecord<string, string>Maps plugin names to placeholder text. Package default: {}; copied BlockPlaceholderKit configures paragraph copy.
initialState.query(context) => booleanFilters eligible blocks. Default: ({ path }) => path.length === 1.
initialState.classNamestringClass applied to the block only while its placeholder is active.
initialState._targetInternal runtime stateStores the current target node and placeholder string.
selectors.placeholder(node)Plugin selectorReturns the placeholder string for the current target node.
plugins: BlockPlaceholderKit,
});
PLUGINS
.paragraph]:
'Type something...'
,
[PLUGINS.heading]: 'Untitled',
[PLUGINS.blockquote]: 'Quote',
[PLUGINS.codeBlock]: 'Code',
},
},
});