@remyxjs/react
v1.3.0-beta
Published
React components and hooks for the Remyx rich-text editor
Downloads
606
Maintainers
Readme
@remyxjs/react
A feature-rich WYSIWYG editor for React, built on the framework-agnostic @remyxjs/core engine. Configurable toolbar, menu bar, markdown support, theming, file uploads, and a plugin system.
Visit us at SmashJaw.com
or the RemyxJS official site: Remyxjs.com
Packages
| Package | Version | Description |
| -------------------------------------------------------------- | ---------- | ----------------------------------------------------------------------- |
| @remyxjs/core | 1.3.0-beta | Framework-agnostic engine, commands, plugins, utilities, and CSS themes |
| @remyxjs/react | 1.3.0-beta | React components, hooks, and TypeScript declarations |
Use @remyxjs/core directly if building a wrapper for another framework (Vue, Svelte, Angular, vanilla JS).
Table of Contents
- Installation
- Quick Start
- Features
- Props
- Config File
- Toolbar
- Menu Bar
- Multiple Editors
- Fonts
- Status Bar
- Theming
- Paste Handling
- File Uploads
- Output Formats
- Attaching to Existing Elements
- Plugins
- Read-Only Mode
- Error Handling
- Engine Access
- Import & Export Documents
- Sanitization
- Keyboard Shortcuts
- Heading Level Offset
- Floating Toolbar & Context Menu
- Autosave
- UX/UI Features
- Collaboration
- Form Integration
- Exports
- TypeScript
- Using
@remyxjs/coreDirectly
Installation
npm install @remyxjs/core @remyxjs/reactThen scaffold the remyxjs/ directory in your project root:
npx remyxjs initThis creates remyxjs/config/, remyxjs/plugins/, and remyxjs/themes/ with built-in presets, plugins, and theme CSS files. The editor discovers these at runtime via Vite's import.meta.glob(). See the setup guide for details and CLI options (--force, --no-plugins, --no-themes).
Import both stylesheets in your app entry point:
import '@remyxjs/core/style.css'; // theme variables, light/dark themes
import '@remyxjs/react/style.css'; // component styles (toolbar, modals, etc.)Quick Start
import { useState } from 'react';
import { RemyxEditor } from '@remyxjs/react';
import '@remyxjs/core/style.css';
import '@remyxjs/react/style.css';
function App() {
const [content, setContent] = useState('');
return (
<RemyxEditor
value={content}
onChange={setContent}
placeholder="Start typing..."
height={400}
/>
);
}Uncontrolled Mode
<RemyxEditor
defaultValue="<p>Initial content</p>"
onChange={(html) => console.log(html)}
/>Markdown Mode
const [markdown, setMarkdown] = useState(
'# Hello\n\nStart typing...',
);
<RemyxEditor
value={markdown}
onChange={setMarkdown}
outputFormat="markdown"
/>;With Upload Handler
<RemyxEditor
value={content}
onChange={setContent}
uploadHandler={async (file) => {
const formData = new FormData();
formData.append('file', file);
const res = await fetch('/api/upload', {
method: 'POST',
body: formData,
});
const { url } = await res.json();
return url;
}}
/>Dark Theme with Google Fonts
<RemyxEditor
theme="dark"
googleFonts={['Inter', 'Fira Code', 'Merriweather']}
height={500}
/>Features
- Rich text editing — bold, italic, underline, strikethrough, subscript, superscript, headings, lists, blockquotes, code blocks, tables (with sorting, filtering, formulas, cell formatting, resize handles, and clipboard interop), and horizontal rules
- Toolbar — fully configurable with presets, helper functions, and per-item theming
- Menu bar — application-style menus (File, Edit, View, Insert, Format) with submenus and auto-deduplication
- Markdown — bidirectional HTML/Markdown conversion, toggle between modes, and auto-detection on paste
- Theming — light/dark mode, 4 built-in presets (Ocean, Forest, Sunset, Rose), full CSS variable customization
- File uploads — images and attachments via drag-and-drop, paste, or toolbar with pluggable upload handlers (S3, R2, GCS, custom)
- Fonts — custom font lists, Google Fonts auto-loading, and helper functions
- Code block syntax highlighting — 11 languages with auto-detection, theme-aware colors, language selector dropdown, line numbers toggle, copy-to-clipboard, inline code highlighting, and extensible language registry
- Enhanced tables — sortable columns, multi-column sort, filterable rows, inline formulas, cell formatting, column/row resize, sticky headers, and Excel/Sheets clipboard interop via
TablePlugin - Template system —
{{merge_tag}}chips,{{#if}}/{{#each}}blocks, live preview, 5 pre-built templates, custom template library viaTemplatePlugin - Keyboard-first editing — Vim (normal/insert/visual), Emacs keybindings, custom key map, multi-cursor (
Cmd+D), smart auto-pairing, jump-to-heading viaKeyboardPlugin - Drag-and-drop content — Drop zones, cross-editor drag, file/image/rich-text drops, block reorder with ghost preview via
DragDropPlugin - Advanced link management — Link previews, broken link detection, auto-linking, click analytics, bookmark anchors via
LinkPlugin - Callouts & alerts — 7 built-in callout types with custom type registration, collapsible toggle, nested content, and GitHub-flavored alert syntax auto-conversion via
CalloutPlugin - Comments & annotations — Inline comment threads via
CommentsPlugin,CommentsPanelsidebar with thread cards/replies/actions,useCommentshook for reactive state, @mention parsing, resolved/unresolved state, comment-only mode, import/export - Real-time collaboration — CRDT-based co-editing with live cursors, presence indicators, offline-first sync, configurable transport (WebSocket, WebRTC, or custom) via
CollaborationPlugin,useCollaborationhook,CollaborationBarcomponent - Plugins —
createPlugin()API with hooks for commands, toolbar items, status bar items, and context menus - Config file — JSON config files in
remyxjs/config/with 5 built-in presets (default,minimal,blog-editor,full-toolbar,toolbar-and-menu) - Block-based editing — block-level toolbar with type conversion, drag-to-reorder, collapsible sections, block grouping,
BlockTemplatePluginwith built-in templates - Mobile & touch — touch floating toolbar, swipe indent/outdent, long-press context menu with haptic feedback, pinch-to-zoom, responsive toolbar overflow, virtual keyboard-aware layout
- Multi-instance — unlimited editors per page with full isolation (state, events, DOM, modals),
EditorBusfor inter-editor communication,SharedResourcesfor memory-efficient shared schemas and icons - Import/Export — PDF, DOCX, Markdown, CSV, and HTML
- Paste cleaning — intelligent cleanup from Word, Google Docs, LibreOffice, Pages, and raw markdown
- Command palette — searchable overlay with all editor commands, opened via
Mod+Shift+Por toolbar button - Keyboard shortcuts — customizable with sensible defaults
- Accessibility — semantic HTML, ARIA attributes, keyboard navigation, and focus management
Props
| Prop | Type | Default | Description |
| --------------------- | ---------------------------------------------------------------- | ------------- | ----------------------------------------------------------------------------------------- |
| config | string | — | Config file name from remyxjs/config/ (e.g., 'default', 'minimal', 'blog-editor') |
| value | string | — | Controlled content (HTML or Markdown) |
| defaultValue | string | — | Initial content for uncontrolled mode |
| onChange | (content: string) => void | — | Called when content changes |
| outputFormat | 'html' \| 'markdown' | 'html' | Format passed to onChange |
| toolbar | string[][] | Full toolbar | Custom toolbar layout |
| menuBar | boolean \| MenuBarConfig[] | — | Enable menu bar |
| theme | 'light' \| 'dark' \| 'ocean' \| 'forest' \| 'sunset' \| 'rose' | 'light' | Editor theme |
| placeholder | string | '' | Placeholder text |
| height | number | 300 | Editor height in px |
| minHeight | number | — | Minimum height |
| maxHeight | number | — | Maximum height (scrolls) |
| readOnly | boolean | false | Disable editing |
| fonts | string[] | Built-in list | Custom font families |
| googleFonts | string[] | — | Google Font families to auto-load |
| statusBar | 'bottom' \| 'top' \| 'popup' \| false | 'bottom' | Word/character count position |
| customTheme | object | — | CSS variable overrides |
| toolbarItemTheme | object | — | Per-item toolbar styling |
| floatingToolbar | boolean | true | Show toolbar on text selection |
| contextMenu | boolean | true | Show right-click context menu |
| commandPalette | boolean | true | Enable command palette (Mod+Shift+P or toolbar button) |
| autosave | boolean \| AutosaveConfig | false | Enable autosave with optional config (storage provider, interval, key) |
| emptyState | boolean \| ReactNode | false | Show empty state when editor has no content (true for default, or custom React node) |
| breadcrumb | boolean | false | Show breadcrumb bar with DOM path to current selection |
| minimap | boolean | false | Show minimap preview on right edge |
| splitViewFormat | 'html' \| 'markdown' | 'html' | Format for split view preview pane |
| customizableToolbar | boolean | false | Enable drag-and-drop toolbar button rearrangement |
| onToolbarChange | (order: string[]) => void | — | Called when toolbar order changes via drag |
| plugins | Plugin[] | — | Custom plugins |
| uploadHandler | (file: File) => Promise<string> | — | File upload handler |
| shortcuts | object | — | Keyboard shortcut overrides |
| sanitize | object | — | HTML sanitization options |
| attachTo | React.RefObject | — | Attach to an existing element |
| onReady | (engine) => void | — | Called when editor initializes |
| onFocus | () => void | — | Called on focus |
| onBlur | () => void | — | Called on blur |
| className | string | '' | CSS class for wrapper |
| style | object | — | Inline styles for wrapper |
Config File
Each <RemyxEditor /> instance is driven by a JSON config file in remyxjs/config/. The config prop references the file name (without .json).
Built-in Presets
5 config presets ship out of the box:
<RemyxEditor config="default" /> {/* Full-featured editor */}
<RemyxEditor config="minimal" /> {/* Stripped-down toolbar */}
<RemyxEditor config="blog-editor" /> {/* Blog-focused layout */}
<RemyxEditor config="full-toolbar" /> {/* All toolbar items */}
<RemyxEditor config="toolbar-and-menu" /> {/* Toolbar + menu bar */}Custom Config
Create a JSON file in remyxjs/config/ (e.g., remyxjs/config/my-editor.json):
{
"theme": "dark",
"placeholder": "Start writing...",
"height": 400,
"toolbar": [
["bold", "italic", "underline"],
["link", "image"]
],
"plugins": {
"syntax-highlight": true,
"table": true
}
}Then use it:
<RemyxEditor config="my-editor" />Config Resolution Priority
Values merge with this priority (highest first):
- Component props — always win (e.g.,
<RemyxEditor config="default" theme="dark" />) - Config file — values from the JSON file in
remyxjs/config/ - Built-in defaults —
theme: 'light',height: 300, etc.
Toolbar
Default Toolbar
[
['undo', 'redo'],
['headings', 'fontFamily', 'fontSize'],
['bold', 'italic', 'underline', 'strikethrough'],
['foreColor', 'backColor'],
['alignLeft', 'alignCenter', 'alignRight', 'alignJustify'],
['orderedList', 'unorderedList', 'taskList'],
['outdent', 'indent'],
[
'link',
'image',
'table',
'embedMedia',
'blockquote',
'codeBlock',
'horizontalRule',
],
['subscript', 'superscript'],
[
'findReplace',
'toggleMarkdown',
'sourceMode',
'export',
'fullscreen',
],
];Each inner array is a visual group separated by a divider.
Custom Toolbar via Props
<RemyxEditor
toolbar={[
['bold', 'italic', 'underline'],
['orderedList', 'unorderedList'],
['link', 'image'],
]}
/>Toolbar Presets
import { TOOLBAR_PRESETS } from '@remyxjs/react';
<RemyxEditor toolbar={TOOLBAR_PRESETS.full} /> // all items including plugin commands (default)
<RemyxEditor toolbar={TOOLBAR_PRESETS.rich} /> // all features with callout, math, toc, bookmark, merge tag
<RemyxEditor toolbar={TOOLBAR_PRESETS.standard} /> // common editing without plugins
<RemyxEditor toolbar={TOOLBAR_PRESETS.minimal} /> // headings, basics, lists, links, images
<RemyxEditor toolbar={TOOLBAR_PRESETS.bare} /> // bold, italic, underline onlyToolbar Helper Functions
removeToolbarItems(config, itemsToRemove)
import { DEFAULT_TOOLBAR, removeToolbarItems } from '@remyxjs/react';
const toolbar = removeToolbarItems(DEFAULT_TOOLBAR, [
'image',
'table',
'embedMedia',
'export',
]);addToolbarItems(config, items, options?)
import { TOOLBAR_PRESETS, addToolbarItems } from '@remyxjs/react';
addToolbarItems(TOOLBAR_PRESETS.minimal, ['fullscreen']); // append as new group
addToolbarItems(TOOLBAR_PRESETS.minimal, 'fullscreen', { group: -1 }); // add to last group
addToolbarItems(TOOLBAR_PRESETS.minimal, 'taskList', {
after: 'unorderedList',
});
addToolbarItems(TOOLBAR_PRESETS.minimal, 'strikethrough', {
before: 'underline',
});createToolbar(items)
Build a toolbar from a flat list — items are auto-grouped by category:
import { createToolbar } from '@remyxjs/react';
const toolbar = createToolbar([
'bold',
'italic',
'underline',
'headings',
'link',
'image',
'orderedList',
'unorderedList',
'fullscreen',
]);
// Groups by category: [['headings'], ['bold', 'italic', 'underline'], ['orderedList', 'unorderedList'], ['link', 'image'], ['fullscreen']]Available Toolbar Items
| Item | Type | Description |
| -------------------- | ------------ | -------------------------------------------------------------------- |
| undo | Button | Undo last action |
| redo | Button | Redo last action |
| headings | Dropdown | Block type (Normal, H1–H6) |
| fontFamily | Dropdown | Font family selector |
| fontSize | Dropdown | Font size selector |
| bold | Button | Bold |
| italic | Button | Italic |
| underline | Button | Underline |
| strikethrough | Button | Strikethrough |
| subscript | Button | Subscript |
| superscript | Button | Superscript |
| foreColor | Color Picker | Text color |
| backColor | Color Picker | Background color |
| alignLeft | Button | Align left |
| alignCenter | Button | Align center |
| alignRight | Button | Align right |
| alignJustify | Button | Justify |
| orderedList | Button | Numbered list |
| unorderedList | Button | Bulleted list |
| taskList | Button | Task/checkbox list |
| indent | Button | Increase indent |
| outdent | Button | Decrease indent |
| link | Button | Insert/edit link |
| image | Button | Insert image |
| attachment | Button | Attach file |
| importDocument | Button | Import (PDF, DOCX, MD, CSV) |
| table | Button | Insert table |
| embedMedia | Button | Embed video/media |
| blockquote | Button | Block quote |
| codeBlock | Button | Code block |
| horizontalRule | Button | Horizontal divider |
| findReplace | Button | Find and replace |
| toggleMarkdown | Button | Toggle markdown mode |
| sourceMode | Button | View/edit HTML source |
| export | Button | Export (PDF, MD, DOCX) |
| commandPalette | Button | Open command palette |
| fullscreen | Button | Toggle fullscreen |
| insertCallout | Button | Insert callout block |
| insertMath | Button | Insert math equation |
| insertToc | Button | Insert table of contents |
| insertBookmark | Button | Insert bookmark anchor |
| insertMergeTag | Button | Insert merge tag |
| toggleAnalytics | Button | Toggle analytics panel |
| toggleSpellcheck | Button | Toggle spellcheck |
| checkGrammar | Button | Run grammar check |
| addComment | Button | Add inline comment on selection |
| removeFormat | Button | Remove all inline formatting |
| distractionFree | Button | Toggle distraction-free mode (Mod+Shift+D) |
| toggleSplitView | Button | Toggle side-by-side preview (Mod+Shift+V) |
| typography | Dropdown | Typography controls (line height, letter spacing, paragraph spacing) |
| lineHeight | Dropdown | Line height adjustment |
| letterSpacing | Dropdown | Letter spacing adjustment |
| paragraphSpacing | Dropdown | Paragraph spacing adjustment |
| startCollaboration | Button | Start real-time collaboration session |
| stopCollaboration | Button | Stop collaboration session |
Menu Bar
Add an application-style menu bar above the toolbar. Both the toolbar and menu bar display all their items — the menu bar is an additional navigation layer, not a replacement for the toolbar.
Enable
<RemyxEditor menuBar={true} />Default menus:
| Menu | Items | | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | File | Import Document, Export Document | | Edit | Undo, Redo, Find & Replace | | View | Fullscreen, Distraction-Free Mode, Split View, Toggle Markdown, Source Mode, Toggle Analytics | | Insert | Link, Image, Table, Attachment, Embed Media, Blockquote, Code Block, Horizontal Rule, Insert Callout, Insert Math, Insert TOC, Insert Bookmark, Insert Merge Tag, Add Comment | | Format | Bold, Italic, Underline, Strikethrough, Subscript, Superscript, Alignment, Lists, Colors, Typography (Line Height, Letter Spacing, Paragraph Spacing), Remove Formatting |
Custom Menu Bar
<RemyxEditor
menuBar={[
{ label: 'File', items: ['importDocument', 'export'] },
{ label: 'Edit', items: ['undo', 'redo', '---', 'findReplace'] },
{
label: 'Format',
items: [
'bold',
'italic',
'underline',
'---',
{
label: 'Alignment',
items: [
'alignLeft',
'alignCenter',
'alignRight',
'alignJustify',
],
},
],
},
]}
/>Each menu object has a label (string) and items array of command names, '---' separators, or nested { label, items } submenu objects. Any command from the Available Toolbar Items table works as a menu item.
Menu Bar with Config File
In your config JSON (remyxjs/config/my-editor.json):
{
"menuBar": [
{ "label": "Edit", "items": ["undo", "redo"] },
{ "label": "Format", "items": ["bold", "italic", "underline"] }
],
"toolbar": [["headings", "fontFamily", "fontSize"]]
}Toolbar and Menu Bar
When both the toolbar and menu bar are enabled, the full toolbar is preserved. The menu bar provides an additional navigation layer — it does not remove items from the toolbar. Both surfaces can be used together to give users maximum flexibility.
Menu Bar Behavior
- Click a trigger to open, hover to switch between open menus
- Escape or click outside to close
- Submenus open on hover with a chevron
- Toggle commands show active state, modal commands open their dialogs
- Inherits the editor's theme and custom theme variables
Multiple Editors
Multiple <RemyxEditor /> instances work on the same page without any additional setup. Each editor is fully isolated.
Basic Usage
<RemyxEditor placeholder="Editor 1..." height={300} />
<RemyxEditor placeholder="Editor 2..." height={300} />
<RemyxEditor placeholder="Editor 3..." height={200} />What's Isolated Per Instance
| Feature | Isolation |
| ---------------------- | ----------------------------------------------------------------- |
| Content & state | Separate undo/redo history, content, and selection |
| Toolbar | Independent dropdowns, color pickers, and active states |
| Menu bar | Menus open/close independently per editor |
| Modals | Each editor has its own modals (link, image, table, find/replace) |
| Floating toolbar | Appears only for the editor with an active selection |
| Context menu | Scoped to the clicked editor |
| Fullscreen | Each editor enters/exits fullscreen independently |
| Plugins | Per-editor plugin instances with separate state |
| Events | Separate EventBus per instance |
| Keyboard shortcuts | Fire only for the focused editor |
Mixed Configurations
<RemyxEditor config="default" menuBar={true} height={400} />
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
<RemyxEditor config="minimal" />
<RemyxEditor config="blog-editor" />
</div>
<RemyxEditor config="default" theme="dark" placeholder="Standalone editor" />No hard limit on editor count. Performance scales linearly. All runtime state is per-instance.
Font Configuration
Custom Font List
<RemyxEditor
fonts={['Arial', 'Georgia', 'Courier New', 'My Custom Font']}
/>Google Fonts
<RemyxEditor googleFonts={['Roboto', 'Open Sans', 'Lato', 'Montserrat']} />
// With custom fonts
<RemyxEditor fonts={['Arial', 'Georgia']} googleFonts={['Poppins', 'Inter']} />
// Specific weights
<RemyxEditor googleFonts={['Roboto:wght@400;700']} />Google Fonts are auto-loaded via CDN and merged into the font dropdown.
Font Helper Functions
import {
DEFAULT_FONTS,
removeFonts,
addFonts,
loadGoogleFonts,
} from '@remyxjs/react';
// Remove fonts
const fonts = removeFonts(DEFAULT_FONTS, ['Comic Sans MS', 'Impact']);
// Add fonts (append by default, or { position: 'start' } to prepend)
const fonts = addFonts(DEFAULT_FONTS, ['My Custom Font']);
// Load Google Fonts programmatically
loadGoogleFonts(['Roboto', 'Lato']);Status Bar
The status bar displays word and character counts.
<RemyxEditor statusBar="bottom" /> {/* below content (default) */}
<RemyxEditor statusBar="top" /> {/* above content */}
<RemyxEditor statusBar="popup" /> {/* toolbar button with popover */}
<RemyxEditor statusBar={false} /> {/* hidden */}Theming
<RemyxEditor theme="light" /> // default
<RemyxEditor theme="dark" />Custom Themes
Override CSS variables using createTheme() or raw variable names. Custom themes layer on top of the base theme — only specify what you want to change.
import { createTheme } from '@remyxjs/react';
const brandTheme = createTheme({
bg: '#1a1a2e',
text: '#e0e0e0',
primary: '#e94560',
primaryHover: '#c81e45',
toolbarBg: '#16213e',
toolbarIcon: '#94a3b8',
toolbarIconActive: '#e94560',
radius: '12px',
contentFontSize: '18px',
});
<RemyxEditor theme="dark" customTheme={brandTheme} />;Or pass raw CSS variables directly:
<RemyxEditor
customTheme={{ '--rmx-primary': '#e94560', '--rmx-bg': '#1a1a2e' }}
/>Theme Presets
All 6 built-in themes are available via the theme prop:
<RemyxEditor theme="light" /> {/* Default — clean white */}
<RemyxEditor theme="dark" /> {/* Neutral dark */}
<RemyxEditor theme="ocean" /> {/* Deep blue */}
<RemyxEditor theme="forest" /> {/* Green earth-tone */}
<RemyxEditor theme="sunset" /> {/* Warm orange/amber */}
<RemyxEditor theme="rose" /> {/* Soft pink */}Override individual variables on top of any theme with customTheme:
import { createTheme } from '@remyxjs/react';
<RemyxEditor
theme="ocean"
customTheme={createTheme({ primary: '#ff6b6b' })}
/>;Available Theme Variables
| Key | CSS Variable | Description |
| --------------------- | ----------------------------- | --------------------------- |
| bg | --rmx-bg | Editor background |
| text | --rmx-text | Primary text color |
| textSecondary | --rmx-text-secondary | Muted text color |
| border | --rmx-border | Border color |
| borderSubtle | --rmx-border-subtle | Subtle border color |
| toolbarBg | --rmx-toolbar-bg | Toolbar background |
| toolbarBorder | --rmx-toolbar-border | Toolbar border |
| toolbarButtonHover | --rmx-toolbar-button-hover | Button hover background |
| toolbarButtonActive | --rmx-toolbar-button-active | Button active background |
| toolbarIcon | --rmx-toolbar-icon | Icon color |
| toolbarIconActive | --rmx-toolbar-icon-active | Active icon color |
| primary | --rmx-primary | Primary accent color |
| primaryHover | --rmx-primary-hover | Primary hover color |
| primaryLight | --rmx-primary-light | Light primary (backgrounds) |
| focusRing | --rmx-focus-ring | Focus outline color |
| selection | --rmx-selection | Text selection color |
| danger | --rmx-danger | Error/danger color |
| dangerLight | --rmx-danger-light | Light danger color |
| placeholder | --rmx-placeholder | Placeholder text color |
| modalBg | --rmx-modal-bg | Modal background |
| modalOverlay | --rmx-modal-overlay | Modal overlay |
| statusbarBg | --rmx-statusbar-bg | Status bar background |
| statusbarText | --rmx-statusbar-text | Status bar text |
| fontFamily | --rmx-font-family | UI font stack |
| fontSize | --rmx-font-size | UI font size |
| contentFontSize | --rmx-content-font-size | Content font size |
| contentLineHeight | --rmx-content-line-height | Content line height |
| radius | --rmx-radius | Border radius |
| radiusSm | --rmx-radius-sm | Small border radius |
| spacingXs | --rmx-spacing-xs | Extra small spacing |
| spacingSm | --rmx-spacing-sm | Small spacing |
| spacingMd | --rmx-spacing-md | Medium spacing |
Per-Item Toolbar Theming
Style individual toolbar buttons independently using toolbarItemTheme.
import { createToolbarItemTheme } from '@remyxjs/react';
const itemTheme = createToolbarItemTheme({
bold: {
color: '#e11d48',
activeColor: '#be123c',
activeBackground: '#ffe4e6',
borderRadius: '50%',
},
italic: {
color: '#7c3aed',
activeColor: '#6d28d9',
activeBackground: '#ede9fe',
},
underline: {
color: '#0891b2',
activeColor: '#0e7490',
activeBackground: '#cffafe',
},
_separator: { color: '#c4b5fd', width: '2px' },
});
<RemyxEditor toolbarItemTheme={itemTheme} />;Both customTheme and toolbarItemTheme can be used together — customTheme sets global styles, toolbarItemTheme overrides specific items.
Per-item style properties:
| Key | CSS Variable | Description |
| ------------------ | ----------------------- | ---------------------- |
| color | --rmx-tb-color | Icon/text color |
| background | --rmx-tb-bg | Default background |
| hoverColor | --rmx-tb-hover-color | Color on hover |
| hoverBackground | --rmx-tb-hover-bg | Background on hover |
| activeColor | --rmx-tb-active-color | Color when active |
| activeBackground | --rmx-tb-active-bg | Background when active |
| border | --rmx-tb-border | Border shorthand |
| borderRadius | --rmx-tb-radius | Border radius |
| size | --rmx-tb-size | Button width & height |
| iconSize | --rmx-tb-icon-size | Icon size |
| padding | --rmx-tb-padding | Button padding |
| opacity | --rmx-tb-opacity | Button opacity |
Separator properties (via _separator key):
| Key | CSS Variable | Description |
| -------- | --------------------- | ---------------- |
| color | --rmx-tb-sep-color | Separator color |
| width | --rmx-tb-sep-width | Separator width |
| height | --rmx-tb-sep-height | Separator height |
| margin | --rmx-tb-sep-margin | Separator margin |
Paste & Auto-Conversion
The editor cleans and normalizes pasted content automatically.
| Source | Handling |
| ------------------ | ----------------------------------------------------------------------------------------------------------- |
| Microsoft Word | Strips Office XML, mso-* styles, conditional comments. Converts Word-style lists to proper <ul>/<ol>. |
| Google Docs | Removes internal IDs/classes. Converts styled spans to semantic tags (<strong>, <em>, <s>). |
| LibreOffice | Strips namespace tags and auto-generated class names. |
| Apple Pages | Removes iWork-specific attributes. |
| Markdown | Auto-detects and converts to rich HTML (headings, lists, bold, links, code fences, etc.). |
| Plain text | Wraps in <p> tags with <br> for line breaks. |
| Images | Inserted as base64 data URIs, or uploaded via uploadHandler if configured. |
All paste paths (keyboard, drag-and-drop, context menu) share the same pipeline: HTML goes through cleanPastedHTML() then Sanitizer.sanitize(). Plain text is checked by looksLikeMarkdown() and converted if detected.
import { cleanPastedHTML, looksLikeMarkdown } from '@remyxjs/react';
const clean = cleanPastedHTML(dirtyHtml);
const isMarkdown = looksLikeMarkdown(text);File Uploads
The uploadHandler prop controls where images and file attachments are stored. It receives a File and returns a Promise<string> with the file URL.
Without an uploadHandler, images are inserted as base64 data URIs and the attachment upload tab is disabled.
Custom Server
<RemyxEditor
uploadHandler={async (file) => {
const formData = new FormData();
formData.append('file', file);
const res = await fetch('/api/upload', {
method: 'POST',
body: formData,
});
const { url } = await res.json();
return url;
}}
/>S3 / R2 / GCS (Pre-signed URL)
<RemyxEditor
uploadHandler={async (file) => {
const res = await fetch('/api/presign', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
filename: file.name,
contentType: file.type,
}),
});
const { uploadUrl, publicUrl } = await res.json();
await fetch(uploadUrl, {
method: 'PUT',
headers: { 'Content-Type': file.type },
body: file,
});
return publicUrl;
}}
/>Upload Paths
The handler is called consistently across all upload surfaces:
| Path | Result |
| ------------------------------- | -------------------------------------- |
| Image toolbar (Upload tab) | Inserted as <img> |
| Attachment toolbar (Upload tab) | Inserted as attachment chip |
| Drag & drop image/file | Image as <img>, others as attachment |
| Paste image/file | Same as drag & drop |
Output Formats
<RemyxEditor outputFormat="html" onChange={(html) => console.log(html)} />
<RemyxEditor outputFormat="markdown" onChange={(md) => console.log(md)} />Conversion utilities for standalone use:
import { htmlToMarkdown, markdownToHtml } from '@remyxjs/react';
const md = htmlToMarkdown('<h1>Hello</h1><p>World</p>');
const html = markdownToHtml('# Hello\n\nWorld');Attaching to Existing Elements
Enhance an existing <textarea> or <div> with a full WYSIWYG editor.
Textarea
const textareaRef = useRef(null);
<textarea ref={textareaRef} defaultValue="Initial content" />
<RemyxEditor attachTo={textareaRef} outputFormat="markdown" height={400} />The textarea is hidden and its value stays in sync for form submission.
Div
const divRef = useRef(null);
<div ref={divRef}>
<h2>Existing content</h2>
<p>This will become editable.</p>
</div>
<RemyxEditor attachTo={divRef} theme="dark" height={400} />useRemyxEditor Hook
For lower-level control:
import { useRemyxEditor } from '@remyxjs/react';
function MyEditor() {
const targetRef = useRef(null);
const { engine, containerRef, editableRef, ready } = useRemyxEditor(
targetRef,
{
onChange: (content) => console.log(content),
placeholder: 'Type here...',
theme: 'light',
height: 400,
},
);
return <textarea ref={targetRef} />;
}Returns engine (EditorEngine), containerRef, editableRef, and ready (boolean).
useEditorEngine Hook
The lowest-level hook — manages just the engine lifecycle:
import { useEditorEngine } from '@remyxjs/react';
function CustomEditor() {
const editorRef = useRef(null);
const engine = useEditorEngine(editorRef, {
outputFormat: 'html',
plugins: [],
});
return <div ref={editorRef} contentEditable />;
}Plugins
Plugins
14 optional plugins are available as drag-and-drop folders in remyxjs/plugins/:
WordCountPlugin — word/character counts in the status bar
PlaceholderPlugin — placeholder text when empty
AutolinkPlugin — auto-converts typed URLs to links
SyntaxHighlightPlugin — automatic syntax highlighting for code blocks with language detection, theme-aware colors,
setCodeLanguage/getCodeLanguage/toggleLineNumberscommands, copy-to-clipboard button, inline<code>mini-highlighting, and extensible language registry (registerLanguage/unregisterLanguage). When active, a language selector dropdown appears on focused code blocks.TablePlugin — enhanced table features including column/row resize handles, click-to-sort on header cells (single + multi-column with Shift), per-column filter dropdowns, inline cell formulas (SUM, AVERAGE, COUNT, MIN, MAX, IF, CONCAT), cell formatting (number, currency, percentage, date), and sticky header rows. Adds 6 new commands:
toggleHeaderRow,sortTable,filterTable,clearTableFilters,formatCell,evaluateFormulas. Context menu adds Toggle Header Row, Format Cell options, and Clear Filters when right-clicking in a table.CalloutPlugin — styled callout blocks with 7 built-in types (info, warning, error, success, tip, note, question), custom type registration, collapsible toggle, nested content, GFM alert syntax auto-conversion. Adds 4 commands:
insertCallout,removeCallout,changeCalloutType,toggleCalloutCollapse. Context menu adds "Insert Callout" and "Remove Callout".CommentsPlugin — inline comment threads on selected text, resolved/unresolved state with visual indicators, @mention parsing, reply threads, comment-only mode (read-only editor with annotation support), import/export, DOM sync via MutationObserver. Adds 6 new commands:
addComment,deleteComment,resolveComment,replyToComment,editComment,navigateToComment. Context menu adds "Add Comment" when text is selected.LinkPlugin — link preview tooltips (via
onUnfurlcallback), broken link detection with periodic scanning andrmx-link-brokenindicators, auto-linking of typed URLs/emails/phone numbers on Space/Enter, link click analytics (onLinkClick), bookmark anchors (insertBookmark,linkToBookmark,getBookmarks,removeBookmark),scanBrokenLinks/getBrokenLinkscommands.TemplatePlugin —
{{merge_tag}}syntax with visual chips,{{#if condition}}...{{/if}}conditional blocks,{{#each items}}...{{/each}}repeatable sections, live preview with sample data, 5 pre-built templates (email, invoice, letter, report, newsletter),registerTemplate/unregisterTemplate/getTemplateLibrary/getTemplateAPIs,exportTemplateJSON export. Adds 6 commands:insertMergeTag,loadTemplate,previewTemplate,exitPreview,exportTemplate,getTemplateTags.KeyboardPlugin — Vim mode (normal/insert/visual with h/j/k/l/w/b motions), Emacs keybindings (Ctrl+A/E/F/B/K/Y), custom keybinding map, smart bracket/quote auto-pairing with wrap-selection, multi-cursor (
Cmd+D), jump-to-heading (Cmd+Shift+G). Adds 5 commands:setKeyboardMode,getVimMode,jumpToHeading,getHeadings,selectNextOccurrence.DragDropPlugin — drop zone overlays with visual guides, cross-editor drag via
text/x-remyx-blocktransfer, external file/image/rich-text drops, block reorder with ghost preview and drop indicator,onDrop/onFileDropcallbacks. Adds 2 commands:moveBlockUp,moveBlockDown(Cmd+Shift+Arrow).MathPlugin — LaTeX/KaTeX math rendering with
$...$inline and$$...$$block syntax, pluggablerenderMath(latex, displayMode)callback, 40+ symbol palette across 4 categories (Greek, Operators, Arrows, Common), auto equation numbering,latexToMathML()conversion,parseMathExpressions()detection. Adds 6 commands:insertMath,editMath,insertSymbol,getSymbolPalette,getMathElements,copyMathAs.TocPlugin — auto-generated table of contents from H1-H6 heading hierarchy, section numbering (1, 1.1, 1.2),
insertTocrenders linked<nav>into document,scrollToHeadingwith smooth scroll,validateHeadingsdetects skipped levels,onOutlineChangecallback +toc:changeevent. Adds 4 commands:getOutline,insertToc,scrollToHeading,validateHeadings.AnalyticsPlugin — real-time readability scores (Flesch-Kincaid, Gunning Fog, Coleman-Liau, Flesch Reading Ease), reading time estimate, vocabulary level (basic/intermediate/advanced), sentence/paragraph length warnings, goal-based word count with progress tracking, keyword density, SEO hints.
onAnalyticscallback +analytics:updateevent. Adds 3 commands:getAnalytics,getSeoAnalysis,getKeywordDensity.SpellcheckPlugin — built-in grammar engine with passive voice detection, wordiness patterns, cliche detection, and punctuation checks. Inline red wavy (spelling), blue wavy (grammar), and green dotted (style) underlines. Right-click context menu with correction suggestions, "Ignore", and "Add to Dictionary". Writing-style presets (formal, casual, technical, academic). BCP 47 multi-language support. Optional
customServiceinterface for LanguageTool/Grammarly. Persistent dictionary via localStorage.onError/onCorrectioncallbacks +spellcheck:update/grammar:checkevents. Adds 6 commands:toggleSpellcheck,checkGrammar,addToDictionary,ignoreWord,setWritingStyle,getSpellcheckStats.
import {
SyntaxHighlightPlugin,
TablePlugin,
CommentsPlugin,
CalloutPlugin,
LinkPlugin,
TemplatePlugin,
KeyboardPlugin,
DragDropPlugin,
MathPlugin,
TocPlugin,
AnalyticsPlugin,
SpellcheckPlugin,
} from '@remyxjs/react';
<RemyxEditor
plugins={[
SyntaxHighlightPlugin(),
TablePlugin(),
CommentsPlugin(),
CalloutPlugin(),
LinkPlugin(),
TemplatePlugin(),
KeyboardPlugin(),
DragDropPlugin(),
MathPlugin(),
TocPlugin(),
AnalyticsPlugin(),
SpellcheckPlugin(),
]}
/>;TablePlugin in depth
When TablePlugin() is active, every <table class="rmx-table"> in the editor automatically gains:
| Feature | How it works |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Sortable columns | Click any <th> to cycle through ascending → descending → unsorted. Rows are physically reordered in the DOM so the sort persists in HTML output. |
| Multi-column sort | Hold Shift and click additional headers. A small priority number appears on each sorted header. |
| Sort data types | The sort auto-detects numeric, date, or alphabetical data. Pass dataType explicitly or provide a custom comparator via the tableSortComparator engine option. |
| Sort indicators | ▲/▼ triangles rendered via CSS ::after on <th> elements with data-sort-dir attributes. |
| Filterable rows | A small ▽ icon appears in each header cell. Click it to open a filter dropdown with a text input. Rows not matching the filter are hidden (non-destructive — they reappear when the filter is cleared). Multiple columns can be filtered simultaneously (AND logic). |
| Column/row resize | Invisible 6px drag handles appear at column and row borders. Drag to resize. The resize is smooth (rAF-driven) and creates an undo snapshot on mouseup. |
| Inline formulas | Type =SUM(A1:A5) in any cell. On blur, the formula is stored in a data-formula attribute and the computed result is displayed. On focus, the formula text is shown for editing. Supports SUM, AVERAGE, COUNT, MIN, MAX, IF, CONCAT, cell references (A1 notation), ranges, arithmetic, and comparison operators. Circular references are detected and display #CIRC!. |
| Cell formatting | Right-click a cell to format as Number, Currency, Percentage, or Date. The raw value is preserved in data-raw-value; the display uses Intl.NumberFormat / Intl.DateTimeFormat. |
| Sticky header | <thead><th> cells use position: sticky so the header row stays visible when scrolling tall tables. |
| Clipboard interop | Copying table content produces both HTML and TSV (tab-separated values). Pasting TSV or HTML tables from Excel / Google Sheets inserts data into the grid starting at the caret cell, auto-expanding rows and columns as needed. |
Custom sort comparator:
<RemyxEditor
plugins={[TablePlugin()]}
engineOptions={{
tableSortComparator: (a, b, dataType, columnIndex) => {
// Custom comparison logic — return negative, zero, or positive
return a.localeCompare(b);
},
}}
/>Programmatic commands (via engine ref):
import { useRef } from 'react';
function MyEditor() {
const engineRef = useRef(null);
const handleSort = () => {
engineRef.current?.executeCommand('sortTable', {
keys: [
{ columnIndex: 0, direction: 'asc' },
{ columnIndex: 2, direction: 'desc', dataType: 'numeric' },
],
});
};
const handleFilter = () => {
engineRef.current?.executeCommand('filterTable', {
columnIndex: 1,
filterValue: 'active',
});
};
return (
<>
<button onClick={handleSort}>Sort by Name then Score</button>
<button onClick={handleFilter}>Show active only</button>
<RemyxEditor
plugins={[TablePlugin()]}
onReady={(engine) => {
engineRef.current = engine;
}}
/>
</>
);
}Context menu additions (appear when right-clicking inside a table):
- Toggle Header Row
- Format as Number / Currency / Percentage / Date
- Clear Filters
CalloutPlugin
The CalloutPlugin adds styled callout/alert/admonition blocks with collapsible toggle and GFM alert auto-conversion:
import { CalloutPlugin } from '@remyxjs/react';
<RemyxEditor plugins={[CalloutPlugin()]} />;Insert callouts programmatically:
// Basic callout
engine.executeCommand('insertCallout', { type: 'warning' });
// Collapsible with custom title
engine.executeCommand('insertCallout', {
type: 'tip',
collapsible: true,
title: 'Pro tip',
});
// With pre-populated content
engine.executeCommand('insertCallout', {
type: 'info',
content: '<ul><li>Step 1</li><li>Step 2</li></ul>',
});
// Change type of the focused callout
engine.executeCommand('changeCalloutType', 'error');
// Register a custom callout type
import { registerCalloutType } from '@remyxjs/react';
registerCalloutType({
type: 'security',
label: 'Security',
icon: '🔒',
color: '#dc2626',
});GitHub-flavored alerts — blockquotes with > [!NOTE], > [!WARNING], etc. are automatically converted to callout blocks on paste or typing.
Available types: info, warning, error, success, tip, note, question (plus any custom types).
CommentsPlugin
The CommentsPlugin adds inline comment threads — highlight text, attach discussion threads, resolve/reopen, reply, and @mention other users.
import { CommentsPlugin } from '@remyxjs/react';
<RemyxEditor
plugins={[
CommentsPlugin({
onComment: (thread) => saveToServer(thread),
onResolve: ({ thread, resolved }) => updateServer(thread),
onDelete: (thread) => deleteFromServer(thread),
onReply: ({ thread, reply }) => notifyUser(reply),
mentionUsers: ['alice', 'bob', 'charlie'],
}),
]}
/>;Comment-only mode — make the editor read-only but still allow annotations:
<RemyxEditor plugins={[CommentsPlugin({ commentOnly: true })]} />useComments hook
The useComments hook provides reactive state and convenience methods:
import { useComments, CommentsPanel } from '@remyxjs/react';
function AnnotatedEditor() {
const engineRef = useRef(null);
const {
threads, activeThread, addComment, deleteComment,
resolveComment, replyToComment, navigateToComment,
exportThreads, importThreads,
} = useComments(engineRef.current);
return (
<div style={{ display: 'flex' }}>
<RemyxEditor
plugins={[CommentsPlugin({ mentionUsers: ['alice', 'bob'] })]}
onReady={(engine) => { engineRef.current = engine; }}
style={{ flex: 1 }}
/>
<CommentsPanel
threads={threads}
activeThread={activeThread}
onNavigate={navigateToComment}
onResolve={(id, resolved) => resolveComment(id, resolved)}
onDelete={deleteComment}
onReply={(id, params) => replyToComment(id, params)}
filter="all" {/* 'all' | 'open' | 'resolved' */}
/>
</div>
);
}CommentsPanel props
| Prop | Type | Description |
| -------------- | -------------------------------------- | ------------------------------------- |
| threads | CommentThread[] | All threads (from useComments) |
| activeThread | CommentThread \| null | Currently focused thread |
| onNavigate | (threadId) => void | Called when a thread card is clicked |
| onResolve | (threadId, resolved) => void | Called when Resolve/Reopen is clicked |
| onDelete | (threadId) => void | Called when Delete is clicked |
| onReply | (threadId, { author, body }) => void | Called when a reply is submitted |
| filter | 'all' \| 'open' \| 'resolved' | Which threads to display |
| className | string | Additional CSS class |
Engine events
| Event | Payload | When |
| ------------------- | ---------------------- | --------------------------- |
| comment:created | { thread } | New comment added |
| comment:resolved | { thread, resolved } | Thread resolved or reopened |
| comment:deleted | { thread } | Thread deleted |
| comment:replied | { thread, reply } | Reply added to thread |
| comment:updated | { thread } | Thread body edited |
| comment:clicked | { thread, element } | Comment highlight clicked |
| comment:navigated | { threadId } | Scrolled to comment |
| comment:imported | { count } | Threads imported |
Custom Plugins
import { createPlugin } from '@remyxjs/react';
const MyPlugin = createPlugin({
name: 'my-plugin',
version: '1.0.0',
description: 'Example plugin with lifecycle hooks',
author: 'You',
// Lifecycle hooks (auto-wired, sandboxed)
onContentChange(api) {
console.log('Content changed:', api.getText().length, 'chars');
},
onSelectionChange(api) {
console.log('Selection:', api.getActiveFormats());
},
// Traditional init/destroy
init(api) {
/* called once on mount */
},
destroy(api) {
/* called on unmount */
},
// Dependencies — ensures 'other-plugin' is initialized first
dependencies: ['other-plugin'],
// Scoped settings with validation
settingsSchema: [
{
key: 'maxLength',
type: 'number',
label: 'Max Length',
defaultValue: 5000,
validate: (v) => v > 0,
},
{
key: 'mode',
type: 'select',
label: 'Mode',
defaultValue: 'auto',
options: [
{ label: 'Auto', value: 'auto' },
{ label: 'Manual', value: 'manual' },
],
},
],
defaultSettings: { maxLength: 5000, mode: 'auto' },
});<RemyxEditor plugins={[MyPlugin]} />Access plugin settings from outside:
<RemyxEditor
plugins={[MyPlugin]}
onReady={(engine) => {
engine.plugins.getPluginSetting('my-plugin', 'maxLength'); // 5000
engine.plugins.setPluginSetting('my-plugin', 'maxLength', 3000); // validates + updates
}}
/>Plugin with Custom Commands
const HighlightPlugin = createPlugin({
name: 'highlight',
init(engine) {
engine.executeCommand('highlight');
},
commands: [
{
name: 'highlight',
execute(eng) {
document.execCommand('backColor', false, '#ffeb3b');
},
},
],
});Read-Only Mode
Disable editing while keeping the full rendered output visible.
<RemyxEditor value={content} readOnly={true} />Combine with a toggle for preview mode:
const [editing, setEditing] = useState(true);
<button onClick={() => setEditing(!editing)}>
{editing ? 'Preview' : 'Edit'}
</button>
<RemyxEditor value={content} onChange={setContent} readOnly={!editing} />Error Handling
onError Callback
Catch errors from plugins, the engine, and file uploads without crashing the app:
<RemyxEditor
onError={(error, info) => {
if (info.source === 'plugin') {
console.warn(`Plugin "${info.pluginName}" failed:`, error);
} else if (info.source === 'upload') {
alert(`Upload failed: ${error.message}`);
} else if (info.source === 'engine') {
console.error(`Engine error in ${info.phase}:`, error);
}
}}
/>Error sources:
| Source | Info Fields | Description |
| ---------- | ------------ | --------------------------- |
| 'plugin' | pluginName | Plugin init/destroy failure |
| 'engine' | phase | Engine initialization error |
| 'upload' | file | Upload handler rejection |
Custom Error Fallback
Replace the default error UI with your own component:
<RemyxEditor
errorFallback={
<div className="editor-error">
<p>Something went wrong. Please refresh.</p>
</div>
}
/>Engine Access
Use the onReady callback to access the EditorEngine instance for programmatic control.
Execute Commands
functi