> ## 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.

# Placeholder

> Display placeholder text in empty editor nodes to guide users

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.

<CodeGroup>
  ```bash npm theme={null}
  npm install @tiptap/extensions
  ```

  ```bash yarn theme={null}
  yarn add @tiptap/extensions
  ```

  ```bash pnpm theme={null}
  pnpm add @tiptap/extensions
  ```
</CodeGroup>

## Basic Usage

```javascript theme={null}
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:

```css theme={null}
/* 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

<ParamField path="placeholder" type="string | function">
  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:**

  ```javascript theme={null}
  Placeholder.configure({
    placeholder: 'Start typing here…',
  })
  ```

  **As a function:**

  ```javascript theme={null}
  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
</ParamField>

<ParamField path="emptyEditorClass" type="string">
  The CSS class added to empty nodes when the entire editor is empty.

  **Default:** `'is-editor-empty'`

  ```javascript theme={null}
  Placeholder.configure({
    emptyEditorClass: 'my-editor-empty-class',
  })
  ```
</ParamField>

<ParamField path="emptyNodeClass" type="string">
  The CSS class added to individual empty nodes.

  **Default:** `'is-empty'`

  ```javascript theme={null}
  Placeholder.configure({
    emptyNodeClass: 'my-empty-node-class',
  })
  ```
</ParamField>

<ParamField path="dataAttribute" type="string">
  The data attribute name used for the placeholder text. Will be prepended with `data-` and converted to kebab-case.

  **Default:** `'placeholder'`

  ```javascript theme={null}
  Placeholder.configure({
    dataAttribute: 'custom-placeholder',
  })
  // Results in: data-custom-placeholder="..."
  ```
</ParamField>

<ParamField path="showOnlyWhenEditable" type="boolean">
  Whether the placeholder should only be shown when the editor is editable.

  **Default:** `true`

  ```javascript theme={null}
  Placeholder.configure({
    showOnlyWhenEditable: false, // Show even in read-only mode
  })
  ```
</ParamField>

<ParamField path="showOnlyCurrent" type="boolean">
  Whether the placeholder should only be shown for the node containing the cursor.

  **Default:** `true`

  ```javascript theme={null}
  Placeholder.configure({
    showOnlyCurrent: false, // Show for all empty nodes
  })
  ```
</ParamField>

<ParamField path="includeChildren" type="boolean">
  Whether to show placeholders for all descendant nodes or just direct children.

  **Default:** `false`

  ```javascript theme={null}
  Placeholder.configure({
    includeChildren: true, // Show for nested empty nodes
  })
  ```
</ParamField>

## Advanced Examples

### Different Placeholders for Different Nodes

```javascript theme={null}
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

```javascript theme={null}
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

```javascript theme={null}
Placeholder.configure({
  placeholder: 'Empty block',
  showOnlyCurrent: false, // Show placeholder in all empty nodes
  includeChildren: true,  // Include nested empty nodes
})
```

### Custom Styling

```css theme={null}
/* 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

```javascript theme={null}
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

```javascript theme={null}
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

```javascript theme={null}
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:

* [Extension](https://github.com/ueberdosis/tiptap/tree/main/packages/extensions/src/placeholder/placeholder.ts:97)
