Skip to content
Edytor
Esc
↑↓navigate↵open⌘Jpreview
On this page

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, server or sync: the view creates its own document and attaches the provider (sync overrides room/server). Stored or remote content wins; value seeds the document only when the provider has nothing, after it answered or timed out. Never pass a changing snapshot (onChange output) as value beside 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 with value when it mounts, unless a server refused it (document.syncRefusal): then an empty document stays waiting, and one that already holds content is shown. Passing document together with doc, awareness or actor throws.

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.

Was this page helpful?