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>