Find

PreviousNext

Highlight search matches in editable text.

Find highlights matching text in editor blocks. FindReplacePlugin owns the search option and decorations; your UI owns the search input and any replace command.

Loading…

Features

  • Case-insensitive search highlighting.
  • Multiple matches inside the same block.
  • Matches split across adjacent text leaves.
  • App-controlled search state through plugin options.
  • Registry highlight leaf for visible matches.

Fast Path

Add The Plugin

Configure FindReplacePlugin with a leaf renderer for highlighted ranges.

import { FindReplacePlugin } from '@platejs/find-replace';
import { createPlateEditor } from 'platejs/react';
 
import { SearchHighlightLeaf } from '@/components/ui/search-highlight-node';
 
export const editor = createPlateEditor({
  plugins: [
    FindReplacePlugin.configure({
      render: { node: SearchHighlightLeaf },
    }),
  ],
});
import { FindReplacePlugin } from '@platejs/find-replace';
import { createPlateEditor } from 'platejs/react';
 
import { SearchHighlightLeaf } from '@/components/ui/search-highlight-node';
 
export const editor = createPlateEditor({
  plugins: [
    FindReplacePlugin.configure({
      render: { node: SearchHighlightLeaf },
    }),
  ],
});

Update the search option from your toolbar and ask Plate to redecorate the editor.

components/find-toolbar.tsx
import { FindReplacePlugin } from '@platejs/find-replace';
import { useEditorPlugin, usePluginOption } from 'platejs/react';
 
export function FindToolbar() {
  const { editor, setOption } = useEditorPlugin(FindReplacePlugin);
  const search = usePluginOption(FindReplacePlugin, 'search');
 
  return (
    <input
      type="search"
      value={search}
      onChange={(event) => {
        setOption('search', event.target.value);
        editor.api.redecorate();
      }}
    />
  );
}
components/find-toolbar.tsx
import { FindReplacePlugin } from '@platejs/find-replace';
import { useEditorPlugin, usePluginOption } from 'platejs/react';
 
export function FindToolbar() {
  const { editor, setOption } = useEditorPlugin(FindReplacePlugin);
  const search = usePluginOption(FindReplacePlugin, 'search');
 
  return (
    <input
      type="search"
      value={search}
      onChange={(event) => {
        setOption('search', event.target.value);
        editor.api.redecorate();
      }}
    />
  );
}

Use The Registry Example

The demo combines FindReplacePlugin, FixedToolbar, Input, and SearchHighlightLeaf.

'use client';
 
import * as React from 'react';
 
import { FindReplacePlugin } from '@platejs/find-replace';
import {
  Plate,
  useEditorPlugin,
  usePlateEditor,
  usePluginOption,
} from 'platejs/react';
 
import { Input } from '@/components/ui/input';
import { EditorKit } from '@/components/editor/editor-kit';
import { findReplaceValue } from '@/registry/examples/values/find-replace-value';
import { Editor, EditorContainer } from '@/components/ui/editor';
import { FixedToolbar } from '@/components/ui/fixed-toolbar';
import { SearchHighlightLeaf } from '@/components/ui/search-highlight-node';
 
export function FindToolbar() {
  const { editor, setOption } = useEditorPlugin(FindReplacePlugin);
  const search = usePluginOption(FindReplacePlugin, 'search');
 
  return (
    <FixedToolbar className="border-none py-3">
      <Input
        data-testid="ToolbarSearchHighlightInput"
        className="mx-2"
        value={search}
        onChange={(e) => {
          setOption('search', e.target.value);
          editor.api.redecorate();
        }}
        placeholder="Search the text..."
        type="search"
      />
    </FixedToolbar>
  );
}
 
export default function FindReplaceDemo() {
  const editor = usePlateEditor(
    {
      plugins: [
        ...EditorKit,
        FindReplacePlugin.configure({
          options: { search: 'text' },
          render: { node: SearchHighlightLeaf },
        }),
      ],
      value: findReplaceValue,
    },
    []
  );
 
  return (
    <Plate editor={editor}>
      <FindToolbar />
 
      <EditorContainer variant="demo" className="border-t">
        <Editor />
      </EditorContainer>
    </Plate>
  );
}
'use client';
 
