npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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.

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: dir prop
  • 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-editor

Peer 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 inside node_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.) — @apply directives pre-processed into raw CSS
  • WYSIWYG content typography via .swe-content class — self-contained replacement for @tailwindcss/typography's prose, 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