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

plug-and-play-editor

v0.3.0

Published

A modern, extensible rich text editor component for the web. Use it with vanilla JavaScript/TypeScript or directly in React apps — zero config needed.

Readme

Plug-and-Play Editor

A modern, extensible rich text editor component for the web. Use it with vanilla JavaScript/TypeScript or directly in React apps — zero config needed.

npm version bundle size


✨ Features

| Category | Features | |----------|----------| | Formatting | Bold, Italic, Underline, Strikethrough, Headings (H1–H6) | | Undo / Redo | Toolbar buttons + keyboard shortcuts (Ctrl/Cmd+Z, Ctrl/Cmd+Y) | | Lists | Unordered, Ordered, Indent, Outdent | | Color | Text color picker, Background highlight color | | Alignment | Left, Center, Right, Justify | | Direction | LTR / RTL support | | Links | Insert link (with URL validation), Unlink | | Media | Insert image (URL), Upload image (file picker, 10 MB limit), Paste image (clipboard), Insert video/embed (sanitized iframes) | | Tables | Insert table, Add/Delete rows, Add/Delete columns | | Code | Inline code, Code blocks (dark theme), HTML source view toggle | | Mentions | @mention dropdown with keyboard navigation, configurable user list, debounced async search | | Emoji | Tabbed emoji picker (Smileys, Gestures, Hearts, Objects, Arrows) | | Structure | Accordion, Page break, Horizontal rule, Table of Contents | | Date/Time | Insert formatted date or time badges | | Template Variables | Insertable {{token}} placeholders for email templates, searchable picker, grouped by category, configurable delimiters | | Font Size | Dropdown with 12 preset sizes (10px–48px), applies as inline styles for email compatibility | | Spacing | Line height (1.0–2.5) and paragraph spacing (compact–double) controls | | Button Block | CTA button builder with text, URL, colors, radius, padding — email-client-compatible inline styles | | Paste Cleanup | Automatically strips Word/Google Docs junk on paste, Ctrl+Shift+V for plain text paste | | Image Resize | Click-to-select images with drag handles for proportional resizing | | Preview Mode | Toggle preview with token replacement (e.g. {{first_name}} → "Alice"), 600px email-width view | | Editing | Tab key inserts spaces (doesn't leave editor) | | Keyboard Shortcuts | Ctrl/Cmd+B (Bold), Ctrl/Cmd+I (Italic), Ctrl/Cmd+U (Underline), Ctrl/Cmd+Z (Undo), Ctrl/Cmd+Shift+Z / Ctrl/Cmd+Y (Redo), Ctrl/Cmd+Shift+V (Paste Plain Text) | | Accessibility | ARIA roles & labels on toolbar, buttons, dropdowns; focus-visible outlines | | Print | Print-ready styles (toolbar hidden, clean layout) | | Responsive | Mobile-friendly emoji picker and toolbar |


📦 Installation

npm install plug-and-play-editor

🚀 Quick Start

Vanilla JavaScript / TypeScript

<!-- index.html -->
<textarea id="editor">
  <p>Hello, world!</p>
</textarea>

<script type="module">
  import { Editor, FormattingPlugin, UndoRedoPlugin, ListsPlugin, EmojiPlugin } from 'plug-and-play-editor';

  const editor = new Editor('#editor', [
    FormattingPlugin,
    UndoRedoPlugin,
    ListsPlugin,
    EmojiPlugin,
  ]);

  // Get content
  console.log(editor.getContent());

  // Set content
  editor.setContent('<p>New content</p>');

  // Clean up when done
  editor.destroy();
</script>

React

npm install plug-and-play-editor react react-dom
import { PlayEditor } from 'plug-and-play-editor/react';

function App() {
  return (
    <PlayEditor
      defaultValue="<p>Start editing...</p>"
      onChange={(html) => console.log(html)}
      minHeight={400}
    />
  );
}

That's it — all 24 plugins load automatically in the React component (including email template features: tokens, button blocks, font size, spacing, paste cleanup, image resize, and preview mode).


📖 Detailed Usage

Vanilla JS — Full Example

import {
  Editor,
  FormattingPlugin,
  UndoRedoPlugin,
  ListsPlugin,
  ColorPlugin,
  AlignmentPlugin,
  DirectionalityPlugin,
  LinksPlugin,
  MediaPlugin,
  TablesPlugin,
  AccordionPlugin,
  PageBreakPlugin,
  TocPlugin,
  PasteImagePlugin,
  MentionsPlugin,
  CodeBlockPlugin,
  DateTimePlugin,
  EmojiPlugin,
  TokensPlugin,
  PasteCleanupPlugin,
  FontSizePlugin,
  SpacingPlugin,
  ButtonBlockPlugin,
  ImageResizePlugin,
  PreviewPlugin,
} from 'plug-and-play-editor';

const editor = new Editor('#my-textarea', [
  FormattingPlugin,
  UndoRedoPlugin,
  ListsPlugin,
  ColorPlugin,
  AlignmentPlugin,
  DirectionalityPlugin,
  LinksPlugin,
  MediaPlugin,
  TablesPlugin,
  AccordionPlugin,
  PageBreakPlugin,
  TocPlugin,
  PasteImagePlugin,
  MentionsPlugin,
  CodeBlockPlugin,
  DateTimePlugin,
  EmojiPlugin,
  TokensPlugin,
  PasteCleanupPlugin,
  FontSizePlugin,
  SpacingPlugin,
  ButtonBlockPlugin,
  ImageResizePlugin,
  PreviewPlugin,
]);

React — With Ref Control

import { useRef } from 'react';
import { PlayEditor } from 'plug-and-play-editor/react';
import type { PlayEditorRef } from 'plug-and-play-editor/react';

function App() {
  const editorRef = useRef<PlayEditorRef>(null);

  const handleSave = () => {
    const html = editorRef.current?.getContent();
    // Send html to your API
    fetch('/api/save', {
      method: 'POST',
      body: JSON.stringify({ content: html }),
    });
  };

  return (
    <>
      <PlayEditor
        ref={editorRef}
        defaultValue="<p>Edit me</p>"
        onChange={(html) => console.log('Changed:', html)}
        minHeight={300}
      />
      <button onClick={handleSave}>Save</button>
    </>
  );
}

React — Custom Plugin Selection

By default, all plugins load. Pass a plugins prop to use only what you need:

import { PlayEditor } from 'plug-and-play-editor/react';
import { FormattingPlugin, UndoRedoPlugin, ListsPlugin, EmojiPlugin } from 'plug-and-play-editor';

function LightEditor() {
  return (
    <PlayEditor
      plugins={[FormattingPlugin, UndoRedoPlugin, ListsPlugin, EmojiPlugin]}
      onChange={(html) => console.log(html)}
    />
  );
}

React — Disabled / Read-Only

// Disabled — no interaction at all
<PlayEditor defaultValue="<p>Locked content</p>" disabled />

// Read-only — content visible but not editable
<PlayEditor defaultValue="<p>View-only content</p>" readOnly />

🔌 Plugins Reference

Built-in Plugins

| Plugin | Import Name | What It Does | |--------|-------------|--------------| | Formatting | FormattingPlugin | Bold, Italic, Underline, Strikethrough, Headings (H1–H6) dropdown | | Undo / Redo | UndoRedoPlugin | Undo and Redo toolbar buttons | | Lists | ListsPlugin | Unordered/Ordered lists, Indent/Outdent | | Color | ColorPlugin | Text color & background color pickers | | Alignment | AlignmentPlugin | Left, Center, Right, Justify | | Directionality | DirectionalityPlugin | LTR / RTL text direction | | Links | LinksPlugin | Insert/remove hyperlinks (URL validated, javascript: blocked) | | Media | MediaPlugin | Insert image (URL + file picker, 10 MB max), sanitized video embed | | Tables | TablesPlugin | Insert 3x3 table, add/delete rows, add/delete columns | | Accordion | AccordionPlugin | Collapsible accordion sections | | Page Break | PageBreakPlugin | Horizontal rule & page break | | TOC | TocPlugin | Auto-generated Table of Contents from headings | | Paste Image | PasteImagePlugin | Cmd+V / Ctrl+V paste images from clipboard | | Mentions | MentionsPlugin | @mention dropdown (uses default empty user list) | | Code Block | CodeBlockPlugin | Inline code, fenced code blocks, HTML source toggle | | Date/Time | DateTimePlugin | Insert current date or time as styled badge | | Emoji | EmojiPlugin | Tabbed emoji picker popup | | Tokens | TokensPlugin | Template variable picker with default email tokens ({{first_name}}, etc.) | | Paste Cleanup | PasteCleanupPlugin | Auto-cleans pasted HTML from Word/Docs, Ctrl+Shift+V for plain text | | Font Size | FontSizePlugin | Font size dropdown (10–48px) with inline style output | | Spacing | SpacingPlugin | Line height & paragraph spacing controls | | Button Block | ButtonBlockPlugin | CTA button builder with colors, padding, radius — click to re-edit | | Image Resize | ImageResizePlugin | Click images to show resize handles, drag to resize proportionally | | Preview | PreviewPlugin | Preview mode with token replacement and 600px email-width view |

Configurable Plugins

Mentions — Custom User List

import { createMentionsPlugin } from 'plug-and-play-editor';

// Static user list
const mentions = createMentionsPlugin({
  users: [
    { id: '1', name: 'Alice Johnson', avatar: 'https://...' },
    { id: '2', name: 'Bob Smith' },
  ],
  trigger: '@', // default
});

// OR async user fetching (automatically debounced at 200 ms)
const asyncMentions = createMentionsPlugin({
  users: async (query) => {
    const res = await fetch(`/api/users?search=${query}`);
    return res.json(); // must return { id, name, avatar? }[]
  },
});

// Use in vanilla JS
const editor = new Editor('#editor', [FormattingPlugin, mentions]);

// Use in React
<PlayEditor plugins={[FormattingPlugin, asyncMentions]} />

Tokens — Custom Variables for Email Templates

import { createTokensPlugin } from 'plug-and-play-editor';

// Custom token list with categories
const tokens = createTokensPlugin({
  tokens: [
    { key: 'first_name', label: 'First Name', category: 'Recipient' },
    { key: 'last_name', label: 'Last Name', category: 'Recipient' },
    { key: 'order_id', label: 'Order ID', category: 'Order' },
    { key: 'total', label: 'Order Total', category: 'Order' },
  ],
});

// With a different delimiter style
const percentTokens = createTokensPlugin({
  tokens: [{ key: 'name', label: 'Name' }],
  delimiter: 'percent',   // renders %name% instead of {{name}}
  // Also supports: 'single-curly' → {name}
});

// Use in vanilla JS
const editor = new Editor('#editor', [FormattingPlugin, tokens]);

// Use in React
<PlayEditor plugins={[FormattingPlugin, tokens]} />

The default TokensPlugin export includes common email template variables:

| Variable | Description | |----------|-------------| | {{first_name}} | Recipient's first name | | {{last_name}} | Recipient's last name | | {{full_name}} | Recipient's full name | | {{email}} | Recipient's email address | | {{company}} | Company name | | {{unsubscribe_url}} | Unsubscribe link | | {{preferences_url}} | Email preferences link | | {{current_year}} | Current year | | {{current_date}} | Current date |

Preview — Custom Sample Data

import { createPreviewPlugin } from 'plug-and-play-editor';

const preview = createPreviewPlugin({
  sampleData: {
    first_name: 'John',
    company: 'My Corp',
    order_id: 'ORD-12345',
    // Add any custom token keys your templates use
  },
});

const editor = new Editor('#editor', [FormattingPlugin, TokensPlugin, preview]);

⌨️ Keyboard Shortcuts

| Shortcut | Action | |----------|--------| | Ctrl/Cmd + B | Bold | | Ctrl/Cmd + I | Italic | | Ctrl/Cmd + U | Underline | | Ctrl/Cmd + Z | Undo | | Ctrl/Cmd + Shift + Z | Redo | | Ctrl/Cmd + Y | Redo | | Ctrl/Cmd + Shift + V | Paste as plain text | | Tab | Insert tab space |


🎨 Styling

The editor ships with its own CSS that loads automatically. You can customize it by overriding CSS custom properties:

/* Override design tokens */
:root {
  --pe-bg: #ffffff;
  --pe-text: #1e293b;
  --pe-text-muted: #64748b;
  --pe-font: 'Inter', system-ui, sans-serif;
  --pe-border: #e2e8f0;
  --pe-radius: 10px;
  --pe-accent: #3b82f6;
  --pe-toolbar-bg: #f8fafc;
  --pe-btn-hover: rgba(0, 0, 0, 0.06);
  --pe-focus: rgba(59, 130, 246, 0.45);
}

Dark Mode Example

.dark .play-editor-container {
  --pe-bg: #1e293b;
  --pe-text: #e2e8f0;
  --pe-text-muted: #94a3b8;
  --pe-border: #334155;
  --pe-toolbar-bg: #0f172a;
  --pe-btn-hover: rgba(255, 255, 255, 0.08);
}

📐 API Reference

Editor Class

const editor = new Editor(selector: string | HTMLTextAreaElement, plugins: Plugin[]);

| Method | Returns | Description | |--------|---------|-------------| | getContent() | string | Get the current HTML content | | setContent(html) | void | Set the editor HTML content | | exec(command, value?) | void | Run a document.execCommand | | addToolbarButton(iconHtml, tooltip, onClick, command?) | HTMLButtonElement | Add a custom toolbar button. Pass command to enable active state tracking. | | addToolbarDivider() | void | Add a visual divider to the toolbar | | onDestroy(fn) | void | Register a cleanup function called on destroy() | | destroy() | void | Tear down the editor, clean up plugins and event listeners, restore the textarea |

| Property | Type | Description | |----------|------|-------------| | editorArea | HTMLDivElement | The contentEditable div | | toolbar | HTMLDivElement | The toolbar container | | textArea | HTMLTextAreaElement | The backing textarea | | container | HTMLElement | The root wrapper element |

PlayEditor React Component

<PlayEditor
  defaultValue?: string          // Initial HTML
  onChange?: (html: string) => void  // Change callback
  plugins?: Plugin[]             // Custom plugins (all by default)
  className?: string             // Wrapper CSS class
  minHeight?: number             // Min editor height in px
  disabled?: boolean             // Disable editor entirely
  readOnly?: boolean             // Read-only mode (visible but not editable)
  ref?: React.Ref<PlayEditorRef> // Imperative handle
/>

PlayEditorRef (via useRef)

| Method / Property | Type | Description | |-------------------|------|-------------| | getContent() | string | Get current HTML | | setContent(html) | void | Set HTML programmatically | | editor | Editor \| null | Access underlying Editor instance |


🧩 Writing Custom Plugins

Create your own plugin by implementing the Plugin interface:

import type { Plugin } from 'plug-and-play-editor';
import type { Editor } from 'plug-and-play-editor';

export const MyPlugin: Plugin = {
  name: 'my-plugin',
  init(editor: Editor) {
    // Add a toolbar button with active state tracking
    editor.addToolbarButton(
      '<svg>...</svg>',   // icon HTML (SVG recommended)
      'My Action',         // tooltip (also used as aria-label)
      () => {
        editor.exec('insertHTML', '<strong>Hello!</strong>');
      },
      'bold'               // optional: command name for active state tracking
    );

    // Add a divider before your buttons
    editor.addToolbarDivider();

    // Access the contentEditable area
    const handler = (e: KeyboardEvent) => {
      // Custom keyboard handling
    };
    editor.editorArea.addEventListener('keydown', handler);

    // Register cleanup for when the editor is destroyed
    editor.onDestroy(() => {
      editor.editorArea.removeEventListener('keydown', handler);
    });
  },
  // Optional: called when editor.destroy() is invoked
  destroy() {
    // Clean up any external resources
  }
};

Then use it:

import { Editor, FormattingPlugin } from 'plug-and-play-editor';
import { MyPlugin } from './my-plugin';

const editor = new Editor('#editor', [FormattingPlugin, MyPlugin]);

// Later, clean up
editor.destroy();

🔒 Security

The editor includes built-in protections against common web vulnerabilities:

  • XSS prevention — User input in links, media embeds, TOC headings, and mentions is sanitized/escaped before insertion
  • URL validation — Only http:, https:, and mailto: URLs are allowed for links; javascript: URLs are blocked
  • Iframe sanitization — Video/media embeds are parsed and rebuilt with only safe attributes; only http:/https: iframe sources are permitted
  • File size limits — Image uploads are capped at 10 MB
  • Safe link defaults — All inserted links get target="_blank" and rel="noopener noreferrer"

📁 Project Structure

plug-and-play-editor/
├── src/
│   ├── core/
│   │   ├── Editor.ts       # Core editor class (lifecycle, shortcuts, active states)
│   │   ├── Plugin.ts       # Plugin interface (init + optional destroy)
│   │   └── icons.ts        # SVG icon library
│   ├── plugins/
│   │   ├── formatting.ts   # Bold, Italic, Headings, Undo/Redo
│   │   ├── lists.ts        # UL, OL, indent
│   │   ├── color.ts        # Color pickers
│   │   ├── alignment.ts    # Text alignment
│   │   ├── directionality.ts # LTR/RTL
│   │   ├── links.ts        # Hyperlinks (with URL validation)
│   │   ├── media.ts        # Images & video (with sanitization)
│   │   ├── tables.ts       # Table management (add/delete rows & cols)
│   │   ├── accordion.ts    # Accordion blocks
│   │   ├── page-break.ts   # HR & page breaks
│   │   ├── toc.ts          # Table of Contents (XSS-safe)
│   │   ├── paste-image.ts  # Clipboard paste
│   │   ├── mentions.ts     # @mentions (debounced, ARIA, XSS-safe)
│   │   ├── code-block.ts   # Code blocks
│   │   ├── datetime.ts     # Date/Time insert
│   │   ├── emoji.ts        # Emoji picker (ARIA, scoped cleanup)
│   │   ├── tokens.ts       # Template variable tokens (configurable, searchable)
│   │   ├── paste-cleanup.ts # Paste cleanup (Word/Docs sanitization)
│   │   ├── font-size.ts    # Font size dropdown (inline styles)
│   │   ├── spacing.ts      # Line height & paragraph spacing
│   │   ├── button-block.ts # CTA button builder (email-compatible)
│   │   ├── image-resize.ts # Image resize handles
│   │   └── preview.ts      # Preview mode with token replacement
│   ├── styles/
│   │   └── core.css        # All styles (responsive, print, a11y)
│   ├── index.ts            # Vanilla JS entry
│   └── react.tsx           # React component (with disabled/readOnly)
├── package.json
├── vite.config.ts
└── tsconfig.json

📄 License

MIT