> ## Documentation Index
> Fetch the complete documentation index at: https://docs.yoopta.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Editor

> Understanding the Yoopta Editor core component

## Overview

The Yoopta Editor is the main component that orchestrates all plugins, marks, and UI components. You create an editor instance with `createYooptaEditor()` (passing plugins and optional marks/value there), then render `<YooptaEditor editor={editor} onChange={...} />`. Content is stored on the editor instance and synced via `onChange`.

## Creating an Editor Instance

Use `createYooptaEditor()` with an options object. **Plugins are required**; marks and initial value are optional:

```jsx theme={null}
import { createYooptaEditor } from '@yoopta/editor';
import Paragraph from '@yoopta/paragraph';

const editor = useMemo(
  () =>
    createYooptaEditor({
      plugins: [Paragraph],
      marks: [], // optional, e.g. from @yoopta/marks
      value: undefined, // optional initial content
      id: undefined, // optional editor ID
      readOnly: false,
    }),
  [],
);
```

<Warning>
  Always wrap `createYooptaEditor()` in `useMemo` (with stable `plugins`/`marks`) to prevent recreating the editor on every render.
</Warning>

<Note>
  Plugins and marks are configured at creation time, not on the `<YooptaEditor>` component. Value is managed via the editor instance and `onChange`.
</Note>

## YooptaEditor Component Props

The `<YooptaEditor>` component accepts:

### Required

<ParamField path="editor" type="YooEditor" required>
  The editor instance from `createYooptaEditor()`
</ParamField>

### Optional

<ParamField path="onChange" type="(value: YooptaContentValue, options: YooptaOnChangeOptions) => void">
  Called when content changes. Use it to sync `value` to your state (e.g. for saving or controlled usage).
</ParamField>

<ParamField path="onPathChange" type="(path: YooptaPath) => void">
  Called when the selection path changes (current block, selected blocks, etc.).
</ParamField>

<ParamField path="autoFocus" type="boolean">
  Whether to focus the editor on mount.
</ParamField>

