Svelte

    Svelte Rich Text Editor

    Scribe Editor is the lightweight, zero-dependency WYSIWYG editor for Svelte and SvelteKit. Under 50KB, minimal boilerplate with onMount, reactive bind:value, and a full feature set — in a framework with almost no native WYSIWYG options.

    $npm install scribejs-editor
    < 50KB
    Gzipped bundle
    0
    Runtime dependencies
    bind:this
    Imperative access

    The Svelte rich text editor gap

    Most popular WYSIWYG editors (Tiptap, Lexical, Slate) are built for React. Their Svelte wrappers are often community-maintained, lagging in features, or incomplete. Scribe is built framework-agnostic from the ground up — Svelte is a first-class citizen.

    • Scribe: works natively in Svelte with standard lifecycle functions
    • No React peer dependency, no framework shims, no unofficial ports
    • Compatible with Svelte 4, Svelte 5, and SvelteKit

    Svelte Component

    A complete, reusable Svelte component with reactive value binding and exported formatting methods.

    <!-- ScribeEditor.svelte -->
    <script lang="ts">
      import { onMount, onDestroy } from 'svelte';
      import { Scribe, type EditorInstance } from 'scribejs-editor';
      import 'scribejs-editor/dist/scribe.css';
    
      export let value = '';
      export let placeholder = 'Start writing...';
    
      let editorEl: HTMLElement;
      let editor: EditorInstance | null = null;
    
      onMount(() => {
        editorEl.innerHTML = value;
        editor = Scribe.init(editorEl, {
          placeholder,
          onChange: (html) => { value = html; },
        });
      });
    
      onDestroy(() => editor?.destroy());
    
      // Exported methods — call via bind:this
      export const bold    = () => editor?.bold();
      export const italic  = () => editor?.italic();
      export const getHTML = () => editor?.getHTML() ?? '';
    </script>
    
    <div bind:this={editorEl} class="scribe-content" />

    Using the Component

    Use bind:this to access imperative methods and bind:value for two-way content binding.

    <!-- App.svelte -->
    <script lang="ts">
      import ScribeEditor from './ScribeEditor.svelte';
    
      let editorRef: ScribeEditor;
      let content = '<p>Hello from Svelte!</p>';
    </script>
    
    <!-- Bind the component instance for imperative access -->
    <button on:click={() => editorRef.bold()}>Bold</button>
    <button on:click={() => editorRef.italic()}>Italic</button>
    <button on:click={() => console.log(editorRef.getHTML())}>Save</button>
    
    <ScribeEditor
      bind:this={editorRef}
      bind:value={content}
      placeholder="Start writing..."
    />
    
    <p>Live preview:</p>
    <!-- eslint-disable-next-line svelte/no-at-html-tags -->
    <div>{@html content}</div>

    SvelteKit SSR Setup

    Dynamic import inside onMount keeps Scribe out of the SvelteKit SSR bundle entirely.

    <!-- SvelteKit — disable SSR per-page or wrap in browser check -->
    <script lang="ts">
      import { browser } from '$app/environment';
      import { onMount, onDestroy } from 'svelte';
    
      let editorEl: HTMLElement;
      let editor: any = null;
    
      onMount(async () => {
        if (!browser) return;
        // Dynamic import keeps Scribe out of the SSR bundle
        const { Scribe } = await import('scribejs-editor');
        editor = Scribe.init(editorEl, { toolbar: 'floating' });
      });
    
      onDestroy(() => editor?.destroy());
    </script>
    
    <div bind:this={editorEl} />

    Feature highlights

    Minimal boilerplate

    onMount + onDestroy — that's the entire lifecycle. No wrappers, no stores, no context. Just Svelte the way it's meant to be.

    bind:value reactive binding

    Update your Svelte reactive variable directly from onChange. Use bind:this for imperative bold(), italic(), getHTML() access.

    SvelteKit compatible

    Dynamic import inside onMount keeps Scribe out of the SSR bundle. No window is not defined errors.

    Lightest option for Svelte

    Under 50KB gzipped. Most other WYSIWYG editors are React-first — Scribe is truly framework-agnostic.

    Complete out of the box

    Bold, italic, underline, strikethrough
    Headings H1–H6
    Ordered & unordered lists
    Links with inline editing
    Floating toolbar on selection
    Fixed toolbar mode
    Paste safe from Word & Google Docs
    Built-in XSS sanitization
    Iframe editing support
    Plugin system for extensibility
    Full TypeScript types included
    MIT license — free forever

    The WYSIWYG editor Svelte deserves.

    Framework-agnostic by design, lightweight by default. The best rich text editor choice for Svelte projects.

    Svelte + Scribe — common questions

    What is the best rich text editor for Svelte?

    Scribe Editor is one of the best lightweight WYSIWYG editors for Svelte. It integrates with onMount and onDestroy lifecycle functions, under 50KB gzipped with zero dependencies, supports bind:value for reactive binding and bind:this for imperative API access. It works with both Svelte and SvelteKit.

    How do I add a rich text editor to a Svelte component?

    Create a Svelte component with a bound element reference (bind:this={editorEl}). In onMount, call Scribe.init(editorEl, options). Pass an onChange callback to sync the editor content with a reactive variable. In onDestroy, call editor.destroy() to clean up.

    Does Scribe Editor work with SvelteKit?

    Yes. In SvelteKit, import Scribe dynamically inside onMount using await import('scribejs-editor'). This ensures the library is never imported during server-side rendering, which avoids window is not defined errors. You can also check the browser flag from $app/environment before initializing.

    Are there many rich text editor options for Svelte?

    The Svelte ecosystem has far fewer native WYSIWYG options compared to React. Most popular editors (Tiptap, Lexical, Slate) are React-first with limited or community-maintained Svelte wrappers. Scribe Editor is framework-agnostic from the ground up, making it one of the most reliable choices for Svelte projects.