---
title: Rich text
description: The rich text plugin's block kinds, marks, Notion hotkeys, placeholders and formatting helpers, including link and color sanitization.
icon: type
---

`richTextPlugin` defines the everyday document: paragraphs, headings, lists, to-dos, toggles, callouts, quotes, dividers, and ten marks, with Notion's shortcuts. The core defines no block kinds, so `<Edytor>` includes it by default, after your plugins:

```svelte title="Editor.svelte" check
<script lang="ts">
	import { Edytor } from 'edytor';
</script>

<!-- Rich text, with no plugins listed. -->
<Edytor />
```

It takes no options. List it yourself only to place it elsewhere in the order, or with `defaultPlugins={false}` (see [Default plugins](/docs/plugins#default-plugins)). To change one of its kinds, define a kind with the same name in any of your plugins, since they come before it, or override only its snippet (see [Blocks](/docs/customization/blocks#override-a-snippet)).

## Block kinds

| Kind                 | Element                    | Data                           | Presets (markdown)                                         |
| -------------------- | -------------------------- | ------------------------------ | ---------------------------------------------------------- |
| `paragraph`          | `div` with a `p` inside    | none                           | Text                                                       |
| `heading`            | `h1` to `h3` from `level`  | `{ level: 'h1' }`              | Heading 1 (`# `), Heading 2 (`## `), Heading 3 (`### `)    |
| `bulleted-list-item` | `li`                       | none                           | Bulleted list (`- `, `* `, `+ `)                           |
| `numbered-list-item` | `li`                       | none                           | Numbered list (`1. `, `a. `, `i. `)                        |
| `todo-item`          | `div` with a checkbox      | `{ checked: boolean }`         | To-do list (`[ ] `, `[] `)                                 |
| `toggle`             | `details`, content in `summary` | none                      | Toggle list (`> `)                                         |
| `callout`            | `div` with an icon         | `{ icon: string }`             | Callout (icon `💡`)                                        |
| `quote`              | `blockquote`               | none                           | Quote (`" `)                                               |
| `divider`            | `hr`, void, no content     | none                           | Divider (`---`)                                            |

The kinds are listed in catalogue order, which the menus follow. Presets feed the [slash menu](/docs/plugins/slash-menu) (all under `Basic blocks`), [markdown shortcuts](/docs/plugins/markdown-shortcuts) and block menus. Their command ids are `block.paragraph`, `block.heading1` to `block.heading3`, `block.bulleted-list-item`, `block.numbered-list-item`, `block.todo-item`, `block.toggle`, `block.callout`, `block.quote` and `block.divider`.

The plugin also defines kinds without presets, for content that arrives from stored documents, the API or an Edytor fragment (an HTML paste gives flat bulleted and numbered items):

- `ordered-list` and `unordered-list`: `ol` and `ul` containers that render only their children. A new child takes the `list-item` kind, and so does a pasted line of the list's own flat kind (a numbered item in an `ordered-list`, a bulleted one in an `unordered-list`, their `itemKind`); any other kind keeps its kind.
- `list-item`: an `li`.
- `details`: the same as `toggle`.
- `horizontalRule`: the same as `divider`.

Behavior worth knowing:

- Bulleted and numbered items, to-dos and toggles are [continuing kinds](/docs/customization/blocks#continuing-kinds): <kbd>Enter</kbd> opens another of the same kind (an unchecked to-do), and in an empty one ends the list. Toggles, callouts and quotes are [containers](/docs/customization/blocks#containers): <kbd>Enter</kbd> at the end of one with children, or of an open toggle, opens a first child, and in the middle of the header moves the rest of the text into that first child. <kbd>Backspace</kbd> at the start of any kind but a paragraph turns it into a paragraph first. [Enter and Backspace by role](/docs/customization/hotkeys#enter-and-backspace-by-role) has the full rules.
- Headings have Notion's three levels. A `heading`'s element follows `data.level`, `h1` to `h3`, and so does its copied HTML. Without a level it is `h1`; any other level (a stored `h5`, say) renders and copies as `h3`, as pasting `h4` to `h6` gives a level `h3` heading.
- A `toggle` is a native `<details>`. The browser owns its `open` attribute, so opening and closing it is not an edit and is never reverted. Arrow keys skip the body of a closed toggle.
- The `todo-item` checkbox (`data-edytor-todo-checkbox`) shows `data.checked`. Clicking it, or <kbd>Mod</kbd> + <kbd>Enter</kbd> in the to-do (or over selected blocks, each shown to-do, while the toggles among them open or close), flips `checked` as one undo step. It does not change in a readonly editor.
- A `callout` shows `data.icon` in a `data-edytor-callout-icon` span, `💡` when it has none.
- The `paragraph` element carries no classes of its own: style it through `[data-edytor-type='paragraph']`.
- Pasted `ol > li` becomes a numbered item and other `li` a bulleted item.

## Marks

| Mark          | Element                      | Value                          | Toolbar button |
| ------------- | ---------------------------- | ------------------------------ | -------------- |
| `bold`        | `strong`                     | `true`                         | B              |
| `italic`      | `em`                         | `true`                         | I              |
| `underline`   | `u`                          | `true`                         | U              |
| `strike`      | `s`                          | `true`                         | S              |
| `code`        | `code`                       | `true`                         | `</>`          |
| `link`        | `a` with `href` and `target` | `{ href, target? }`            | Link panel     |
| `superscript` | `sup`                        | `true`                         |                |
| `subscript`   | `sub`                        | `true`                         |                |
| `color`       | `span` with `color`          | a CSS color                    | A ⌄ (text)     |
| `highlight`   | `span` with `background-color` | a CSS color                  | A ⌄ (background) |

Marks nest in this order, `bold` innermost. Typing at the end of a link extends it only when the caret is inside the link; typing at the edge of any other mark extends it.

Values are sanitized when rendered and copied. A link `href` keeps only `http:`, `https:`, `mailto:`, `tel:` and scheme-less URLs; any other scheme, such as `javascript:`, drops the `href` and leaves the text. A color that could inject CSS (it contains `;`, braces, quotes, `url(` and similar) drops the `style`.

Pasted HTML maps `b`, `i`, `u`, `strike` and `del` to their marks, reads Google Docs' styled spans for bold, italic, underline, strike, color and highlight, and turns `mark` into a yellow highlight.

## Hotkeys

| Keys                                           | Action                        |
| ---------------------------------------------- | ----------------------------- |
| <kbd>Mod</kbd> + <kbd>B</kbd>                    | Toggle bold                   |
| <kbd>Mod</kbd> + <kbd>I</kbd>                    | Toggle italic                 |
| <kbd>Mod</kbd> + <kbd>U</kbd>                    | Toggle underline              |
| <kbd>Mod</kbd> + <kbd>E</kbd>                    | Toggle inline code            |
| <kbd>Mod</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd>, <kbd>Mod</kbd> + <kbd>Shift</kbd> + <kbd>X</kbd> | Toggle strikethrough |
| <kbd>Mod</kbd> + <kbd>Shift</kbd> + <kbd>H</kbd>   | Toggle red text color         |
| <kbd>Mod</kbd> + <kbd>Enter</kbd>                  | Check or uncheck the to-do (in a to-do, or the selected to-dos) |

With a collapsed caret, a mark hotkey sets the mark for the next characters you type instead.

Notion's "turn into" chords convert the block holding the caret, or, as in Notion, every block of a block selection or of a text range across blocks, as one undo step that keeps the selection (Code converts only the first):

| Keys                                              | Turns into      |
| ------------------------------------------------- | --------------- |
| <kbd>Mod</kbd> + <kbd>Alt</kbd> + <kbd>0</kbd>    | Text            |
| <kbd>Mod</kbd> + <kbd>Alt</kbd> + <kbd>1</kbd>    | Heading 1       |
| <kbd>Mod</kbd> + <kbd>Alt</kbd> + <kbd>2</kbd>    | Heading 2       |
| <kbd>Mod</kbd> + <kbd>Alt</kbd> + <kbd>3</kbd>    | Heading 3       |
| <kbd>Mod</kbd> + <kbd>Alt</kbd> + <kbd>4</kbd>    | To-do list      |
| <kbd>Mod</kbd> + <kbd>Alt</kbd> + <kbd>5</kbd>    | Bulleted list   |
| <kbd>Mod</kbd> + <kbd>Alt</kbd> + <kbd>6</kbd>    | Numbered list   |
| <kbd>Mod</kbd> + <kbd>Alt</kbd> + <kbd>7</kbd>    | Toggle list     |
| <kbd>Mod</kbd> + <kbd>Alt</kbd> + <kbd>8</kbd>    | Code (with the [code plugin](/docs/plugins/code)); a block with text or children keeps them, and the code block is inserted after it |

Each runs the kind's command (`block.paragraph`, `block.heading1`, …, `block.code`) and is claimed only when that command is registered.

The plugin also handles the formatting input types browsers send from their own menus and from iOS: bold, italic, underline, strikethrough, superscript, subscript, remove formatting, text and background color, insert link, ordered and unordered list (converts the block), and horizontal rule (inserts a divider at the caret, splitting the block when the caret is inside it; a block that holds nothing becomes the divider; in a list's item, the divider goes out of the list as Turn into puts one: in the place of an item that holds nothing, else after the item, whose text stays whole; an inline block such as a mention counts as content, so a block holding only one keeps it; in an emptied document it creates the divider and a paragraph after it). `insertDividerAtSelection()` below does the same.

## Formatting from code

`richTextOperations(edytor)` returns the helpers the hotkeys and the toolbar use. They act on the current selection:

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

const format = richTextOperations(edytor);

format.setMarkAtRange('bold'); // toggle, or stage at a caret
format.setMarkValueAtRange('highlight', '#fff3a3'); // set, never toggle
format.removeMarkAtRange('highlight'); // remove one mark
format.setLinkAtRange({ href: 'https://svelte.dev', target: '_blank' });
format.removeLinkAtRange();
format.removeAllMarksAtRange();
format.insertDividerAtSelection();
```

`setLinkAtRange` ignores an unsafe `href`. `setMarkValueAtRange` ignores an unsafe color. `removeMarkAtRange` does nothing at a collapsed caret.

## Placeholders

`richTextPlaceholder` gives the kinds Notion's placeholders ("Heading 1", "List", "To-do", "Type '/' for commands" in a focused paragraph…). Pass it to `<Edytor placeholder={richTextPlaceholder} />`. See [Placeholder](/docs/customization/placeholder#notion-placeholders).