<ParamField path="placeholder" type="string">
  Global placeholder text shown when the editor has a single empty block. If a plugin defines its own element-level placeholder, the element-level placeholder takes priority in multi-block editors. See [Placeholders](#placeholders) for details.
</ParamField>

<ParamField path="className" type="string">
  CSS class for the editor wrapper.
</ParamField>

<ParamField path="style" type="CSSProperties">
  Inline styles for the editor wrapper.
</ParamField>

<ParamField path="children" type="ReactNode">
  UI components to render inside the editor (e.g. toolbar, slash menu, block actions).
</ParamField>

<ParamField path="renderBlock" type="(props: RenderBlockProps) => ReactNode">
  Custom wrapper for each block (e.g. for drag-and-drop).
</ParamField>

## Basic Example

```jsx theme={null}
import { useMemo, useState } from 'react';
import YooptaEditor, { createYooptaEditor } from '@yoopta/editor';
import Paragraph from '@yoopta/paragraph';

const plugins = [Paragraph];

export default function MyEditor() {
  const editor = useMemo(
    () =>
      createYooptaEditor({
        plugins,
        value: undefined,
      }),
    [],
  );

  const [value, setValue] = useState(editor.children);

  const handleChange = (newValue, options) => {
    setValue(newValue);
    console.log('Operations:', options.operations);
  };

  return (
    <YooptaEditor
      editor={editor}
      onChange={handleChange}
      placeholder="Start typing..."
      autoFocus
    />
  );
}
```

## Placeholders

Yoopta supports placeholders at two levels that work together:

* **Editor-level** — a global fallback placeholder passed to `<YooptaEditor placeholder="..." />`
* **Element-level** — per-plugin placeholders set via `.extend()` on each element type

### How resolution works

When a block is focused and its element is empty, the editor resolves which placeholder to display:

1. Check the focused element's type (e.g. `heading-one`, `step-list-item-heading`)
2. If that element has a `placeholder` defined — use it
3. Otherwise, fall back to the editor-level `placeholder` prop

This means element-level placeholders always take priority over the global one.

### Editor-level placeholder

```jsx theme={null}
<YooptaEditor
  editor={editor}
  placeholder="Type / to open menu..."
/>
```

### Element-level placeholder

Set placeholders per element type using `.extend()`:

```jsx theme={null}
import { HeadingOne } from '@yoopta/headings';
import Paragraph from '@yoopta/paragraph';

const plugins = [
  Paragraph.extend({
    elements: {
      paragraph: {
        placeholder: 'Type something...',
      },
    },
  }),
  HeadingOne.extend({
    elements: {
      'heading-one': {
        placeholder: 'Heading 1',
      },
    },
  }),
];
```

### Nested plugins

For plugins with multiple nested elements (e.g. Steps, Accordion), you can set a different placeholder for each element:

```jsx theme={null}
import Steps from '@yoopta/steps';

const plugins = [
  Steps.extend({
    elements: {
      'step-list-item-heading': {
        placeholder: 'Step title',
      },
      'step-list-item-content': {
        placeholder: 'Describe this step...',
      },
    },
  }),
];
```

Each nested element resolves its own placeholder independently — the heading shows "Step title" while the content area shows "Describe this step...".

### Styling

Placeholders are rendered via the `.yoopta-placeholder` CSS class and `data-placeholder` attribute. If you use a Yoopta theme (e.g. `@yoopta/themes-shadcn`), placeholder styles are included. For headless usage, add your own styles:

```css theme={null}
.yoopta-placeholder {
  position: relative;
}

.yoopta-placeholder::before {
  content: attr(data-placeholder);
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: none;
  opacity: 0.4;
  white-space: nowrap;
  user-select: none;
}
```

## Content Value Structure

Content is a record of block ID → block data. Type names use PascalCase for block types (e.g. `Paragraph`), and kebab-case for element types inside `value`.

```typescript theme={null}
type YooptaContentValue = Record<string, YooptaBlockData>;

type YooptaBlockData = {
  id: string;
  type: string; // e.g. 'Paragraph', 'HeadingOne'
  meta: {
    order: number;
    depth: number;
    align?: 'left' | 'center' | 'right';
  };
  value: SlateElement[]; // Slate nodes (paragraph, heading-one, etc.)
};
```

### Example

```json theme={null}
{
  "block-1": {
    "id": "block-1",
    "type": "Paragraph",
    "meta": { "order": 0, "depth": 0 },
    "value": [
      {
        "id": "el-1",
        "type": "paragraph",
        "children": [{ "text": "Hello World!" }]
      }
    ]
  }
}
```

## onChange Options

The `onChange` callback receives the new content and an options object:

```typescript theme={null}
type YooptaOnChangeOptions = {
  operations: YooptaOperation[]; // List of operations that caused the change
};
```

Use `options.operations` to know what changed (insert, update, delete, move, etc.) without diffing the whole value.

## Styling the Editor

### Using className

```jsx theme={null}
<YooptaEditor editor={editor} onChange={onChange} className="my-editor" />
```

```css theme={null}
.my-editor {
  max-width: 800px;
  margin: 0 auto;
  padding: 40px 20px;
  min-height: 500px;
}
```

### Using style

```jsx theme={null}
<YooptaEditor
  editor={editor}
  onChange={onChange}
  style={{
    maxWidth: '800px',
    margin: '0 auto',
    padding: '40px 20px',
    minHeight: '500px',
  }}
/>
```

## Multiple Editors

Each editor instance is independent. Create separate instances with `createYooptaEditor()` and separate state for each:

```jsx theme={null}
function MultipleEditors() {
  const editor1 = useMemo(() => createYooptaEditor({ plugins }), []);
  const editor2 = useMemo(() => createYooptaEditor({ plugins }), []);

  const [value1, setValue1] = useState(editor1.children);
  const [value2, setValue2] = useState(editor2.children);

  return (
    <>
      <YooptaEditor editor={editor1} onChange={setValue1} />
      <YooptaEditor editor={editor2} onChange={setValue2} />
    </>
  );
}
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Plugins" icon="puzzle-piece" href="/core/plugins">
    Learn about plugins and how to use them
  </Card>

  <Card title="Editor API" icon="code" href="/api-reference/editor">
    createYooptaEditor options and YooEditor instance API
  </Card>

  <Card title="Events" icon="bolt" href="/advanced/events">
    Handle editor events (on, off, emit)
  </Card>
</CardGroup>
