svelte-wysiwyg-editor
v0.2.3
Published
A dependency-light WYSIWYG rich text editor for Svelte 4 & 5. Built on the native contenteditable + document.execCommand API, with link/image/text bubbles, markdown import, HTML source view, fullscreen, and configurable upload/gallery hooks.
Maintainers
Readme
svelte-wysiwyg-editor
A dependency-light WYSIWYG rich text editor for Svelte 4 & 5. Built on the native contenteditable + document.execCommand API — no ProseMirror, no TipTap, no heavy framework. Just Svelte + the browser.
Features
- Formatting: bold, italic, underline, strikethrough, headings (H1–H3), blockquote, paragraph
- Lists: ordered & unordered
- Alignment: left, center, right, justify
- Colors: text color & highlight color pickers
- Links: insert/edit/remove via toolbar popover or inline text bubble
- Images: insert by URL, upload via configurable handler, or pick from a configurable gallery
- Image controls: alignment, size (S/M/L), caption toggle, alt text, link wrapping, remove
- Markdown import: paste markdown, converted to HTML via
marked - HTML source view: toggle raw HTML editing
- Fullscreen mode
- Paste cleanup: strips foreign styles/classes from Word/Google Docs paste
- HTML cleanup: whitelist-based class/style sanitizer
- RTL support:
dirprop - Keyboard shortcuts: Ctrl/Cmd+Z (undo), Ctrl/Cmd+Shift+Z or Ctrl/Cmd+Y (redo)
- Dark mode: Tailwind
dark:variants throughout
Install
npm install svelte-wysiwyg-editor
# or
pnpm add svelte-wysiwyg-editor
# or
yarn add svelte-wysiwyg-editorPeer requirements
svelte^4.0.0 || ^5.0.0
That's it. The package ships a self-contained editor.css with all styling pre-compiled (utility classes + component styles + WYSIWYG typography). You do not need Tailwind CSS or @tailwindcss/typography installed in your project — just import the CSS file once (see Usage below).
Usage
Basic
<script>
import { RichTextEditor } from 'svelte-wysiwyg-editor';
import 'svelte-wysiwyg-editor/editor.css';
let html = '';
</script>
<RichTextEditor bind:value={html} on:change={(e) => html = e.detail.html} />Important: You must import
'svelte-wysiwyg-editor/editor.css'once in your app entry (or in any component that uses the editor). This file contains the pre-compiled Tailwind utility classes used by the editor's templates. Without it, the editor will render unstyled because Tailwind's content scanner cannot see files insidenode_modules(they are excluded via.gitignore).
With image upload
Pass an uploadHandler — an async function that receives a File and returns the uploaded image URL:
<script>
import { RichTextEditor } from 'svelte-wysiwyg-editor';
let html = '';
async function uploadImage(file) {
const formData = new FormData();
formData.append('file', file);
const res = await fetch('/api/upload', {
method: 'POST',
body: formData,
});
const data = await res.json();
return data.url; // must return the public URL string
}
</script>
<RichTextEditor bind:value={html} {uploadHandler} />When uploadHandler is provided, the "Upload Image" button appears in the image popover. When omitted, only "Insert by URL" is available.
With image gallery
Pass a galleryFetcher — an async function that receives { page, limit } and returns { data: Array<{ url, name }>, meta: { total } }:
<script>
import { RichTextEditor } from 'svelte-wysiwyg-editor';
let html = '';
async function fetchGallery({ page, limit }) {
const res = await fetch(`/api/gallery?page=${page}&limit=${limit}`);
return await res.json(); // { data: [{ url, name }], meta: { total } }
}
</script>
<RichTextEditor bind:value={html} {galleryFetcher} />When galleryFetcher is provided, the "Choose from Gallery" button appears and opens the gallery modal with pagination.
RTL
<RichTextEditor bind:value={html} dir="rtl" placeholder="اكتب هنا..." />Disabled
<RichTextEditor bind:value={html} disabled />Props
| Prop | Type | Default | Description |
|------------------|------------|----------------------------|-----------------------------------------------------------------------------|
| value | string | '' | HTML content (two-way bindable) |
| placeholder | string | 'Write something amazing...' | Placeholder shown when empty |
| dir | string | 'ltr' | Text direction: 'ltr' or 'rtl' |
| minHeight | string | '300px' | CSS min-height of the editor area |
| disabled | boolean | false | Disable editing |
| uploadHandler | function | null | async (file: File) => Promise<string> — returns uploaded image URL |
| galleryFetcher | function | null | async ({ page, limit }) => Promise<{ data, meta }> — gallery listing |
Events
| Event | Detail | Description |
|----------|------------------|----------------------------------------------|
| change | { html: string } | Fired on every content change (input, format, paste, etc.) |
<RichTextEditor on:change={(e) => console.log(e.detail.html)} />Styling
All styling is pre-compiled into dist/editor.css during the package build. This includes:
- Utility classes used inline in templates (
flex,items-center,min-w-[300px], etc.) — pre-compiled with Tailwind JIT - Component styles from
<style>blocks (.toolbar,.link-bubble,.bubble-btn, etc.) —@applydirectives pre-processed into raw CSS - WYSIWYG content typography via
.swe-contentclass — self-contained replacement for@tailwindcss/typography'sprose, scoped to the editor area only
The .svelte files in dist/ ship with empty <style> blocks — all CSS comes from editor.css. This avoids any dependency on the consumer's Tailwind/PostCSS setup and works in both Vite dev and build modes.
You must import the CSS once in your app entry:
import 'svelte-wysiwyg-editor/editor.css';Dark mode
The editor uses dark: variant classes (compiled to :is(.dark *) selectors in editor.css). To enable dark mode, add the dark class to a parent element (typically <html class="dark">). This matches Tailwind's default class-based dark mode strategy.
How it works
The editor uses the browser's native contenteditable + document.execCommand API. This keeps the bundle tiny (no ProseMirror/TipTap) and the behavior familiar. execCommand is technically deprecated but still works in all major browsers and is the pragmatic choice for a lightweight editor.
Content is sanitized on paste and via the "Clean HTML" toolbar button using a class/property whitelist so that foreign styles from Word/Google Docs don't pollute the output.
License
MIT © Maulana Shalihin
