Overview
editable-kit
Inline editing for Svelte 5. Plain text, rich text via TipTap, and images with cropping — without compromising performance, safety, or prerendering.
Why editable-kit?
Most content editing libraries assume your page exists to be an editor. They load heavy JavaScript up front, require a client-side runtime, and render content by injecting raw HTML into the DOM. That tradeoff makes sense for a full CMS, but not when you just want a heading, a paragraph, or an image to be editable in place.
editable-kit takes a different approach. Your content is static by default. The editor only loads when a user actually starts editing, and disappears when they stop. The result is a page that is fast, prerenderable, and safe — with inline editing that feels native.
Zero JS by default
TipTap, the image cropper, and the toolbar are all lazily imported. Until a user toggles editing, none of that code is loaded. Your visitors get a static page with no editor overhead.
Fully prerenderable
The included Renderer component converts ProseMirror JSON to real Svelte components at build time. Pages prerender to static HTML with no client-side
rendering step required.
No {@html} tag
The Renderer walks ProseMirror JSON and outputs native Svelte elements — no {@html} injection. Content is rendered through the framework, not around it, eliminating an entire class
of XSS vulnerabilities.
Try it out
Click "Try editing" above
This is a live editor. Toggle editing to change this heading and paragraph, then save your changes.
Core Concepts
Two components form the editing context. Root manages global editing state. Data wraps a typed data object
and exposes editor snippets.
<script lang="ts">
import * as Editable from 'editable-kit';
import type { ProseMirrorJSON, ImageState } from 'editable-kit';
type PageData = {
title: ProseMirrorJSON;
body: ProseMirrorJSON;
image: ImageState;
};
let data: PageData = $state({} as PageData); // your data here
let editing = $state(false);
</script>
<Editable.Root {editing} onsave={handleSave}>
{#snippet children({ state, save, editing })}
<Editable.Data key="page" {data}>
{#snippet children({ text, rich, image })}
<h1>{@render text('title')}</h1>
<div>{@render rich('body')}</div>
{@render image('image', { maxWidth: 800, maxHeight: 500, quality: 0.85 })}
{/snippet}
</Editable.Data>
{/snippet}
</Editable.Root>Root
Provides EditableContext. Toggle editing mode, handle saves.
Data
Generic <T> wrapper. Exposes text, multiline, rich, image snippets.
Renderer
Renders ProseMirrorJSON to HTML without loading TipTap.