---
title: Blocks
description: Block kinds, nesting, void and island blocks, default children, and the kind catalogue that powers menus and markdown shortcuts.
icon: blocks
---

Every block has a kind, its `type`. Kinds come from plugins: each plugin's `blocks` record declares the kinds it provides, how they render and how they behave structurally. This page covers the kinds that ship with Edytor and the structural rules every kind can opt into. To define your own, see [Custom blocks](/docs/customization/blocks).

## Bundled kinds

`<Edytor>` includes `richTextPlugin` and `imagePlugin` by default. `richTextPlugin` provides:

| Type | Renders | `data` | Notes |
| --- | --- | --- | --- |
| `paragraph` | `div` > `p` | | The default block type. |
| `heading` | `h1`–`h3` | `level: 'h1' \| 'h2' \| 'h3'` | Without a level, `h1`; any other level renders and copies as `h3`. |
| `bulleted-list-item` | `li` | | Continues on <kbd>Enter</kbd>. |
| `numbered-list-item` | `li` | | Continues on <kbd>Enter</kbd>. |
| `todo-item` | `div` with a checkbox | `checked: boolean` | Clicking the checkbox writes `checked`. Continues on <kbd>Enter</kbd>. |
| `toggle` | `details` | | Children form the collapsible body. Continues on <kbd>Enter</kbd>; a header over its children (`container`). |
| `callout` | `div` | `icon: string` | The icon defaults to `💡`. A header over its children (`container`). |
| `quote` | `blockquote` | | A header over its children (`container`). |
| `divider` | `hr` | | Void, no content. Converting a block to it adds a paragraph after it for the caret. |
| `ordered-list`, `unordered-list` | `ol`, `ul` | | Containers: children only, default child `list-item`. |
| `list-item` | `li` | | |
| `details`, `horizontalRule` | `details`, `hr` | | Not offered in menus (they have no presets). |

