Reference

API Reference

Complete prop and method reference for all exported components and classes.

<Root>

Component
editing boolean — Toggles editing mode for all nested editors.
onsave? (data: Map<string, Record<string, EditorContent>>) => void
children Snippet receiving { state, save, get, editing }

<Data>

Component Generic
key string — Unique key for this data section in the save map.
data T extends EditorData — Your typed data object.
overrides? NodeOverrides — Custom render overrides for read mode.
children Snippet receiving { text, multiline, rich, image }

<Renderer>

Component
doc ProseMirrorJSON — The document to render.
overrides? NodeOverrides — Override any node or mark rendering.

EditableState

Class
.active Editable | undefined — Currently focused editor.
.isText boolean — Whether active editor is text.
.isImage boolean — Whether active editor is image.
.command() Create a reactive toolbar command object.
.text(fn) Run a function against the active TipTap Editor.

<Each>

Component Generic
key string — Unique key for this array in the save map.
data T[] — Array of typed data objects.
overrides? NodeOverrides — Custom render overrides for read mode.
onsave? (data: EditorSaveData<T>[]) => MaybePromise<void> — Per-section save callback.
each Snippet receiving (item: T, index: number, { text, multiline, rich, image })
<!-- Editable.Each manages arrays of items -->
<Editable.Each key="notes" data={notes} onsave={(items) => {
  // items is EditorSaveData<Note>[]
  items.forEach((item, i) => {
    if (item.title.type === 'text') notes[i].title = item.title.content;
    if (item.body.type === 'text') notes[i].body = item.body.content;
  });
}}>
  {#snippet each(item, index, { text, multiline })}
    <div class="rounded-lg border p-4">
      <h3>{@render text('title')}</h3>
      <div>{@render multiline('body')}</div>
    </div>
  {/snippet}
</Editable.Each>

Standalone Editors

Components

Import from editable-kit/editors. These work with or without Root.

PlainText / MultilineText / RichText

value ProseMirrorJSON — Bindable document state.
editing boolean — Whether the editor is active.
key? string — Registers with Root when provided.
overrides? NodeOverrides — Custom render overrides for read mode.
options? TextEditorOptions — TipTap editor configuration (placeholder, extensions, etc.).

EditableImage

value ImageState — Bindable image state ({ src, alt }).
editing boolean — Whether the editor is active.
key? string — Registers with Root when provided.
maxWidth? number — Max output width in pixels.
maxHeight? number — Max output height in pixels.
quality? number — WebP compression quality (0-1).
aspect? number — Fixed aspect ratio for cropping.

Types

Save Payload

When you call save(), Root collects all editor content into a Map<string, Record<string, EditorContent>>. Each key in the Map matches a Data component's key prop, and each value is a record mapping field names to their content.

// SaveResult is a Map<string, Record<string, EditorContent>>
// Each key matches a Data component's key prop

async function handleSave(data: SaveResult) {
  for (const [key, fields] of data) {
    for (const [name, content] of Object.entries(fields)) {
      if (content.type === 'text') {
        // content.content is ProseMirrorJSON
        await saveText(key, name, content.content);
      } else if (content.type === 'image-src') {
        // Image unchanged — content.src is the original URL
        await saveImageUrl(key, name, content.src, content.alt);
      } else if (content.type === 'image-blob') {
        // Image was cropped/replaced — content.blob is the new image
        const url = await uploadBlob(content.blob);
        await saveImageUrl(key, name, url, content.alt);
      }
    }
  }
}

EditorContent

A discriminated union with three variants. Use the type field to narrow to the correct shape.

// EditorContent is a discriminated union:

type EditorContent =
  | { type: 'text'; content: ProseMirrorJSON }
  | { type: 'image-src'; src: string; alt: string }
  | { type: 'image-blob'; blob: Blob; alt: string };

// Use the type field to narrow:
if (content.type === 'text') {
  content.content // ProseMirrorJSON
} else if (content.type === 'image-blob') {
  content.blob // Blob — upload this
}

EditableState in Depth

EditableState is the reactive class exposed by Root's children snippet. It tracks which editor is focused and provides methods for building toolbars and controlling editors programmatically. All properties are reactive via Svelte 5 runes.

// Access EditableState from Root's children snippet
<Editable.Root editing={true}>
  {#snippet children({ state })}
    {#if state}
      <!-- Check what's focused -->
      {state.isText}  <!-- boolean -->
      {state.isImage}  <!-- boolean -->

      <!-- Create reactive toolbar commands -->
      {@const bold = state.command('toggleBold',
        (e) => e.chain().focus().toggleBold().run())}
      <button
        class:active={bold.isActive}
        disabled={!bold.has}
        onclick={bold.run}
        aria-label="Bold"
        aria-pressed={bold.isActive}>Bold</button>

      <!-- Run arbitrary TipTap commands -->
      <button onclick={() => state.text(
        (editor) => editor.chain().focus().toggleItalic().run()
      )}>Italic</button>

      <!-- Image controls (when isImage is true) -->
      {#if state.isImage}
        <button onclick={() => state.replaceImage()}>Replace</button>
        <input value={state.getImageAlt()}
          oninput={(e) => state.setImageAlt(e.currentTarget.value)} />
      {/if}
    {/if}
  {/snippet}
</Editable.Root>

Method Reference

.active Editable | undefined — Currently focused editor instance.
.isText boolean — Whether active editor is a text editor (reactive).
.isImage boolean — Whether active editor is an image editor (reactive).
.command(name, fn, attrs?) EditableCommand — Create a reactive toolbar command. Returns { isActive: boolean, has: boolean, run(): void }.
.isActive(name, attrs?) boolean — Check if a mark/node is active (reactive).
.has(extension) boolean — Check if extension exists in active editor.
.text(fn) R | undefined — Run function against active TipTap Editor.
.replaceImage() void — Open file picker for active image editor.
.setImageSrc(src) void — Set image source URL.
.getImageAlt() string | undefined — Get current alt text.
.setImageAlt(alt) void — Set alt text.

Per-Section Save

Data and Each components can define their own onsave callback. These fire with typed data for just that section, making it easy to handle saves without parsing the full Map.

<!-- Per-section onsave on Data components -->
<Editable.Data key="profile" data={profile} onsave={(d) => {
  // d is EditorSaveData<typeof profile>
  // Type-safe: d.name is { type: 'text', content: ProseMirrorJSON }
  if (d.name.type === 'text') profile.name = d.name.content;
  if (d.avatar.type === 'image-blob') {
    uploadImage(d.avatar.blob).then(url => profile.avatar.src = url);
  }
}}>
  {#snippet children({ text, image })}
    <h2>{@render text('name')}</h2>
    {@render image('avatar', { maxWidth: 200, maxHeight: 200, aspect: 1 })}
  {/snippet}
</Editable.Data>