Documentation
Everything you need to know

Installation

$npm install @rohanyeole/ray-editor
$yarn add @rohanyeole/ray-editor
$pnpm add @rohanyeole/ray-editor
index.html — CDN (always latest)
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@rohanyeole/[email protected]/dist/ray-editor.css">
<script src="https://cdn.jsdelivr.net/npm/@rohanyeole/[email protected]/dist/ray-editor.umd.js"></script>

<div id="editor"></div>
<script>
  const editor = new RayEditor.RayEditor('editor', { theme: 'light' });
</script>

The UMD build exposes window.RayEditor. Instantiate as new RayEditor.RayEditor(...).

index.html — ESM native module
<script type="module">
  import { RayEditor } from 'https://cdn.jsdelivr.net/npm/@rohanyeole/[email protected]/dist/ray-editor.esm.js';
  const editor = new RayEditor('editor', { theme: 'auto' });
</script>

Quick start

main.ts
import { RayEditor } from '@rohanyeole/ray-editor';
import '@rohanyeole/ray-editor/css';

const editor = new RayEditor('my-editor', {
  theme: 'light',            // 'light' | 'dark' | 'auto'
  wordCount: true,          // show live word count bar
  slashCommands: true,      // / command palette
  markdownShortcuts: true,  // ## → H2, **text** → bold
  toolbar: [
    ['bold', 'italic', 'underline', 'highlight'],
    ['headings', 'blockquote', 'callout'],
    ['orderedList', 'unorderedList', 'taskList'],
    ['codeBlock', 'link', 'table'],
    ['undo', 'redo', 'removeFormat'],
  ],
  onChange: (html) => console.log('changed', html),
});

editor.setContent('<p>Hello world!</p>');
const html = editor.getContent(); // clean HTML string

Toolbar keys

The toolbar option takes an array of arrays. Each inner array is a visual group separated by a divider. Use any combination of the keys below.

Why groups? Grouping keeps related actions together visually and matches how users expect to find formatting tools — the same pattern used by Google Docs, Notion, and CKEditor 5.

