The Edytor component
Every prop of the Edytor component, with types and defaults, plus the snippet overrides for blocks, marks and inline blocks.
<Edytor> renders one editable (or readonly) view of a document. This page lists every prop it accepts and the snippets you can pass to replace how a kind renders.
<script lang="ts">
import { Edytor } from 'edytor';
</script>
<Edytor
value={{ children: [{ type: 'paragraph', content: [{ text: 'Hello' }] }] }}
placeholder="Write something…"
onChange={(root) => save(root.children)}
/>
Props
Most props are read once, when the component is created. Changing them later has no effect; recreate the component (for example with {#key}) to apply new ones. The Live column marks the props that follow updates.
| Prop | Type | Default | Live | Description |
|---|---|---|---|---|
plugins |
Plugin[] |
[] |
Block kinds, marks, inline blocks, hotkeys and hooks. Order matters: the first definition of a type wins. The view adds the rich text, image and arrow move plugins unless the list has them. See Plugins. | |
defaultPlugins |
boolean |
true |
false renders exactly plugins, without the default rich text, image and arrow move plugins. Block handles still follow blockHandles. |
|
value |
JSONDoc |
{ children: [] } |
The initial content. Used only to seed a document that has none; an empty value seeds one paragraph. See Document model. | |
document |
EdytorDocument |
A document created with createDocument, loadDocument or attachDocument. Several views can share one. The view does not destroy it on unmount. |
||
server |
string |
The sync server’s base URL (wss://…/rooms). With room, the view joins <server>/<room> over a WebSocket and keeps a local copy. Read once. See WebSocket. |
||
room |
string |
The document’s id. Alone, it names a local IndexedDB copy; with server, the room to join. Read once. |
||
params |
Record<string, string> |
Next dial | Query parameters sent with each connection, such as an auth token. | |
onSyncExpired |
({ reason, attempts, nextRetryMs }) => void |
Yes | With server: the room closed the connection with 4401 (expired credentials). Pass a fresh token in params before the redial, due in nextRetryMs. Until a dial gets in, an empty document is not seeded. If the callback throws, the error is logged and the redial still happens. See WebSocket. |
|
onSyncRefused |
(refusal: SyncRefusedError) => void |
Yes | The server refused a provider of the view’s document for good (4403, 4409, 1008…): called at mount for a refusal already standing, then for each new one. See refusals. |
|
sync |
EdytorSync |
Advanced: a custom provider factory such as createIndexeddbSync(name) or createWebsocketSync(options). Attached in the browser when the view is editable; the view renders once the document is ready. See Collaboration. |
||
actor |
DocumentActor |
anonymous | The local author { id, name?, color? } of the view’s own document: undo lineage, per-block attribution and the presence profile peers see. Cannot be combined with document (set it in createDocument). |
|
readonly |
boolean |
false |
Yes | Render the document without editing. See Readonly mode. |
placeholder |
string | (view) => string | null |
Text shown in an empty block. A function receives { type, data, focused, empty } and returns the text or null. Falls back to the first plugin that declares a placeholder. |
||
blockHandles |
boolean | BlockHandlesOptions |
true |
Show a handle beside each block. draggable: false keeps the handle and its keyboard moves without pointer dragging. onActivate({ block, anchor }) runs when the handle is clicked. handle is a snippet that replaces the handle’s markup. See Block handles. |
|
blockDnd |
boolean |
true |
Deprecated. false hides the handles. blockHandles wins when both are set. |
|
hotKeys |
Partial<Record<HotKeyCombination, HotKey>> |
Key bindings ('mod+s', 'alt+shift+arrowup', …) checked before the plugins’ and the built-in ones. See Hotkeys. |
||
onChange |
(value: JSONBlock) => void |
Called after every committed change, local or remote, with the document as a root block { type: 'root', children }. |
||
onSelectionChange |
(selection: EdytorSelection) => void |
Called when the selection value changes. See Selection. | ||
edytor |
Edytor |
Bindable | The editor instance. Use bind:edytor. See The editor instance. |
|
class |
string |
Yes | Class on the editable root element. | |
spellcheck |
boolean |
true |
Yes | The root’s spellcheck attribute. |
autocorrect |
'on' | 'off' |
'off' |
Yes | The root’s autocorrect attribute. |
autocomplete |
'on' | 'off' |
'off' |
Yes | The root’s autocomplete attribute. |
autocapitalize |
'off' | 'none' | 'on' | 'sentences' | 'words' | 'characters' |
'none' |
Yes | The root’s autocapitalize attribute. |
inputmode |
'none' | 'text' | 'decimal' | 'numeric' | 'tel' | 'search' | 'email' | 'url' |
unset | Yes | Virtual keyboard hint. Omitted from the DOM when unset. |
enterkeyhint |
'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send' |
unset | Yes | Label of the virtual keyboard’s action key. Omitted when unset. |
translate |
'yes' | 'no' |
'no' |
Yes | The root’s translate attribute. 'no' keeps page translators from rewriting the editable text. |
doc |
YDoc |
Advanced: build the view’s own document around an existing engine document. Cannot be combined with document. |
||
awareness |
Awareness |
Advanced: the presence instance for the view’s own document. Cannot be combined with document. |
value is the initial content, not a binding: bind:value fails type-checking. Read changes with onChange or edytor.value.
document, room/server/sync and value together
- Only
value: the view creates its own document, seeds it, and destroys it on unmount. room,serverorsync: the view creates its own document and attaches the provider (syncoverridesroom/server). Stored or remote content wins;valueseeds the document only when the provider has nothing, after it answered or timed out. Never pass a changing snapshot (onChangeoutput) asvaluebeside a room: a late seed of it can replace the room’s edits. Pass a fixed template or nothing; see deterministic seeds.document: the view renders that document once it is ready. If the document is still waiting for content and no provider is attached to it, the editable view seeds it withvaluewhen it mounts, unless a server refused it (document.syncRefusal): then an empty document stays waiting, and one that already holds content is shown. Passingdocumenttogether withdoc,awarenessoractorthrows.
A readonly view never connects: it shows value, or the document you pass.
Block handles
Handles are on unless you turn them off: a + that adds a block and a drag grip. List blockMenuPlugin to open Notion’s block menu on a grip click, or pass onActivate to open your own:
<script lang="ts">
import { Edytor, type BlockHandleActivation } from 'edytor';
let menu = $state<{ blockId: string; anchor: HTMLElement } | null>(null);
const onActivate = ({ block, anchor }: BlockHandleActivation) => {
menu = { blockId: block.id, anchor };
};
</script>
<Edytor blockHandles={{ onActivate }} />
A focused handle moves its block with Alt+↑/↓ and nests or outdents it with Alt+→/←. Set the drop indicator color with the --edytor-drop-indicator-color custom property. To draw your own + and grip, pass a handle snippet: blockHandles={{ handle }} (see Menus and handles).
Snippets
Pass a snippet named <type>Block, <type>Mark or <type>InlineBlock to replace how one kind renders. The override replaces only the snippet: the kind’s element, void and island flags, hooks and clipboard forms stay as the plugin defined them.
<Edytor>
{#snippet quoteBlock({ block, content, children })}
<span class="quote-mark" contenteditable="false">“</span>
{@render content()}
{#if children}<div class="quote-children">{@render children()}</div>{/if}
{/snippet}
{#snippet boldMark({ content })}
<span class="font-semibold">{@render content()}</span>
{/snippet}
</Edytor>
| Snippet name | Receives |
|---|---|
<type>Block |
{ block, content, children }. block is a reactive view (id, type, data, selected, focused, handle); render content() for the block’s text and children() (or null when it has none) for nested blocks. |
<type>Mark |
{ content, mark, text }. mark is the mark’s value. The snippet renders inside a <span data-edytor-mark> instead of the mark’s tag. |
<type>InlineBlock |
{ block }, a reactive view with id, type, data, selected and handle. |
The core renders the block element itself (the kind’s element, a div by default), so a block snippet renders only the markup inside it. Mark non-editable chrome inside a block with contenteditable="false", or with the use:block.void action.
For a type that is not a valid identifier, such as todo-item, spread the snippet under its name: <Edytor {...{ 'todo-itemBlock': todo }} />.
Content placed inside <Edytor> other than these snippets is ignored. To define a new kind rather than restyle one, write a plugin: see Custom blocks. To replace the slash menu, toolbar, block menu or block handle markup, pass snippets to their plugins: see Menus and handles.