{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "block-placeholder-docs",
  "title": "Block Placeholder",
  "description": "Placeholder text for the active empty block.",
  "files": [
    {
      "path": "../../content/docs/(plugins)/(functionality)/block-placeholder.mdx",
      "content": "---\ntitle: Block Placeholder\ndescription: Placeholder text for the active empty block.\ndocs:\n  - route: /docs/examples/block-placeholder\n    title: Demo\n---\n\nBlock 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.\n\n<ComponentPreview name=\"block-placeholder-demo\" />\n\n<PackageInfo>\n\n## Features\n\n- Active-block placeholder text.\n- Per-type placeholder map through `placeholders`.\n- Root-level filtering through `query`.\n- Custom placeholder styling through `className`.\n- Focus, read-only, composition, selection, and empty-editor guards.\n\n</PackageInfo>\n\n## Fast Path\n\n<Steps>\n\n### Add The Kit\n\n`BlockPlaceholderKit` configures `BlockPlaceholderPlugin` for paragraph blocks.\n\n<ComponentSource name=\"block-placeholder-kit\" />\n\n```tsx\nimport { createPlateEditor } from 'platejs/react';\n\nimport { BlockPlaceholderKit } from '@/components/editor/plugins/block-placeholder-kit';\n\nexport const editor = createPlateEditor({\n  plugins: BlockPlaceholderKit,\n});\n```\n\n### Style The Placeholder\n\nThe registry kit uses a `before:` pseudo-element that reads the injected `placeholder` attribute.\n\n```tsx\nBlockPlaceholderPlugin.configure({\n  options: {\n    className:\n      'before:absolute before:cursor-text before:text-muted-foreground/80 before:content-[attr(placeholder)]',\n  },\n});\n```\n\n</Steps>\n\n## Ownership\n\n| Surface | Owner | What It Does |\n|---------|-------|--------------|\n| `BlockPlaceholderPlugin` | `platejs/react` / `@platejs/utils/react` | Tracks the current placeholder target and injects block node props. |\n| `BlockPlaceholderKit` | Registry | Configures the default paragraph placeholder and styling. |\n| `block-placeholder-demo` | Registry example | Shows the placeholder on an empty paragraph inside a non-empty editor. |\n| `Editor` `placeholder` prop | `platejs/react` | Covers the globally empty editor state. |\n\nThe plugin stores its current target in `_target`. That option is runtime state for rendering; do not serialize it.\n\n## Manual Setup\n\n<Steps>\n\n### Add The Plugin\n\n`BlockPlaceholderPlugin` is available from `platejs/react`.\n\n```tsx\nimport { KEYS } from 'platejs';\nimport { BlockPlaceholderPlugin, createPlateEditor } from 'platejs/react';\n\nexport const editor = createPlateEditor({\n  plugins: [\n    BlockPlaceholderPlugin.configure({\n      options: {\n        className:\n          'before:absolute before:cursor-text before:text-muted-foreground/80 before:content-[attr(placeholder)]',\n        placeholders: {\n          [KEYS.p]: 'Type something...',\n        },\n        query: ({ path }) => path.length === 1,\n      },\n    }),\n  ],\n});\n```\n\n### Add Type-Specific Copy\n\nKeys in `placeholders` are plugin keys. The plugin resolves each key with `editor.getType(key)` before matching the active block type.\n\n```tsx\nBlockPlaceholderPlugin.configure({\n  options: {\n    placeholders: {\n      [KEYS.p]: 'Type something...',\n      [KEYS.h1]: 'Untitled',\n      [KEYS.blockquote]: 'Quote',\n      [KEYS.codeBlock]: 'Code',\n    },\n  },\n});\n```\n\n</Steps>\n\n## Visibility Rules\n\nThe plugin shows a placeholder only when every gate passes.\n\n| Gate | Requirement |\n|------|-------------|\n| Editor mode | Not read-only and not composing. |\n| Focus | Editor is focused and has a selection. |\n| Selection | Selection is collapsed. |\n| Active block | `editor.api.block()` returns an empty block. |\n| Whole editor | The editor is not in its pristine single-empty-block state. Empty blocks with visible structural state, such as list metadata, still qualify. |\n| Placeholder map | The block type matches one entry in `placeholders`. |\n| Query | `query({ editor, node, path, ...ctx })` returns `true`. |\n\nThe default `query` returns `true` for root blocks only. The whole-editor guard uses `editor.api.isElementStateEmpty`, so only `type` and props claimed by plugins through `node.isMetadataProp` are treated as pristine metadata.\n\n```tsx\nquery: ({ path }) => path.length === 1\n```\n\nUse `query` when placeholders should skip nested content, tables, columns, or app-specific containers.\n\n## Styling\n\nThe plugin injects two props on the target block:\n\n| Prop | Source |\n|------|--------|\n| `placeholder` | Resolved string from `placeholders`. |\n| `className` | `options.className`. |\n\nUse 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.\n\n```tsx\nclassName:\n  'before:absolute before:pointer-events-none before:text-muted-foreground/80 before:content-[attr(placeholder)]'\n```\n\n## API Reference\n\n| API | Package | Use |\n|-----|---------|-----|\n| `BlockPlaceholderPlugin` | `platejs/react` / `@platejs/utils/react` | Adds block placeholders through injected node props. |\n| `options.placeholders` | `Record<string, string>` | Maps plugin keys to placeholder text. Default: `{ [KEYS.p]: 'Type something...' }`. |\n| `options.query` | `(context) => boolean` | Filters eligible blocks. Default: `({ path }) => path.length === 1`. |\n| `options.className` | `string` | Class applied to the block only while its placeholder is active. |\n| `options._target` | Internal runtime state | Stores the current target node and placeholder string. |\n| `selectors.placeholder(node)` | Plugin selector | Returns the placeholder string for the current target node. |\n",
      "type": "registry:file",
      "target": "content/docs/plate/(plugins)/(functionality)/block-placeholder.mdx"
    }
  ],
  "type": "registry:file"
}