KeyWhat it doesWhen to addOutput HTML
boldBold — wraps selection in <strong>Almost always — core formatting<strong>
italicItalic — wraps in <em>Almost always — core formatting<em>
underlineUnderlineEmails/documents; omit if it conflicts with link styling<u>
strikethroughStrikethroughEditorial, changelog, or markdown-flavored content<s>
highlight NEWHighlight — yellow <mark>Notes, study apps, any annotation use case<mark>
superscriptSuperscriptScientific/math content, footnotes<sup>
subscriptSubscriptChemistry, math (H₂O etc.)<sub>
uppercaseTransform selection to UPPERCASEForm inputs, headings — keeps underlying HTML texttext-transform
lowercaseTransform to lowercaseSame as uppercasetext-transform
toggleCaseToggle Title Case / sentence caseCopywriting, blog titlestext-transform
textColorText color — inline swatch popupRich CMS editors, email composersstyle="color:…"
backgroundColorBackground color of selected textSame as textColorstyle="background-color:…"
fontSize v2.0.8Font size picker — popup with preset sizes (10–72 px), live preview on hover, custom inputEmail editors, rich newsletters, anywhere font size matters<span style="font-size:…px">
fontsFont family dropdownDesign tools, email composers<font face="…">
headingsHeading level (H1–H6, paragraph, blockquote) dropdownAlmost always — structure is fundamental<h1>…<h6>
blockquoteBlock quoteBlogs, articles, citation-heavy content<blockquote>
callout NEWCallout picker — Info / Warning / Success / ErrorDocumentation, wikis, onboarding flows — draws attention to key info<div class="ray-callout ray-callout-info">
orderedListNumbered listAnywhere steps or ranked items are needed<ol><li>
unorderedListBullet listAlmost always — most common list type<ul><li>
taskList NEWInteractive checkbox task listTo-do apps, project management, meeting notes<li data-type="taskItem" data-checked="…">
indentIncrease list/text indentWhenever you show orderedList or unorderedListPadding-left / list nesting
outdentDecrease indentAlways paired with indentPadding-left / list nesting
textAlignmentAlignment dropdown (left, center, right, justify)Email editors, articles with centered images/captionsalign="…"
hrHorizontal ruleLong-form content, separating sections<hr>
codeBlockFenced code block — language selector + syntax highlightingDev docs, technical blogs, READMEs<pre data-lang="js">
codeInlineInline code — wraps selection in <code>Whenever you have codeBlock — or for inline variable/key references<code>
linkInsert / edit / remove link with modal — popup also has ↗ one-click new-tab toggleAlmost always<a href="…">
imageUploadUpload image — needs imageUpload.imageUploadUrl in optionsCMS, blog editor, any content with images<img>
fileUploadUpload file attachment — needs fileUpload.fileUploadUrlEmail composers, document editors<a> file link
tableInsert table via hover grid picker; floating toolbar for row/col opsData tables, comparison content, structured docs<table>
emojiEmoji picker popupChat, social, informal contentUnicode character
specialChars NEWSpecial characters grid (90+ symbols in 6 categories)Academic, scientific, legal, multilingual contentUnicode character
insertDateTimeDate/time picker popup — today, yesterday, or customJournals, meeting notes, task tracking<span class="ray-date-time">
markdownToggleToggle between rich text and raw MarkdownDev tools, technical writers, anyone comfortable in Markdown
importMarkdownImport a .md file into the editorPair with markdownToggle
exportMarkdownExport current content as a .md file downloadPair with markdownToggle
undoUndo (Ctrl+Z)Almost always
redoRedo (Ctrl+Y)Almost always
removeFormatStrip all inline formatting from selectionUseful when users paste in heavily styled content
showSourceView / edit raw HTML source in a modalPower users, devs, CMS admins
fullscreenToggle fullscreen mode (F11)Long-form writing, focus mode
printPrint editor content only (hides surrounding page)Document editors, invoices, tickets

All options

OptionTypeDefaultDescription
toolbarToolbarGroup[]full toolbarArray of arrays — each inner array is a button group. Omit to show all buttons.
theme'light'|'dark'|'auto''light''auto' follows the OS dark/light preference via prefers-color-scheme.
wordCountbooleanfalseRenders a live word + character count bar below the editor.
slashCommandsbooleantrueEnables the / command palette when the user types / on a blank line.
findReplacebooleantrueCtrl+F (find) and Ctrl+H (find & replace) panel.
markdownShortcutsbooleantrue## → H2, **text** → bold, *text* → italic, > → blockquote, --- → hr.
readOnlybooleanfalseStart in read-only mode. Toolbar hidden; contenteditable disabled.
onChange(html: string) => voidCallback fired on every content change — equivalent to editor.on('content:change', cb).
imageUpload{ imageUploadUrl, imageMaxSize? }Required to show imageUpload button. Endpoint must return { url: string }.
fileUpload{ fileUploadUrl, fileMaxSize? }Required to show fileUpload button.
mentions{ tag?, url? }Configure the @mention trigger and base URL.
overflowMenubooleanfalseCollapse overflow buttons into a … menu when toolbar is too narrow.
pluginsRayPlugin[][]Pre-install plugins at construction time (same as calling editor.use(p) for each).

API methods

All public methods
// ── Content ─────────────────────────────────────────────────────────
editor.getContent()             // → clean HTML string (strips UI chrome)
editor.setContent(html)          // set HTML — rebuilds code blocks, tables, task lists

// ── Theme ────────────────────────────────────────────────────────────
editor.setTheme('dark')           // 'light' | 'dark' | 'auto'

// ── Editing mode ─────────────────────────────────────────────────────
editor.setReadOnly(true)         // lock/unlock editing + toolbar

