Framework Guides

    Framework Guides

    Scribe Editor works with any JavaScript framework. Follow the guide for your stack — each includes complete, copy-paste ready examples with TypeScript support.

    Vanilla JavaScript

    The reference implementation - works everywhere

    Recommended Starting Point
    <!DOCTYPE html>
    <html>
    <head>
      <title>Scribe Editor</title>
      <link rel="stylesheet" href="scribe.css">
    </head>
    <body>
      <div id="editor">
        <p>Start editing...</p>
      </div>
      
      <script type="module">
        import { Scribe } from './scribe.js';
        
        // One-line initialization
        const editor = Scribe.init('#editor', {
          placeholder: 'Write something amazing...',
          autofocus: true
        });
        
        // Direct formatting methods
        document.querySelector('#bold-btn').onclick = () => editor.bold();
        document.querySelector('#italic-btn').onclick = () => editor.italic();
        
        // Save content
        document.querySelector('#save-btn').onclick = () => {
          const html = editor.getHTML();
          console.log('Content:', html);
        };
      </script>
    </body>
    </html>

    React

    Component wrapper with hooks and controlled state

    import { ScribeEditor, ScribeEditorRef } from '@/components/scribe';
    import { useRef, useState } from 'react';
    
    function BasicEditor() {
      const [content, setContent] = useState('<p>Hello world!</p>');
      
      return (
        <ScribeEditor
          defaultValue={content}
          placeholder="Start writing..."
          toolbar="floating"
          onChange={(html) => setContent(html)}
        />
      );
    }

    Vue 3

    Composition API with composables

    <script setup lang="ts">
    import { ref, onMounted, onBeforeUnmount } from 'vue'
    import { Scribe, type EditorInstance } from '@/lib/scribe'
    
    const editorEl = ref<HTMLElement | null>(null)
    const editor = ref<EditorInstance | null>(null)
    const content = ref('<p>Hello from Vue!</p>')
    
    onMounted(() => {
      if (editorEl.value) {
        editor.value = Scribe.init(editorEl.value, {
          onChange: (html) => {
            content.value = html
          }
        })
      }
    })
    
    onBeforeUnmount(() => {
      editor.value?.destroy()
    })
    
    // Expose methods
    const bold = () => editor.value?.bold()
    const italic = () => editor.value?.italic()
    const getHTML = () => editor.value?.getHTML() || ''
    </script>
    
    <template>
      <div class="editor-wrapper">
        <div class="toolbar">
          <button @click="bold">Bold</button>
          <button @click="italic">Italic</button>
        </div>
        <div 
          ref="editorEl" 
          class="scribe-content"
          v-html="content"
        />
      </div>
    </template>

    Svelte

    Reactive bindings with minimal boilerplate

    <!-- ScribeEditor.svelte -->
    <script lang="ts">
      import { onMount, onDestroy, createEventDispatcher } from 'svelte';
      import { Scribe, type EditorInstance, type FormatState } from '@/lib/scribe';
      
      export let value = '';
      export let placeholder = 'Start writing...';
      export let readonly = false;
      
      let editorEl: HTMLElement;
      let editor: EditorInstance | null = null;
      
      const dispatch = createEventDispatcher<{
        change: string;
        selectionChange: FormatState | null;
      }>();
      
      onMount(() => {
        editorEl.innerHTML = value;
        
        editor = Scribe.init(editorEl, {
          placeholder,
          readOnly: readonly,
          onChange: (html) => {
            value = html;
            dispatch('change', html);
          },
          onFormatChange: (event) => {
            dispatch('selectionChange', event.current);
          }
        });
      });
      
      onDestroy(() => {
        editor?.destroy();
      });
      
      // Exported methods
      export function bold() { editor?.bold(); }
      export function italic() { editor?.italic(); }
      export function heading(level: 1 | 2 | 3 | 4 | 5 | 6) { editor?.heading(level); }
      export function getHTML() { return editor?.getHTML() || ''; }
    </script>
    
    <div 
      bind:this={editorEl}
      class="scribe-content prose"
    />
    
    <!-- Usage -->
    <!--
    <script>
      import ScribeEditor from './ScribeEditor.svelte';
      
      let editorRef;
      let formats;
    </script>
    
    <button on:click={() => editorRef.bold()}>Bold</button>
    
    <ScribeEditor 
      bind:this={editorRef}
      on:selectionChange={(e) => formats = e.detail}
    />
    -->

    Web Components

    Framework-agnostic custom element

    Web Components work in any framework or vanilla HTML. Perfect for design systems and micro-frontends.
    // scribe-editor.js - Custom Element
    class ScribeEditor extends HTMLElement {
      static get observedAttributes() {
        return ['placeholder', 'readonly'];
      }
      
      constructor() {
        super();
        this.attachShadow({ mode: 'open' });
        this.editor = null;
      }
      
      connectedCallback() {
        // Create shadow DOM structure
        this.shadowRoot.innerHTML = `
          <style>
            :host {
              display: block;
            }
            .content {
              min-height: 200px;
              padding: 1rem;
              outline: none;
            }
            .content:empty::before {
              content: attr(data-placeholder);
              color: #999;
            }
          </style>
          <div class="content" data-placeholder="${this.placeholder}"></div>
        `;
        
        const content = this.shadowRoot.querySelector('.content');
        
        // Initialize Scribe
        import('./scribe.js').then(({ Scribe }) => {
          this.editor = Scribe.init(content, {
            placeholder: this.placeholder,
            readOnly: this.readonly,
            onChange: (html) => {
              this.dispatchEvent(new CustomEvent('change', { 
                detail: { html },
                bubbles: true 
              }));
            }
          });
        });
      }
      
      disconnectedCallback() {
        this.editor?.destroy();
      }
      
      attributeChangedCallback(name, oldVal, newVal) {
        if (name === 'readonly') {
          this.editor?.setReadOnly(newVal !== null);
        }
      }
      
      get placeholder() {
        return this.getAttribute('placeholder') || 'Start writing...';
      }
      
      get readonly() {
        return this.hasAttribute('readonly');
      }
      
      // Public API
      bold() { this.editor?.bold(); }
      italic() { this.editor?.italic(); }
      getHTML() { return this.editor?.getHTML() || ''; }
      setHTML(html) { this.editor?.setHTML(html); }
    }
    
    customElements.define('scribe-editor', ScribeEditor);
    
    // Usage:
    // <scribe-editor placeholder="Write here..."></scribe-editor>
    // 
    // const editor = document.querySelector('scribe-editor');
    // editor.bold();
    // editor.addEventListener('change', (e) => console.log(e.detail.html));

    SSR Considerations

    Server-side rendering compatibility

    Hydration Strategy

    Rich text editors require the DOM and cannot run on the server. Use these patterns:

    React/Next.js

    // Dynamic import with ssr: false
    const ScribeEditor = dynamic(
      () => import('@/components/scribe'),
      { ssr: false }
    );

    Vue/Nuxt

    <ClientOnly>
      <ScribeEditor v-model="content" />
    </ClientOnly>