Modern WYSIWYG editor with Notion-like experience. Clean HTML output. Zero dependencies. Vanilla JS.
Compatible with everything that can work with a plain <textarea>.
- π¨ Notion-like UI β Floating toolbar for text selection, block handles for drag & drop
- β¨οΈ Slash Commands β Type
/to open command menu with all block types (h1, h2, h3, quote, callout, code, image, youtube, table, hr, ol, ul) - π Rich Text Formatting β Bold, italic, underline, strikethrough, inline code, highlights, spoilers
- π Advanced Links β Full control over href, title, target, rel attributes (nofollow, sponsored, ugc)
- π Lists β Ordered and unordered lists with drag & drop reordering and nesting
- π― Block Controls β Drag handle on hover, context menu for transformations
- πΌοΈ Images β Upload, browse gallery, drag & drop, paste from clipboard, auto-upload base64
- π Tables β Full-featured tables with row/column manipulation
- π¬ Quotes & Callouts β Blockquotes with styles, aside blocks with presets (info, warning, danger, success) and optional emoji
- π¬ YouTube Embeds β Automatic responsive video wrapper
- π± Touch Support β Full mobile support with touch events for drag & drop
- β¨οΈ Markdown Shortcuts β
#,##,###,*,-,1.,>,!,---for quick formatting - π Find & Replace β Built-in search with navigation and replace functionality
- π History β Unlimited undo/redo with smart batching
- π HTML Mode β Switch to raw HTML editing with syntax highlighting
- π Internationalization β Native support for 20+ languages with automatic RTL detection
- π Auto-sync β Automatic synchronization with original textarea
- π― Element Attributes β Edit ID (anchors) and CSS classes for any element
- π Word/Character Counter β Real-time statistics in the bottom right
- π₯οΈ Fullscreen Mode β Distraction-free editing experience
- π Configurable Height β Set maximum height for the editor area
- π¬ Lite Mode β Simplified editor for comments and forums (no uploads, auto-nofollow links)
- π€ Upload β Drag & drop, paste, file picker
- πΌοΈ Gallery Browser β Visual grid of previously uploaded images
- π Replace β Upload new image while editing existing one
- ποΈ Delete β Optional delete functionality in gallery
- π Base64 Auto-upload β Automatically converts pasted base64 images to uploaded files
- βοΈ Full Settings β src, srcset, alt, title, loading, caption, link with rel attributes
- π― Zero Dependencies β Pure vanilla JavaScript, no external libraries
- π¦ Modular Architecture β Easy to extend with custom modules
- π¨ Customizable Styles β CSS variables for easy theming
- π Simple Integration β Works with any backend, just a textarea
- π‘οΈ XSS Protection β Built-in HTML sanitization
- π± Responsive β Works perfectly on mobile devices
- π Multi-instance β Multiple editors on one page
- ποΈ Configurable β Custom presets, classes, upload handlers
<link rel="stylesheet" href="/redactix/Redactix.css">
<script type="module">
import Redactix from './redactix/Redactix.js';
new Redactix({
selector: '.redactix'
});
</script><textarea class="redactix">
<h1>Hello World!</h1>
<p>Start editing...</p>
</textarea>That's it! The editor will automatically initialize.
Redactix includes redactix_images.php β a unified script for upload, browse, and delete operations.
new Redactix({
selector: '.redactix',
uploadUrl: '/redactix_images.php',
browseUrl: '/redactix_images.php',
allowImageDelete: true // Show delete buttons in gallery
});// redactix_images.php
$uploadDir = __DIR__ . '/uploads/';
$uploadUrlPrefix = '/uploads/';
$maxFileSize = 5 * 1024 * 1024; // 5MB
$allowDelete = false; // Enable delete functionalityFor testing without real uploads, use redactix_images_demo.php:
- Always returns
uploads/default.jpgon upload - Browse and delete work with real files
- Perfect for demonstrations
Upload
POST /redactix_images.php
Content-Type: multipart/form-data
Body: image file
Response:
{
"success": true,
"src": "/uploads/abc123.jpg",
"srcset": "",
"alt": "",
"title": "",
"caption": ""
}
Browse
POST /redactix_images.php
Content-Type: multipart/form-data
Body: action=browse
Response:
{
"success": true,
"images": [
{
"src": "/uploads/image.jpg",
"filename": "image.jpg",
"size": "1.2 MB",
"sizeBytes": 1258291,
"modified": 1638360000
}
],
"allowDelete": false
}
Delete
POST /redactix_images.php
Content-Type: multipart/form-data
Body: action=delete&file=image.jpg
Response:
{
"success": true,
"message": "File deleted successfully"
}
Each textarea with Redactix gets a reference to its instance via textarea.redactix. This allows programmatic control over the editor content.
// Get textarea element
const textarea = document.querySelector('#my-editor');
// Get clean HTML content (without editor wrappers)
const html = textarea.redactix.getContent();
// Set new HTML content
textarea.redactix.setContent('<h1>New content</h1><p>Hello world!</p>');If you have multiple editors and need to copy content from one to another:
// Old way (doesn't work with Redactix):
// targetTextarea.value = sourceTextarea.value;
// New way:
const source = document.querySelector('#source-editor');
const target = document.querySelector('#target-editor');
target.redactix.setContent(source.redactix.getContent());The editor automatically syncs with the original textarea on every change. You can also trigger sync manually:
// Force sync editor content to textarea
textarea.redactix.sync();
// Now textarea.value contains the latest HTML
console.log(textarea.value);const textarea = document.querySelector('#my-editor');
// Switch to dark theme
textarea.redactix.setTheme('dark');
// Switch to light theme
textarea.redactix.setTheme('light');
// Follow system preference (prefers-color-scheme)
textarea.redactix.setTheme('auto');| Method | Description |
|---|---|
getContent() |
Returns clean HTML without editor-specific wrappers |
setContent(html) |
Sets new HTML content, re-initializes editor elements, resets history |
sync() |
Manually syncs editor content to the original textarea |
setTheme(theme) |
Changes the editor theme at runtime. Accepts 'light', 'dark', or 'auto' |
new Redactix({
selector: '.redactix', // CSS selector for textareas
locale: 'en', // Language (en, ru, fr, es, etc.)
uploadUrl: '/upload.php', // Image upload endpoint
browseUrl: '/browse.php', // Image gallery endpoint
allowImageDelete: true, // Show delete buttons in gallery
maxHeight: '500px', // Maximum editor height
classes: ['highlight', 'centered'], // Quick-select classes in Attributes
liteMode: false // Enable lite mode for comments/forums
});Lite mode provides a simplified editor perfect for comment forms and forums:
new Redactix({
selector: '.comment-editor',
liteMode: true
});What's disabled in lite mode:
- Toolbar β completely hidden (use
/commands or Markdown shortcuts instead) - Fullscreen, HTML mode, and Find & Replace
- Image uploads (drag & drop, paste, file picker) β only URL-based images allowed
- Image gallery browser
- Base64 image paste (automatically removed)
- Advanced link settings (title, rel) β all links are automatically
nofollowand open in new tab - Element attributes editing (ID, classes)
- Word/character counter
What's simplified:
- Image dialog: only URL and alt text, images get
loading="lazy"by default - Link dialog: only URL and text, all links are
nofollowwithtarget="_blank"
What still works:
- All text formatting (bold, italic, underline, strikethrough, code, highlight, spoiler)
- Slash commands β type
/to insert any block type - Markdown shortcuts β
#,-,>, etc. - Links (with automatic nofollow)
- Images by URL (wrapped in
<figure>withloading="lazy") - Lists, blockquotes, callouts
- Tables, code blocks, separators
- Block drag & drop
- Undo/redo
- Floating toolbar for text selection
new Redactix({
selector: '.redactix',
// Custom callout (aside) styles
calloutPresets: [
{ name: 'note', label: 'Note', class: 'my-note' },
{ name: 'tip', label: 'Pro Tip', class: 'my-tip' }
],
// Custom blockquote styles
quotePresets: [
{ name: 'pull', label: 'Pull Quote', class: 'pull-quote' },
{ name: 'testimonial', label: 'Testimonial', class: 'testimonial' }
]
});Type / anywhere to open the command menu:
| Command | Description |
|---|---|
/h1 |
Heading 1 |
/h2 |
Heading 2 |
/h3 |
Heading 3 |
/quote |
Blockquote |
/callout |
Callout/aside block |
/code |
Code block |
/image |
Insert image |
/youtube |
YouTube video |
/table |
Insert table |
/hr |
Horizontal divider |
/ol |
Numbered list |
/ul |
Bullet list |
Use arrow keys to navigate, Enter to select, Escape to close.
Type at the start of a line and press space:
| Shortcut | Result |
|---|---|
# |
Heading 1 |
## |
Heading 2 |
### |
Heading 3 |
- or * |
Bullet list |
1. |
Numbered list |
> |
Blockquote |
! |
Callout (aside) |
--- |
Horizontal rule |
| Shortcut | Action |
|---|---|
Ctrl+B |
Bold |
Ctrl+I |
Italic |
Ctrl+U |
Underline |
Ctrl+Z |
Undo |
Ctrl+Y |
Redo |
Ctrl+F |
Find & Replace |
Escape |
Exit fullscreen |
Appears when you select text:
- Bold, Italic, Underline, Strikethrough
- Inline Code, Spoiler, Highlight
- Link with full configuration
Hover over any block to see the drag handle on the left:
- Click β Open context menu (transform, duplicate, delete)
- Drag β Reorder blocks with drag & drop
- Touch β Long press opens menu, drag to reorder
Right-click or click the handle to:
- Transform block type (H1, H2, H3, P, Quote, Callout)
- Change callout/quote style
- Add or change callout emoji
- Convert list type (bulleted β numbered)
- Insert block below
- Duplicate block
- Edit attributes (ID, classes)
- Delete block
redactix/
βββ core/
β βββ Editor.js # Core editing logic, paste handling
β βββ Module.js # Base class for modules
β βββ Selection.js # Selection management utilities
βββ modules/
β βββ Attributes.js # ID and class editor
β βββ BaseStyles.js # Bold, italic, underline, etc.
β βββ BlockControl.js # Drag handles and context menu
β βββ BlockStyles.js # Headings, paragraphs
β βββ Code.js # Code blocks
β βββ FindReplace.js # Search functionality
β βββ FloatingToolbar.js # Text selection toolbar
β βββ Fullscreen.js # Fullscreen mode
β βββ History.js # Undo/redo
β βββ HtmlMode.js # Raw HTML editor
β βββ Image.js # Image upload and management
β βββ Link.js # Link insertion
β βββ List.js # Lists (UL/OL)
β βββ Markdown.js # Markdown shortcuts
β βββ Separator.js # Horizontal rules
β βββ SlashCommands.js # "/" command menu (Notion-like)
β βββ Table.js # Table management
β βββ Youtube.js # Video embeds
βββ ui/
β βββ Icons.js # SVG icon library
β βββ Modal.js # Modal dialog component
β βββ Toolbar.js # Main toolbar with sticky behavior
βββ Redactix.css # Complete styling
βββ Redactix.js # Main entry point
import Module from './core/Module.js';
import Icons from './ui/Icons.js';
export default class MyModule extends Module {
getButtons() {
return [
{
name: 'myButton',
icon: Icons.bold,
title: 'My Feature',
action: () => {
// Your logic here
this.instance.sync();
}
}
];
}
init() {
// Initialize your module
// Access editor: this.instance.editorEl
// Access config: this.instance.config
}
}Redactix produces clean, semantic HTML:
<h1>Heading</h1>
<p>Paragraph with <b>bold</b>, <i>italic</i>, and <a href="https://example.com">links</a>.</p>
<ul>
<li>List item</li>
<li>Another item</li>
</ul>
<blockquote>Standard quote</blockquote>
<blockquote class="big">Large quote</blockquote>
<aside class="warning">Warning callout</aside>
<aside data-emoji="π‘" class="information">Callout with emoji</aside>
<figure>
<img src="/uploads/image.jpg" alt="Description">
<figcaption>Image caption</figcaption>
</figure>
<table>
<thead>
<tr><th>Header</th></tr>
</thead>
<tbody>
<tr><td>Cell</td></tr>
</tbody>
</table>
<div class="redactix-video-wrapper">
<iframe src="https://www.youtube.com/embed/VIDEO_ID"></iframe>
</div>
<div class="redactix-separator"><hr></div>
<pre><code>function hello() {
console.log('Hello!');
}</code></pre><b>β Bold<i>β Italic<u>β Underline<s>β Strikethrough<code>β Inline code<mark>β Highlight<span class="spoiler">β Spoiler<a>β Links with full attributes
Redactix sanitizes all pasted content:
- Removes dangerous tags (
<script>,<iframe>, etc.) - Strips inline styles and event handlers
- Validates URLs (blocks
javascript:) - Filters CSS classes (whitelist only)
- Converts Google Docs artifacts
- Validates image content (MIME + actual format)
The included PHP script provides:
- MIME type validation (server-side)
- File content validation with
getimagesize() - Random filename generation
- File size limits
- Extension whitelist
- Directory traversal protection
Important: Add authentication and CSRF tokens for production use.
- Long press on block handle opens menu
- Drag blocks to reorder
- Pinch to zoom (native)
- Double tap to select word
- Adaptive toolbar (wraps on small screens)
- Touch-friendly 44x44px tap targets
- Optimized floating toolbar
- Mobile-friendly modal dialogs
- Prevents zoom on input focus
- Handles virtual keyboard
- Smooth scrolling
- No hover states on touch devices
Redactix uses CSS custom properties (variables) for all colors, making theming simple without !important.
Add the redactix-dark class to enable dark mode:
new Redactix({
selector: '.redactix',
theme: 'dark' // Adds 'redactix-dark' class automatically
});Or manually add the class:
<div class="redactix-wrapper redactix-dark">...</div>Use redactix-auto class to follow system preference:
new Redactix({
selector: '.redactix',
theme: 'auto' // Follows prefers-color-scheme
});Override these variables to customize the editor. No !important needed.
/* Light theme customization */
.redactix-wrapper {
--redactix-primary: #8b5cf6; /* Purple accent */
--redactix-primary-hover: #7c3aed;
--redactix-bg: #fafafa;
--redactix-text: #1a1a1a;
}
/* Dark theme customization - use the same selector specificity */
.redactix-wrapper.redactix-dark {
--redactix-primary: #a855f7; /* Brighter purple for dark */
--redactix-primary-hover: #c084fc;
}
/* Scope to a specific editor */
.redactix-wrapper.my-theme {
--redactix-primary: #10b981; /* Green accent */
}Note: When using dark theme (
theme: 'dark'), override variables with.redactix-wrapper.redactix-darkselector to ensure proper specificity.
<style>
#my-editor {
--redactix-primary: #f59e0b;
--redactix-border: #fcd34d;
}
</style>
<textarea class="redactix" id="my-editor">...</textarea>/* ===== Primary Colors ===== */
--redactix-primary: #2563eb; /* Main accent color (buttons, links, focus) */
--redactix-primary-hover: #1d4ed8; /* Hover state for primary */
--redactix-primary-light: #dbeafe; /* Light version for backgrounds */
--redactix-primary-rgb: 37, 99, 235; /* RGB values for rgba() usage */
/* ===== Danger Colors ===== */
--redactix-danger: #dc2626; /* Delete buttons, errors */
--redactix-danger-hover: #b91c1c; /* Hover state for danger */
--redactix-danger-light: #fef2f2; /* Light danger background */
/* ===== Base Colors ===== */
--redactix-border: #e5e7eb; /* All borders */
--redactix-bg: #ffffff; /* Main background */
--redactix-bg-secondary: #f9fafb; /* Toolbar, table headers */
--redactix-bg-hover: #f3f4f6; /* Hover states */
--redactix-bg-active: #e5e7eb; /* Active/pressed states */
--redactix-text: #374151; /* Main text color */
--redactix-text-muted: #6b7280; /* Secondary text */
--redactix-text-placeholder: #9ca3af; /* Placeholders */
/* ===== UI Elements ===== */
--redactix-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); /* Dropdowns, modals */
--redactix-radius: 6px; /* Border radius */
--redactix-overlay: rgba(0, 0, 0, 0.4); /* Modal overlay */
/* ===== Content Styling ===== */
--redactix-blockquote-bg: #f8fafc; /* Blockquote background */
--redactix-code-bg: #f3f4f6; /* Inline code background */
--redactix-code-color: #e11d48; /* Inline code text */
--redactix-mark-bg: #fef08a; /* Highlight/mark background */
--redactix-spoiler-bg: #374151; /* Spoiler hidden state */
/* ===== Pre/Code Blocks ===== */
--redactix-pre-bg: #1f2937; /* Code block background */
--redactix-pre-text: #e5e7eb; /* Code block text */
/* ===== Callouts (aside) ===== */
--redactix-callout-bg: #f3f4f6; /* Default callout background */
--redactix-callout-border: #d1d5db; /* Default callout border */
--redactix-callout-text: #374151; /* Default callout text */
/* ===== Find & Replace ===== */
--redactix-find-highlight: #fef08a; /* Found text highlight */
--redactix-find-current: #f97316; /* Current match highlight */
/* ===== Drag & Drop ===== */
--redactix-drag-bg: #dbeafe; /* Dragging element background */
--redactix-dragover-bg: #eff6ff; /* Drop zone highlight */
/* ===== Code Editor (HTML mode) - Always dark ===== */
--redactix-code-editor-bg: #1e1e1e;
--redactix-code-editor-gutter: #252526;
--redactix-code-editor-text: #d4d4d4;
--redactix-code-editor-line: #858585;
--redactix-code-editor-selection: #264f78;
/* ===== Floating Toolbar - Always dark ===== */
--redactix-floating-bg: #1f2937;
--redactix-floating-text: #e5e7eb;
--redactix-floating-separator: rgba(255, 255, 255, 0.2);The theme system covers all UI elements:
- Editor area (background, text, borders)
- Toolbar and buttons
- Modal dialogs (backgrounds, inputs, buttons)
- Dropdown menus and context menus
- Find & Replace panel
- Slash commands menu
- Callouts and blockquotes
- Code blocks and inline code
When .redactix-dark is applied:
.redactix-wrapper.redactix-dark {
--redactix-primary: #3b82f6;
--redactix-border: #374151;
--redactix-bg: #1f2937;
--redactix-bg-secondary: #111827;
--redactix-bg-hover: #374151;
--redactix-text: #f3f4f6;
--redactix-text-muted: #9ca3af;
/* ... and more */
}/* Company brand colors */
.redactix-wrapper.brand-theme {
--redactix-primary: #6366f1; /* Indigo */
--redactix-primary-hover: #4f46e5;
--redactix-primary-light: #e0e7ff;
--redactix-primary-rgb: 99, 102, 241;
--redactix-radius: 12px; /* More rounded */
}// Apply the theme
const editor = new Redactix({ selector: '.redactix' });
document.querySelector('.redactix-wrapper').classList.add('brand-theme');/* Override the built-in dark theme */
.redactix-wrapper.redactix-dark {
--redactix-primary: #a855f7; /* Purple accent */
--redactix-primary-hover: #9333ea;
--redactix-primary-light: #3b1f5e;
}new Redactix({
selector: '.redactix',
theme: 'dark' // Uses your customized dark theme
});/* Create your own dark theme class */
.redactix-wrapper.my-dark {
--redactix-border: #2d2d44;
--redactix-bg: #1a1a2e;
--redactix-bg-secondary: #16162a;
--redactix-bg-hover: #2d2d44;
--redactix-text: #e2e2e2;
--redactix-text-muted: #a0a0a0;
--redactix-primary: #818cf8;
--redactix-primary-hover: #a5b4fc;
}// Initialize without theme, add class manually
new Redactix({ selector: '.redactix' });
document.querySelector('.redactix-wrapper').classList.add('my-dark');.redactix-editor aside.my-custom {
background: #f0f9ff;
border-color: #0ea5e9;
color: #075985;
}new Redactix({
calloutPresets: [
{ name: 'custom', label: 'My Style', class: 'my-custom' }
]
});Redactix comes with built-in support for multiple languages and Right-to-Left (RTL) text direction.
Specify the locale option during initialization:
new Redactix({
selector: '.redactix',
locale: 'ru' // Set language to Russian
});| Code | Language | Code | Language | |
|---|---|---|---|---|
en |
English | ru |
Russian | |
fr |
French | es |
Spanish | |
de |
German | pt |
Portuguese | |
uk |
Ukrainian | pl |
Polish | |
tr |
Turkish | sr |
Serbian | |
ja |
Japanese | ko |
Korean | |
zh |
Chinese | vi |
Vietnamese | |
th |
Thai | sw |
Swahili | |
ka |
Georgian | kk |
Kazakh | |
uz |
Uzbek | ar |
Arabic (RTL) | |
he |
Hebrew (RTL) |
Redactix automatically detects RTL languages (like Arabic and Hebrew) and adjusts the UI accordingly:
- Toolbar alignment
- Text direction
- UI elements positioning
No extra configuration is neededβjust set the locale.
// Controller
public function store(Request $request)
{
$validated = $request->validate([
'content' => 'required|string'
]);
// Redactix content is already HTML
$article->content = $validated['content'];
$article->save();
}
// Blade template
<textarea name="content" class="redactix">
{!! old('content', $article->content) !!}
</textarea>// Add to functions.php
function enqueue_redactix() {
wp_enqueue_style('redactix', get_template_directory_uri() . '/redactix/Redactix.css');
wp_enqueue_script('redactix', get_template_directory_uri() . '/redactix/Redactix.js', [], false, true);
}
add_action('wp_enqueue_scripts', 'enqueue_redactix');
// Use in template
<textarea class="redactix" name="content">
<?php echo esc_html(get_post_meta($post->ID, 'content', true)); ?>
</textarea>app.post('/articles', (req, res) => {
const content = req.body.content;
// Sanitize on server if needed
const sanitized = DOMPurify.sanitize(content);
// Save to database
db.articles.insert({ content: sanitized });
});Use redactix_images_demo.php for testing without cluttering your uploads folder:
new Redactix({
uploadUrl: '/redactix_images_demo.php',
browseUrl: '/redactix_images_demo.php'
});- Upload images via drag & drop
- Upload images via paste
- Upload images via file picker
- Browse uploaded images
- Edit existing images
- Delete images (if enabled)
- Paste from Google Docs (with images)
- Paste from Word
- Drag & drop blocks
- Touch drag on mobile
- Undo/redo operations
- Find & replace
- Fullscreen mode
- HTML mode
- Multiple editors on one page
- Check
uploadUrlis set correctly - Verify PHP script has write permissions to uploads folder
- Check PHP
upload_max_filesizeandpost_max_size - Open browser console for error messages
- Toolbar is sticky only when no
maxHeightis set - In fullscreen mode, toolbar is static
- Check browser console for JavaScript errors
- Ensure
uploadUrlis configured - Check browser console for upload errors
- Verify server accepts POST requests
- Check server response format (must be JSON)
- Long press (500ms) to open menu
- Drag immediately to reorder blocks
- Ensure no other touch handlers are interfering
MIT License - feel free to use in personal and commercial projects.
- Issues: GitHub Issues
- Documentation: See
index.htmlfor live examples - Questions: Open a discussion on GitHub
Made with vanilla JavaScript. No frameworks. No dependencies. Just clean code.