npm install @rohanyeole/ray-editoryarn add @rohanyeole/ray-editorpnpm add @rohanyeole/ray-editor<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(...).
<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>
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
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.
| Key | What it does | When to add | Output HTML |
|---|---|---|---|
bold | Bold — wraps selection in <strong> | Almost always — core formatting | <strong> |
italic | Italic — wraps in <em> | Almost always — core formatting | <em> |
underline | Underline | Emails/documents; omit if it conflicts with link styling | <u> |
strikethrough | Strikethrough | Editorial, changelog, or markdown-flavored content | <s> |
highlight NEW | Highlight — yellow <mark> | Notes, study apps, any annotation use case | <mark> |
superscript | Superscript | Scientific/math content, footnotes | <sup> |
subscript | Subscript | Chemistry, math (H₂O etc.) | <sub> |
uppercase | Transform selection to UPPERCASE | Form inputs, headings — keeps underlying HTML text | text-transform |
lowercase | Transform to lowercase | Same as uppercase | text-transform |
toggleCase | Toggle Title Case / sentence case | Copywriting, blog titles | text-transform |
textColor | Text color — inline swatch popup | Rich CMS editors, email composers | style="color:…" |
backgroundColor | Background color of selected text | Same as textColor | style="background-color:…" |
fontSize v2.0.8 | Font size picker — popup with preset sizes (10–72 px), live preview on hover, custom input | Email editors, rich newsletters, anywhere font size matters | <span style="font-size:…px"> |
fonts | Font family dropdown | Design tools, email composers | <font face="…"> |
headings | Heading level (H1–H6, paragraph, blockquote) dropdown | Almost always — structure is fundamental | <h1>…<h6> |
blockquote | Block quote | Blogs, articles, citation-heavy content | <blockquote> |
callout NEW | Callout picker — Info / Warning / Success / Error | Documentation, wikis, onboarding flows — draws attention to key info | <div class="ray-callout ray-callout-info"> |
orderedList | Numbered list | Anywhere steps or ranked items are needed | <ol><li> |
unorderedList | Bullet list | Almost always — most common list type | <ul><li> |
taskList NEW | Interactive checkbox task list | To-do apps, project management, meeting notes | <li data-type="taskItem" data-checked="…"> |
indent | Increase list/text indent | Whenever you show orderedList or unorderedList | Padding-left / list nesting |
outdent | Decrease indent | Always paired with indent | Padding-left / list nesting |
textAlignment | Alignment dropdown (left, center, right, justify) | Email editors, articles with centered images/captions | align="…" |
hr | Horizontal rule | Long-form content, separating sections | <hr> |
codeBlock | Fenced code block — language selector + syntax highlighting | Dev docs, technical blogs, READMEs | <pre data-lang="js"> |
codeInline | Inline code — wraps selection in <code> | Whenever you have codeBlock — or for inline variable/key references | <code> |
link | Insert / edit / remove link with modal — popup also has ↗ one-click new-tab toggle | Almost always | <a href="…"> |
imageUpload | Upload image — needs imageUpload.imageUploadUrl in options | CMS, blog editor, any content with images | <img> |
fileUpload | Upload file attachment — needs fileUpload.fileUploadUrl | Email composers, document editors | <a> file link |
table | Insert table via hover grid picker; floating toolbar for row/col ops | Data tables, comparison content, structured docs | <table> |
emoji | Emoji picker popup | Chat, social, informal content | Unicode character |
specialChars NEW | Special characters grid (90+ symbols in 6 categories) | Academic, scientific, legal, multilingual content | Unicode character |
insertDateTime | Date/time picker popup — today, yesterday, or custom | Journals, meeting notes, task tracking | <span class="ray-date-time"> |
markdownToggle | Toggle between rich text and raw Markdown | Dev tools, technical writers, anyone comfortable in Markdown | — |
importMarkdown | Import a .md file into the editor | Pair with markdownToggle | — |
exportMarkdown | Export current content as a .md file download | Pair with markdownToggle | — |
undo | Undo (Ctrl+Z) | Almost always | — |
redo | Redo (Ctrl+Y) | Almost always | — |
removeFormat | Strip all inline formatting from selection | Useful when users paste in heavily styled content | — |
showSource | View / edit raw HTML source in a modal | Power users, devs, CMS admins | — |
fullscreen | Toggle fullscreen mode (F11) | Long-form writing, focus mode | — |
print | Print editor content only (hides surrounding page) | Document editors, invoices, tickets | — |
| Option | Type | Default | Description |
|---|---|---|---|
toolbar | ToolbarGroup[] | full toolbar | Array 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. |
wordCount | boolean | false | Renders a live word + character count bar below the editor. |
slashCommands | boolean | true | Enables the / command palette when the user types / on a blank line. |
findReplace | boolean | true | Ctrl+F (find) and Ctrl+H (find & replace) panel. |
markdownShortcuts | boolean | true | ## → H2, **text** → bold, *text* → italic, > → blockquote, --- → hr. |
readOnly | boolean | false | Start in read-only mode. Toolbar hidden; contenteditable disabled. |
onChange | (html: string) => void | — | Callback 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. |
overflowMenu | boolean | false | Collapse overflow buttons into a … menu when toolbar is too narrow. |
plugins | RayPlugin[] | [] | Pre-install plugins at construction time (same as calling editor.use(p) for each). |
// ── 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
| Event name | Payload | When it fires |
|---|---|---|
content:change | html: string | Every time the editor content changes (debounced on input) |
selection:change | void | Every time the cursor/selection moves |
focus | void | Editor area receives focus |
blur | void | Editor area loses focus |
Plugins let you extend the editor without forking it. A plugin is a plain object with name and install(editor).
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);
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:
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> `); }, });
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:
<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.
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.
<div class="ray-callout ray-callout-info"> <span class="ray-callout-icon">ℹ️</span> <div class="ray-callout-body">Your note here.</div> </div>
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 this | Then press | Produces |
|---|---|---|
# / ## … ###### | Space after the hash | H1 – 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> |
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().
language-js, highlight-source-python, etc.
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:
Tab moves to the next cell (creates a new row at the end). Shift+Tab moves backward.
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>).
HTML pasted from any external source is automatically cleaned before insertion. The pipeline runs on every paste:
<script>, <iframe>, <object>, event attributes (onclick etc.), javascript: hrefs<div class="docs-…"> removed, children kept<b> → <strong>, <i> → <em>font-weight:700 → <strong>, italic span → <em>, underline → <u>, line-through → <s>, highlight bg → <mark>color, background-color, font-size, font-family, and text-align are kept on any element<pre> → code block UI, <table> → table wrapper, task-list li → span checkboxesThe 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 editor colors for your brand */ .ray-editor-wrapper { --ray-bg: #f0f9ff; --ray-toolbar-bg: #e0f2fe; --ray-border: #bae6fd; --ray-accent: #0284c7; --ray-text: #0c4a6e; }
| Shortcut | Action |
|---|---|
| Ctrl+B | Bold |
| Ctrl+I | Italic |
| Ctrl+U | Underline |
| Ctrl+Z | Undo |
| Ctrl+Y / Ctrl+Shift+Z | Redo |
| Ctrl+F | Open Find panel |
| Ctrl+H | Open Find & Replace panel |
| Ctrl+Shift+V | Paste as plain text (strips all HTML) |
| / on empty line | Open slash command palette |
| Tab in table cell | Move to next cell |
| Shift+Tab in table cell | Move to previous cell |
| Enter on empty last line of code block | Exit code block, insert paragraph below |
| Escape | Close any open popup (slash palette, color picker, etc.) |