Continuing kinds and `container` headers are described in [Enter and Backspace by role](/docs/customization/hotkeys#enter-and-backspace-by-role).

`imagePlugin` provides `image`, a void `figure` with an image from `data.src` and an editable caption (see [Image](/docs/plugins/image)).

`codePlugin` provides `code`, an island whose children are `codeLine` blocks (default child `codeLine`), highlighted as you type.

## Nesting

Any block can hold `children` unless its kind is void. Children render inside the parent, wherever the kind's snippet places them.

In the editor:

- <kbd>Tab</kbd> nests the current block under the block before it. <kbd>Shift</kbd>+<kbd>Tab</kbd> moves it out, after its parent; the blocks nested after it follow as its children, as in any outliner (an item of a list container leaves the list instead, see [Containers](#containers)). Over a selection of sibling blocks, both keys move them together and keep them selected.
- <kbd>Backspace</kbd> at the start of a nested block that is its parent's last child moves it out one level instead of merging (a heading, list item or other menu kind first turns into a paragraph; see [Enter and Backspace by role](/docs/customization/hotkeys#enter-and-backspace-by-role)).
- Block handles drag blocks before, after or inside other blocks.
- `edytor.moveBlocks` and `edytor.canMoveBlocks` do the same from code (see [Commands](/docs/editor/commands#moving-blocks)).

Deleting a block, or a block selection, does not delete the children that were not selected: they take the deleted block's place. `block.removeBlock({ keepChildren: false })` removes a whole subtree.

## Void blocks

A void block (`void: true`) is a self-contained unit, such as a divider, an image or an embed. It is not part of the text flow:

- Its element is `contenteditable="false"`; the editor does not edit its markup. Controls inside it (buttons, inputs) keep working.
- It cannot receive children. Nothing can be inserted, moved or dropped into it. Turning a block with children into a void kind (`setBlockType`, `setBlock`, a menu conversion) moves the children out, right after it; they take the default type of that slot when they came from an island. `setBlock` with `children` for a void kind is refused. A child that still lands under it (a collaborator nests one at the same moment, or `value` places one there) is shown right after it instead ([concurrent editing](/docs/collaboration/concurrent-editing#void-blocks-hold-no-children)).
- It never merges with a neighbor. Backspace at the start of the block after it selects the void block; a second Backspace deletes it.
- Slash commands and markdown shortcuts don't convert it.

A void kind can still render its `content`. That text stays editable, which is how an image gets an editable caption.

## Island blocks

An island (`island: true`) is editable inside but structurally sealed from the rest of the document. The code block is one: its lines are child blocks, but they belong to the code block only.

- Blocks inside an island cannot be moved, and nothing can be moved or dropped into it. The island itself moves as one block.
- Content never merges across its boundary. Backspace at the start of its first line does not join the line into the block before the island; with a single line, it selects the whole island.
- A range delete that starts inside an island never merges out of it.
- Extending a block selection with <kbd>Shift</kbd>+<kbd>↑</kbd>/<kbd>↓</kbd> from outside never steps into an island's inner blocks.
- Slash commands and markdown shortcuts don't convert the island block itself.
- When an island is deleted, its children take its place and are converted to the default child type of their new parent. A line a collaborator adds to the island at the same time shows the same way; undoing the delete shows it as a line of the island again.

An island keeps whatever structure its interior builds: a table island of rows of cells, or a callout island holding a heading and nested paragraphs, keeps its kinds and nesting. An island that also declares `lines: true` is an island of lines, like the code block:

- Every direct child shows as the island's `defaultChild` (a `codeLine`) and holds no children, even when an undo or a collaborator's edit puts another kind or a nested block there. A block nested under a line shows right after the island instead, and stays movable.
- Inserting under a line is refused, and a block of the line kind outside such an island shows as its parent's default child.
- The first line never merges into the island's own content when the island renders none (`rendersContent: false`).

The difference in one line: a void keeps the editor out, an island keeps the structure in.

These roles come from the plugins of the views attached to a document. A document edited with no view (a headless `createDocument`, a script) takes them from its `semantics` option instead: pass `defaultSemantics` for the bundled kinds (see [Documents](/docs/collaboration/documents#create-a-document)). The server room applies `defaultSemantics` itself (see [the room](/docs/server/room#block-roles)).

## Containers

A kind that declares `rendersContent: false` has no text of its own. It only renders its children, like `ordered-list`, `unordered-list` and `code`. The caret never lands in a container's own slot. A list container that a deletion empties is removed.

A list holds only its items. Turn into converts the items a selection holds, never the list itself. An item turned into another kind leaves the list, where <kbd>Shift</kbd> + <kbd>Tab</kbd> lifts it: before the list, after it, or between its two halves. Out of a list nested right in a list it leaves both. Turned into the kind it already shows as (Numbered list in an ordered list), an item stays, and so does a block a merge left in the list turned into its own kind (Heading 2 on a heading). A divider or a code block that Turn into adds after an item with text goes after the item outside the list, which splits there. A pasted list of the list's own kind (an `<ol>` into an ordered list) gives items; another kind keeps its kind. <kbd>Enter</kbd> in an empty item outdents it the same way, one level per press, until it ends the list. A list that a Turn into or an outdent split stays two lists once the block between them goes: the second one's numbering restarts, and its first item does not nest under the first list's last item. Undo right after the split restores one list. List containers come from JSON, the API or a paste of an Edytor fragment; an HTML paste (from Google Docs, Word or a web page) gives flat bulleted and numbered items, as the lists you type (`1. `, `- `) do, and flat items never split. The keys at an item's edges are listed in [Enter and Backspace by role](/docs/customization/hotkeys#enter-and-backspace-by-role).

## Default children

When the editor creates a block next to the caret, the new block takes its parent's **default child** type. This covers <kbd>Enter</kbd> at the start or end of a block, the second half of a block split with <kbd>Enter</kbd>, and the lines of an island that merges out. At the root, pressing <kbd>Enter</kbd> at the end of a heading or a quote therefore creates a paragraph; inside an `ordered-list`, it creates a `list-item`.

Continuing kinds (bulleted and numbered items, to-dos, toggles) continue themselves instead, and a block with both text and children keeps its kind when <kbd>Enter</kbd> splits it at its end: see [Enter and Backspace by role](/docs/customization/hotkeys#enter-and-backspace-by-role).

Where the type comes from:

- A kind declares it with `defaultChild`. `ordered-list` and `unordered-list` declare `list-item`; `code` declares `codeLine`.
- Kinds without one, and the root, use the document's default type, `paragraph`.
- Two plugins declaring different default children for the same kind is an error (`SemanticConflictError`) when the editor starts.

Read the answer at runtime with `edytor.defaultChild(parentBlock)`, which returns a type name.

## Kinds and presets

A kind lists the ways to create it as `presets`. Each preset is a row in the kind catalogue, `edytor.kinds`, which the slash menu, the markdown shortcuts and your own block menus read:

```ts
edytor.kinds;
// [
//   { id: 'block.paragraph', label: 'Text', icon: 'T', keywords: ['paragraph', 'plain'], value: { type: 'paragraph', data: {} }, replaces: false },
//   { id: 'block.heading1', label: 'Heading 1', markdown: ['# '], value: { type: 'heading', data: { level: 'h1' } }, replaces: false },
//   { id: 'block.heading2', label: 'Heading 2', … },
//   { id: 'block.code', label: 'Code', markdown: ['```'], replaces: true, … },
//   …
// ]
```

Each row has the preset's `label`, `icon`, `keywords`, `data`, `markdown` prefixes and slash menu `group`, plus:

| Field | Meaning |
| --- | --- |
| `id` | The command id: `block.<type>`, numbered from 1 when a kind has several presets (`block.heading2`). |
| `value` | The conversion: type, the preset's data, and the kind's `empty` shape if it has one. |
| `replaces` | `true` when the kind has an `empty` shape (a divider, a code block). It converts in place only a block that holds nothing; a block with text or children stays intact and the new block is inserted after it. See [presets](/docs/customization/blocks#presets). |

Convert a block with `convertToKind`, or run the row's command by id:

```ts
import { convertToKind } from 'edytor';

const row = edytor.kinds.find((kind) => kind.id === 'block.quote');
const block = edytor.selection.state.startBlock;
if (row) convertToKind(edytor, block, row); // true when it applied

await edytor.runCommand('block.heading2'); // converts the caret's block, or every selected or touched block
```

A kind's command converts the caret's block, every block of a block selection, or every block a text range touches that shows its own text (a closed toggle's hidden body is not touched, and a list around the items stays a list), as one undo step. A replacing kind acts only on the block holding the selection's start. See [presets](/docs/customization/blocks#presets).

A "Turn into" menu that keeps the block's text lists the rows with `replaces: false`:

```svelte
{#each edytor.kinds.filter((kind) => !kind.replaces) as kind (kind.id)}
	<button onclick={() => convertToKind(edytor, block, kind)}>{kind.icon} {kind.label}</button>
{/each}
```

Only convertible blocks convert: `block.convertible` is `false` for void blocks, islands and blocks inside an island.