// ── Word count ───────────────────────────────────────────────────────
editor.getWordCount()            // → { words: number, chars: number }

// ── Export ───────────────────────────────────────────────────────────
editor.exportHtml()              // downloads filename.html with getContent() output
editor.exportText()              // downloads filename.txt (plain text, strips tags)

// ── Events ───────────────────────────────────────────────────────────
editor.on('content:change', (html) => {});
editor.on('selection:change', () => {});
editor.on('focus', () => {});
editor.on('blur', () => {});
editor.off('content:change', handler);

// ── Plugins ──────────────────────────────────────────────────────────
editor.use({ name: 'my-plugin', install(ed) { /* ... */ } });

// ── Slash commands ───────────────────────────────────────────────────
editor.registerSlashCommand({
  name: "Today's Date", icon: '📅',
  description: 'Insert today\'s date',
  action: () => document.execCommand('insertText', false, new Date().toLocaleDateString()),
});

// ── Cleanup ──────────────────────────────────────────────────────────
editor.destroy();                  // removes event listeners and DOM — call in SPA unmount

Events

Event namePayloadWhen it fires
content:changehtml: stringEvery time the editor content changes (debounced on input)
selection:changevoidEvery time the cursor/selection moves
focusvoidEditor area receives focus
blurvoidEditor area loses focus

Plugin API

Plugins let you extend the editor without forking it. A plugin is a plain object with name and install(editor).

my-plugin.ts
import type { RayPlugin } from '@rohanyeole/ray-editor';

const myPlugin: RayPlugin = {
  name: 'my-plugin',
  version: '1.0.0',

  install(editor) {
    // Listen to content changes
    editor.on('content:change', (html) => {
      console.log('changed:', html.length, 'chars');
    });

    // Register a custom slash command
    editor.registerSlashCommand({
      name: 'Timestamp', icon: '🕐', description: 'Insert current timestamp',
      action: () => document.execCommand('insertText', false, new Date().toISOString()),
    });
  },

  destroy() {
    // Cleanup — called by editor.destroy()
  },
};

// Install at construction time:
const editor = new RayEditor('editor', { plugins: [myPlugin] });

// Or after construction:
editor.use(myPlugin);

Slash commands

When slashCommands: true, typing / at the start of an empty block opens a searchable command palette. Built-in commands include all block types (headings, table, code block, task list, callout, etc.). Register your own:

Custom slash command
editor.registerSlashCommand({
  name: 'Template',                     // shown in palette
  icon: '📄',                           // emoji or text
  description: 'Insert a bug report template',
  action: () => {
    editor.setContent(`
      <h2>Bug Report</h2>
      <p><strong>Steps to reproduce:</strong></p>
      <ol><li></li></ol>
      <p><strong>Expected:</strong></p>
      <p><strong>Actual:</strong></p>
    `);
  },
});

Task lists new in v2.0.5

Click the ☑ Task List toolbar button to insert a checkbox list. Each checkbox is clickable inside the editor — no form element involved (uses CSS-styled spans for reliable cross-browser contenteditable behavior).

Clean output: getContent() serializes task items as:

Clean HTML output
<ul class="ray-task-list">
  <li data-type="taskItem" data-checked="false">Buy groceries</li>
  <li data-type="taskItem" data-checked="true">Call the bank</li>
</ul>

setContent() automatically rebuilds the span structure from this clean format, so you can store/retrieve freely.

Callout blocks new in v2.0.5

The 💬 Callout button opens a picker with four types: Info (ℹ️), Warning (⚠️), Success (✅), Error (❌). The callout body is fully editable rich text — you can bold, link, and list inside it.

Callout HTML structure (getContent output)
<div class="ray-callout ray-callout-info">
  <span class="ray-callout-icon">ℹ️</span>
  <div class="ray-callout-body">Your note here.</div>
</div>

Markdown mode