import * as React from 'react';
 
import { FindReplacePlugin } from '@platejs/find-replace';
import {
  Plate,
  useEditorPlugin,
  usePlateEditor,
  usePluginOption,
} from 'platejs/react';
 
import { Input } from '@/components/ui/input';
import { EditorKit } from '@/components/editor/editor-kit';
import { findReplaceValue } from '@/registry/examples/values/find-replace-value';
import { Editor, EditorContainer } from '@/components/ui/editor';
import { FixedToolbar } from '@/components/ui/fixed-toolbar';
import { SearchHighlightLeaf } from '@/components/ui/search-highlight-node';
 
export function FindToolbar() {
  const { editor, setOption } = useEditorPlugin(FindReplacePlugin);
  const search = usePluginOption(FindReplacePlugin, 'search');
 
  return (
    <FixedToolbar className="border-none py-3">
      <Input
        data-testid="ToolbarSearchHighlightInput"
        className="mx-2"
        value={search}
        onChange={(e) => {
          setOption('search', e.target.value);
          editor.api.redecorate();
        }}
        placeholder="Search the text..."
        type="search"
      />
    </FixedToolbar>
  );
}
 
export default function FindReplaceDemo() {
  const editor = usePlateEditor(
    {
      plugins: [
        ...EditorKit,
        FindReplacePlugin.configure({
          options: { search: 'text' },
          render: { node: SearchHighlightLeaf },
        }),
      ],
      value: findReplaceValue,
    },
    []
  );
 
  return (
    <Plate editor={editor}>
      <FindToolbar />
 
      <EditorContainer variant="demo" className="border-t">
        <Editor />
      </EditorContainer>
    </Plate>
  );
}

Ownership

SurfaceOwnerWhat It Does
FindReplacePlugin@platejs/find-replaceStores options.search, registers a leaf node, and wires decorateFindReplace.
decorateFindReplace@platejs/find-replaceFinds matches in one element's text children and returns Slate ranges.
SearchHighlightLeafRegistry UIRenders decorated ranges with a yellow background.
FindToolbarApp or registry exampleUpdates options.search and calls editor.api.redecorate().
Replace actionsApp codePerform text replacement with editor transforms. The package does not replace content for you.

The package is headless. The registry gives you a visible highlight leaf and a demo toolbar.

Search Behavior

BehaviorSource
Empty searchReturns no ranges.
Case handlingText and query are lowercased before matching.
Match scopeOnly element nodes whose direct children are all text nodes are decorated.
Multiple matchesEach match becomes one or more ranges.
Split text leavesA match crossing adjacent leaves is split into per-leaf ranges.
Range payloadEach range includes [FindReplacePlugin.key]: true and the matched search slice.

decorateFindReplace searches the concatenated text for one element, then maps each match back to the original child text paths.

Search Highlight Leaf

Install the registry leaf when you want the default yellow highlight.

pnpm dlx shadcn@latest add @plate/search-highlight-node
pnpm dlx shadcn@latest add @plate/search-highlight-node

The copied component is intentionally small:

components/ui/search-highlight-node.tsx
import { type PlateLeafProps, PlateLeaf } from 'platejs/react';
 
export function SearchHighlightLeaf(props: PlateLeafProps) {
  return <PlateLeaf {...props} className="bg-yellow-100" />;
}
components/ui/search-highlight-node.tsx
import { type PlateLeafProps, PlateLeaf } from 'platejs/react';
 
export function SearchHighlightLeaf(props: PlateLeafProps) {
  return <PlateLeaf {...props} className="bg-yellow-100" />;
}

Change the class name when your design system needs a different search color.

API Reference

APIPackageUse
FindReplacePlugin@platejs/find-replaceMain plugin for search highlighting.
options.searchstringQuery text to highlight. Defaults to ''.
decorateFindReplace@platejs/find-replaceDecoration function used by the plugin.
SearchRange.searchstringThe query slice represented by that range.