diff --git a/.changeset/khaki-seals-smell.md b/.changeset/khaki-seals-smell.md
new file mode 100644
index 0000000000..9279c51d4f
--- /dev/null
+++ b/.changeset/khaki-seals-smell.md
@@ -0,0 +1,5 @@
+---
+'@udecode/plate-selection': minor
+---
+
+Feat: api.blockSelection.selectBlocks
diff --git a/.changeset/plenty-rivers-repeat.md b/.changeset/plenty-rivers-repeat.md
new file mode 100644
index 0000000000..16a63730af
--- /dev/null
+++ b/.changeset/plenty-rivers-repeat.md
@@ -0,0 +1,5 @@
+---
+'@udecode/plate-ai': patch
+---
+
+Fix replaceSelection, insertBelow
diff --git a/apps/www/content/docs/en/components/changelog.mdx b/apps/www/content/docs/en/components/changelog.mdx
index fa4bb148b6..dd70518f91 100644
--- a/apps/www/content/docs/en/components/changelog.mdx
+++ b/apps/www/content/docs/en/components/changelog.mdx
@@ -21,8 +21,6 @@ const aiEditor = usePlateEditor({ plugins });
useAIChatEditor(aiEditor, content);
```
-
-
### January 12 #18.2
- `ai-plugins`: remove `createAIEditor`, it's now created in `ai-chat-editor`
diff --git a/apps/www/content/docs/en/form.mdx b/apps/www/content/docs/en/form.mdx
new file mode 100644
index 0000000000..3784c5a628
--- /dev/null
+++ b/apps/www/content/docs/en/form.mdx
@@ -0,0 +1,428 @@
+---
+title: Form
+description: How to integrate Plate editor with react-hook-form.
+---
+
+While Plate is typically used as an **uncontrolled** input, there are valid scenarios where you want to integrate the editor within a form library like [**react-hook-form**](https://www.react-hook-form.com) or the [**Form**](https://ui.shadcn.com/docs/components/form) component from **shadcn/ui**. This guide walks through best practices and common pitfalls.
+
+## When to Integrate Plate with a Form
+
+- **Form Submission**: You want the editor's content to be included along with other fields (e.g., ` `, ``) when the user submits the form.
+- **Validation**: You want to validate the editor's content (e.g., checking if it's empty) at the same time as other form fields.
+- **Form Data Management**: You want to store the editor content in the same store (like `react-hook-form`'s state) as other fields.
+
+However, keep in mind the warning about **fully controlling** the editor value. Plate (and Slate) strongly prefer an uncontrolled model. If you attempt to replace the editor's internal state too frequently, you can break **selection**, **history**, or cause performance issues. The recommended pattern is to treat the editor as uncontrolled, but still **sync** form data on certain events.
+
+## Approach 1: Sync on `onChange`
+
+This is the most straightforward approach: each time the editor changes, update your form field's value. For small documents or infrequent changes, this is usually acceptable.
+
+### React Hook Form Example
+
+```tsx
+import { useForm } from 'react-hook-form';
+import type { Value } from '@udecode/plate';
+import { Plate, PlateContent, usePlateEditor } from '@udecode/plate/react';
+
+type FormData = {
+ content: Value;
+};
+
+export function RHFEditorForm() {
+ const initialValue = [
+ { type: 'p', children: [{ text: 'Hello from react-hook-form!' }] },
+ ]
+
+ // Setup react-hook-form
+ const { register, handleSubmit, setValue } = useForm({
+ defaultValues: {
+ content: initialValue,
+ },
+ });
+
+ // Create/configure the Plate editor
+ const editor = usePlateEditor({ value: initialValue });
+
+ // Register the field for react-hook-form
+ register('content', { /* validation rules... */ });
+
+ const onSubmit = (data: FormData) => {
+ // data.content will have final editor content
+ console.log('Submitted:', data.content);
+ };
+
+ return (
+
+ );
+}
+```
+
+**Notes**:
+1. **`defaultValues.content`**: your initial editor content.
+2. **`register('content')`**: signals to RHF that the field is tracked.
+3. **`onChange({ value })`**: calls `setValue('content', value)` each time.
+
+If you expect large documents or fast typing, consider debouncing or switching to an `onBlur` approach to reduce form updates.
+
+### shadcn/ui Form Example
+
+[shadcn/ui](https://ui.shadcn.com/docs/components/form) provides a `
+ );
+}
+```
+
+This approach makes your editor content behave like any other field in the shadcn/ui form.
+
+## Approach 2: Sync on Blur (or Another Trigger)
+
+Instead of syncing on every keystroke, you might only need the final value when the user:
+- Leaves the editor (`onBlur`),
+- Clicks a “Save” button, or
+- Reaches certain form submission logic.
+
+```tsx
+
+ {
+ // Only sync on blur
+ setValue('content', editor.children);
+ }}
+ />
+
+```
+
+This reduces overhead but your form state won't reflect partial updates while the user is typing.
+
+## Approach 3: Controlled Replacement (Advanced)
+
+If you want the form to be the single source of truth (completely [controlled](/docs/controlled)):
+```ts
+editor.tf.setValue(formStateValue);
+```
+But this has known drawbacks:
+- Potentially breaks cursor position and undo/redo.
+- Can cause frequent full re-renders for large docs.
+
+**Recommendation**: Stick to a partially uncontrolled model if you can.
+
+## Example: Save & Reset
+
+Here's a more complete form demonstrating both saving and resetting the editor/form:
+
+```tsx
+import { useForm } from 'react-hook-form';
+import { Plate, PlateContent, usePlateEditor } from '@udecode/plate/react';
+
+function MyForm() {
+ const form = useForm({
+ defaultValues: {
+ content: [
+ { type: 'p', children: [{ text: 'Initial content...' }] },
+ ],
+ },
+ });
+
+ const editor = usePlateEditor();
+
+ const onSubmit = (data) => {
+ alert(JSON.stringify(data, null, 2));
+ };
+
+ return (
+
+ form.setValue('content', value)}
+ >
+
+
+
+ Save
+
+ {
+ // Reset the editor
+ editor.tf.reset();
+ // Reset the form
+ form.reset();
+ }}
+ >
+ Reset
+
+
+ );
+}
+```
+
+- **`onChange`** -> updates form state.
+- **Reset** -> calls both `editor.tf.reset()` and `form.reset()` for consistency.
+
+## Migrating from a shadcn Textarea to Plate
+
+If you have a standard [TextareaForm from shadcn/ui docs](https://ui.shadcn.com/docs/components/textarea#form) and want to replace `` with a Plate editor, you can follow these steps:
+
+```tsx
+// 1. Original code (TextareaForm)
+ (
+
+ Bio
+
+
+
+
+ You can @mention other users and organizations.
+
+
+
+ )}
+/>
+```
+
+Create a new `EditorField` component:
+
+```tsx
+// EditorField.tsx
+"use client";
+
+import * as React from "react";
+import type { Value } from "@udecode/plate";
+import { Plate, PlateContent, usePlateEditor } from "@udecode/plate/react";
+
+/**
+ * A reusable editor component that works like a standard ,
+ * accepting `value`, `onChange`, and optional placeholder.
+ *
+ * Usage:
+ *
+ * (
+ *
+ * Bio
+ *
+ *
+ *
+ * Some helpful description...
+ *
+ *
+ * )}
+ * />
+ */
+export interface EditorFieldProps
+ extends React.HTMLAttributes {
+ /**
+ * The current Slate Value. Should be an array of Slate nodes.
+ */
+ value?: Value;
+
+ /**
+ * Called when the editor value changes.
+ */
+ onChange?: (value: Value) => void;
+
+ /**
+ * Placeholder text to display when editor is empty.
+ */
+ placeholder?: string;
+}
+
+export function EditorField({
+ value,
+ onChange,
+ placeholder = "Type here...",
+ ...props
+}: EditorFieldProps) {
+ // We create our editor instance with the provided initial `value`.
+ const editor = usePlateEditor({
+ value: value ?? [
+ { type: "p", children: [{ text: "" }] }, // Default empty paragraph
+ ],
+ });
+
+ return (
+ {
+ // Sync changes back to the caller via onChange prop
+ onChange?.(value);
+ }}
+ {...props}
+ >
+
+
+ );
+}
+```
+
+3. Replace the `` with a `` block:
+
+```tsx
+"use client";
+
+import { z } from "zod";
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import { EditorField } from "./EditorField"; // Import the component above
+
+// 1. Define our validation schema with zod
+const FormSchema = z.object({
+ bio: z
+ .string()
+ .min(10, { message: "Bio must be at least 10 characters." })
+ .max(160, { message: "Bio must not exceed 160 characters." }),
+});
+
+// 2. Build our main form component
+export function EditorForm() {
+ // 3. Setup the form
+ const form = useForm>({
+ resolver: zodResolver(FormSchema),
+ defaultValues: {
+ // Here "bio" is just a string, but our editor
+ // will interpret it as initial content if you parse it as JSON
+ bio: "",
+ },
+ });
+
+ // 4. Submission handler
+ function onSubmit(data: z.infer) {
+ alert("Submitted: " + JSON.stringify(data, null, 2));
+ }
+
+ return (
+
+
+ (
+
+ Bio
+
+
+
+
+ You can @mention other users and organizations.
+
+
+
+ )}
+ />
+
+ Submit
+
+
+
+ );
+}
+```
+
+- Any existing form validations or error messages remain the same.
+- For default values, ensure `form.defaultValues.bio` is a valid Slate value (array of nodes) instead of a string.
+- For controlled values, use `editor.tf.setValue(formStateValue)` with moderation.
+
+## Best Practices
+
+1. **Use an Uncontrolled Editor**: Let Plate manage its own state, updating the form only when necessary.
+2. **Minimize Replacements**: Avoid calling `editor.tf.setValue` too often; it can break selection, history, or degrade performance.
+3. **Validate at the Right Time**: Decide if you need instant validation (typing) or upon blur/submit.
+4. **Reset Both**: If you reset the form, call `editor.tf.reset()` to keep them in sync.
diff --git a/apps/www/content/docs/en/getting-started.mdx b/apps/www/content/docs/en/getting-started.mdx
index 6a2b5101a7..d7560702e2 100644
--- a/apps/www/content/docs/en/getting-started.mdx
+++ b/apps/www/content/docs/en/getting-started.mdx
@@ -22,7 +22,7 @@ For an existing React project, jump to the next step.
First, install the core dependencies:
```bash
-npm install @udecode/plate slate-react slate-history
+npm install @udecode/plate
```
For the examples in this guide, we'll also use these plugins:
@@ -36,6 +36,24 @@ npm install @udecode/plate-basic-marks @udecode/plate-heading @udecode/plate-blo
- `@udecode/plate-block-quote` adds blockquote support.
- `@udecode/cn` helps with component styling (optional).
+### TypeScript Requirements
+
+Ensure your `tsconfig.json` is properly configured. The recommended setup for Plate requires TypeScript 5.0+ with the `"bundler"` module resolution:
+
+```jsonc
+{
+ "compilerOptions": {
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ // ... other options
+ }
+}
+```
+
+
+ **Note**: If you can't use TypeScript 5.0+ or "bundler" resolution, see the [TypeScript](/docs/typescript) page for alternative solutions using path aliases.
+
+
### Basic Editor
Let's start with a minimal editor setup.
diff --git a/apps/www/content/docs/en/html.mdx b/apps/www/content/docs/en/html.mdx
index 6f65cf9267..01f0f70539 100644
--- a/apps/www/content/docs/en/html.mdx
+++ b/apps/www/content/docs/en/html.mdx
@@ -4,12 +4,6 @@ title: Serializing HTML
-
- **Note**: Round-tripping is not yet supported: the HTML serializer will not
- preserve all information from the Slate value when converting to HTML and
- back.
-
-
## Slate -> HTML
[Server-side example](/docs/examples/slate-to-html)
@@ -33,7 +27,7 @@ const components = {
// ...
};
-// You can also pass a custom editor component and props
+// You can also pass a custom editor component and props.
// For example, EditorStatic is a styled version of PlateStatic.
const html = await serializeHtml(editor, {
components,
@@ -67,148 +61,67 @@ const fullHtml = `