Add markdownToggle, importMarkdown, and exportMarkdown to your toolbar. Clicking MD converts the rich text content to Markdown in-place — you can edit the raw Markdown, then click MD again to convert back.

Markdown shortcuts (enabled with markdownShortcuts: true):

Type thisThen pressProduces
# / ## ###### Space after the hashH1 – H6 heading
**text**Space after the closing **<strong>text</strong>
*text* or _text_Space<em>text</em>
`code`Space<code>code</code>
> Start of line + space<blockquote>
---Enter<hr>

Code blocks

Click ⌥ Code Block or type /code to insert a fenced code block. Each block has a language selector (JavaScript, TypeScript, Python, HTML, CSS, JSON, Bash). Syntax highlighting is applied automatically via Highlight.js, loaded lazily from CDN on first use.

Output: getContent() strips all Highlight.js classes and outputs clean <pre data-lang="javascript"> — safe to store in a database and re-render via setContent().

Tip: Pasting code from GitHub, Stack Overflow, or VS Code is handled by the paste normalizer — the language is detected from class names like language-js, highlight-source-python, etc.

Tables

Click the Table button to open an 8×8 hover grid picker — hover to select dimensions, click to insert. Once the cursor is inside a table cell, a floating context toolbar appears above the table with:

  • Insert Row Above / Below
  • Insert Column Left / Right
  • Move Row Up / Down
  • Toggle Header Row (converts td ↔ th)
  • Delete Row / Column / Table

Tab moves to the next cell (creates a new row at the end). Shift+Tab moves backward.

Images

Add imageUpload to the toolbar and provide imageUpload: { imageUploadUrl: '/api/upload' } in options. The endpoint must accept a multipart/form-data POST with a file field and return { "url": "…" }.

After upload, the image is resizable by dragging the bottom-right handle. Click the ✎ Edit button on hover to set alt text, title, and an optional caption (wraps in <figure><figcaption>).

Paste normalization new in v2.0.5

HTML pasted from any external source is automatically cleaned before insertion. The pipeline runs on every paste:

  • Sandbox: Parsed in a detached DOMParser document — no scripts can execute
  • Strip forbidden: <script>, <iframe>, <object>, event attributes (onclick etc.), javascript: hrefs
  • Remove MSO: Word/Outlook conditional comments and proprietary styles stripped
  • GDocs unwrap: Google Docs outer wrapper <div class="docs-…"> removed, children kept
  • Tag morphing: <b><strong>, <i><em>
  • Span promotion: font-weight:700<strong>, italic span → <em>, underline → <u>, line-through → <s>, highlight bg → <mark>
  • Style filter: Only color, background-color, font-size, font-family, and text-align are kept on any element
  • Structure rebuild: <pre> → code block UI, <table> → table wrapper, task-list li → span checkboxes

Theming

The editor's color scheme is controlled via CSS custom properties on .ray-editor-wrapper. Set theme: 'dark' in options or call editor.setTheme('dark') at runtime.

Override CSS variables for custom branding
/* Override editor colors for your brand */
.ray-editor-wrapper {
  --ray-bg: #f0f9ff;
  --ray-toolbar-bg: #e0f2fe;
  --ray-border: #bae6fd;
  --ray-accent: #0284c7;
  --ray-text: #0c4a6e;
}

Keyboard shortcuts

ShortcutAction
Ctrl+BBold
Ctrl+IItalic
Ctrl+UUnderline
Ctrl+ZUndo
Ctrl+Y / Ctrl+Shift+ZRedo
Ctrl+FOpen Find panel
Ctrl+HOpen Find & Replace panel
Ctrl+Shift+VPaste as plain text (strips all HTML)
/ on empty lineOpen slash command palette
Tab in table cellMove to next cell
Shift+Tab in table cellMove to previous cell
Enter on empty last line of code blockExit code block, insert paragraph below
EscapeClose any open popup (slash palette, color picker, etc.)