Redactix

Modern WYSIWYG editor for content creators. Notion-like experience.
Slash commands, floating toolbar, clean HTML output. Zero dependencies.
Compatible with everything that can work with a plain <textarea>.

Why Redactix?

A lightweight, powerful editor that produces clean semantic HTML without any framework dependencies.

Rich Text Formatting

Bold, italic, underline, strikethrough, inline code, spoilers, and text highlighting with a floating toolbar.

📝

Slash Commands

Type / to open command menu. Insert headings, lists, images, tables, and more. Just like Notion.

📃

Block Controls

Drag-and-drop blocks, transform types, duplicate, delete. Full control with the handle menu.

Block Gap Insert

Hover the gap between two blocks to get a "+" handle — click to drop a fresh paragraph right there. Toggle off via gapInsertHandle: false.

📊

Tables

Insert and edit tables with context menus for rows, columns, and header management.

🎨

Images

Drag & drop, paste, or upload images. Full control over alt, title, srcset, and captions.

🎥

Universal Embeds

One /embed for YouTube, Spotify, X, Instagram, TikTok, Reddit and more — provider auto-detected from the URL. Custom HTML tab for niche iframes.

🎬

Native Video

Insert by URL out of the box. Provide videoUploadUrl to also accept MP4 / WebM / MOV file uploads. Renders as a real <figure><video controls> with a chosen aspect ratio.

🌄

Photo Galleries

Multiple images grouped under one <figure> with a single shared caption. Drag-to-reorder in the modal, per-image alt and optional link. Same upload pipeline as single images.

💻

Code Blocks

Syntax-highlighted code blocks with language selection. Click to edit in a dedicated modal.

💡

Callouts & Quotes

Styled callout boxes (info, warning, danger, success) with optional emoji, and quote presets for visual variety.

🔍

Find & Replace

Search through content with highlighting, navigate matches, and replace text globally.

📄

Clean HTML Output

Semantic HTML without inline styles or unnecessary attributes. Perfect for CMS integration.

Smart Paste

Paste from Google Docs, Word, or any source. Formatting is preserved, junk is removed.

🔄

Undo/Redo History

Full history stack with Ctrl+Z and Ctrl+Y support. Never lose your changes.

Fullscreen Mode

Distraction-free writing with fullscreen toggle. Focus on your content.

Keyboard Shortcuts

Speed up your workflow with slash commands and Markdown-style shortcuts.

Shortcut Action Description
/ Slash Commands Opens command menu with all block types (h1, h2, h3, quote, callout, code, image, embed, table, hr, ol, ul)
# Heading 1 Type at the start of a line and press space
## Heading 2 Type at the start of a line and press space
### Heading 3 Type at the start of a line and press space
- or * Bullet List Creates an unordered list item
1. Numbered List Creates an ordered list item
> Blockquote Creates a quote block
! Callout Creates a callout/aside block
--- Separator Inserts a horizontal rule
Ctrl+Z Undo Revert last change
Ctrl+Y Redo Restore undone change
Ctrl+F Find Open find & replace panel

Try It Out

Play with the editor below. All features are enabled.

Lite Mode

Simplified editor for comments and forum posts. No toolbar, no image uploads, no advanced settings, all links are nofollow. Use / command or Markdown shortcuts for formatting.

Localization

Redactix ships with English and Russian out of the box, but the editor is fully ready for any other language — including RTL ones such as Arabic or Hebrew. To add a locale, copy redactix/i18n/en.js, translate the strings, and import it from redactix/i18n/index.js (add the code to rtlLocales if it's right-to-left). Translate the file yourself or hand it to an AI agent — one prompt is enough.

🇺🇸
English
'en'
🇷🇺
Русский
'ru'
locale: 'en'

Custom Presets

Both calloutPresets and quotePresets accept a { defaults?, custom? } object — pass defaults: false to drop the built-ins (warning/danger/information/success for callouts, "big" for quotes) and replace them with your own. Pick any class name; the sanitizer auto-allows it.

Configuration used for this editor:

new Redactix({ selector: '.redactix-presets', calloutPresets: { defaults: false, // drop warning/danger/information/success custom: [ { name: 'tip', label: 'Pro Tip', class: 'my-tip' }, { name: 'note', label: 'Note', class: 'my-note' } ] }, quotePresets: { defaults: false, // drop "big" custom: [ { name: 'pull', label: 'Pull Quote', class: 'pull-quote' }, { name: 'testimonial', label: 'Testimonial', class: 'testimonial' } ] } });

How to make your custom CSS show up — both in the editor and on the published page

When the editor renders your content, it wraps it in <div class="redactix-editor">. On the published page there's no such wrapper — the article is just plain HTML inside your normal layout. That means a CSS rule like aside.my-tip { background: ... } works on the published page but isn't necessarily picked up inside the editor preview, depending on which stylesheets you've loaded.

There are three ways to handle this — pick whichever fits your setup:

1. Easiest — load the same stylesheet on both sides. If your site's main CSS is included on the admin page (where the editor lives) as well as on the public site, one rule covers both:

/* site.css — loaded on both /admin and the public site */ aside.my-tip { background: #f0f9ff; border: 1px solid #0ea5e9; color: #075985; }

2. Editor-only stylesheet. If your site CSS does NOT load on the admin page, write a separate file just for the editor and include it next to Redactix.css. Scope every rule to .redactix-editor so it can't accidentally leak into your admin chrome:

/* redactix-presets.css — loaded only on /admin, only Redactix scope */ .redactix-editor aside.my-tip { background: #f0f9ff; border: 1px solid #0ea5e9; color: #075985; }

Then on the published page you'll have the same rule unscoped (in site.css) so the same callout looks the same to readers.

3. One rule that works in both places. Combine the two selectors with a comma — it's slightly more verbose but means the rule lives in one file no matter where you load it:

/* Works whether this file is loaded by /admin, by the public site, or both */ .redactix-editor aside.my-tip, aside.my-tip { background: #f0f9ff; border: 1px solid #0ea5e9; color: #075985; }

This is exactly what the inline <style> block above this demo uses — open the page source and search for my-tip to see it in context.

Common mistake: writing only the unscoped aside.my-tip { ... } rule and not including that stylesheet on the admin page. The published page will look right; the editor preview won't. If your custom callout looks like the default grey one inside the editor, that's almost always why.

Dark Theme

Built-in dark theme with full CSS variable support. All UI elements are themed including modal dialogs, menus, and buttons. Use theme: 'dark' or theme: 'auto' (follows system preference).

Getting Started

Set up Redactix in your project in minutes.

1

Include the Files

Add the CSS and JS files to your project.

<!-- CSS --> <link rel="stylesheet" href="/redactix/Redactix.css"> <!-- JS (ES Module) --> <script type="module"> import Redactix from './redactix/Redactix.js'; </script>
2

Add a Textarea

Create a textarea element with initial content (optional).

<textarea class="redactix"> <p>Your initial content here...</p> </textarea>
3

Initialize the Editor

Create a new Redactix instance with optional configuration.

new Redactix({ selector: '.redactix', // Custom callout presets (optional) calloutPresets: [ { name: 'custom', label: 'My Style', class: 'my-callout' } ], // Custom quote presets (optional) quotePresets: [ { name: 'fancy', label: 'Fancy Quote', class: 'fancy-quote' } ] });

Configuration Options

selector

CSS selector for textarea elements to transform into editors. Default: .redactix

uploadUrl

URL endpoint for image uploads. Enables drag & drop, paste, and file picker. Default: null

browseUrl

URL endpoint for browsing uploaded images. Shows "Choose from uploaded" button. Default: null

allowImageDelete

Show delete buttons in image gallery. Requires server-side support. Default: false

videoUploadUrl

Endpoint for video file uploads. Same script can serve both images and videos. Without it the /video modal still works in URL-only mode. Default: null

videoBrowseUrl

Optional endpoint for the video gallery (browse + reuse already-uploaded videos). Default: null

maxHeight

Maximum height of the editor area. Any CSS value: '500px', '50vh'. Default: null (no limit)

classes

Array of CSS classes for quick select in Attributes dialog. If empty/null, the section is hidden.

calloutPresets

Array of custom callout styles. Each preset needs name, label, and class properties.

quotePresets

Array of custom blockquote styles. Same structure as callout presets.

theme

Color theme: 'light' (default), 'dark', or 'auto' (follows system preference).

locale

Interface language: 'en' (default) or 'ru'. Drop in any other locale by translating redactix/i18n/en.js — RTL is wired in, just add the code to rtlLocales in i18n/index.js.

gapInsertHandle

Notion-style "+" affordance that appears between two top-level blocks on hover; click to insert a paragraph exactly there. Default true. Pass false to hide it for users who find the always-visible handle distracting.

JavaScript API

Access editor instances via textarea.redactix for programmatic control.

// Get textarea element const textarea = document.querySelector('#my-editor'); // Get clean HTML content const html = textarea.redactix.getContent(); // Set new content (resets history) textarea.redactix.setContent('<h1>New content</h1>'); // Copy content between editors target.redactix.setContent(source.redactix.getContent());

getContent()

Returns clean HTML without editor-specific wrappers (separators, contenteditable attrs).

setContent(html)

Sets new HTML content, re-initializes editor elements (figures, code blocks), resets undo history.

sync()

Manually syncs editor content to the original textarea. Called automatically on changes.

Theming with CSS Variables

All colors use CSS custom properties — editor, toolbar, modals, menus, buttons. Override them without !important.

/* Light theme customization */ .redactix-wrapper { --redactix-primary: #8b5cf6; /* Purple accent */ --redactix-primary-hover: #7c3aed; } /* Dark theme customization - match selector specificity */ .redactix-wrapper.redactix-dark { --redactix-primary: #a855f7; /* Brighter for dark */ --redactix-primary-hover: #c084fc; }

Note: When customizing dark theme, use .redactix-wrapper.redactix-dark selector for proper specificity.

Primary Colors

--redactix-primary, --redactix-primary-hover, --redactix-primary-light — accent color for buttons, links, focus states.

Base Colors

--redactix-bg, --redactix-bg-secondary, --redactix-bg-hover, --redactix-border — backgrounds and borders.

Text Colors

--redactix-text, --redactix-text-muted, --redactix-text-placeholder — main text, secondary text, placeholders.

Content Elements

--redactix-code-bg, --redactix-code-color, --redactix-mark-bg, --redactix-blockquote-bg — inline and block styles.

See README for the complete list of 40+ CSS variables.

Image, Gallery & Video Upload

Built-in support for drag & drop, paste, file picker, and image gallery — plus opt-in native HTML5 video upload. All driven by a single unified PHP backend.

📩

Drag & Drop

Simply drag images from your desktop or file manager directly into the editor. Visual feedback shows where images will be inserted.

📋

Paste from Clipboard

Copy an image and press Ctrl+V to insert it. Works with screenshots, copied images from browsers, and image editors.

📁

File Picker

Click the Image button in the toolbar to open a modal with file upload zone. Drag & drop works there too.

🖼

Image Gallery

Browse and select from previously uploaded images. Square thumbnails with filename and file size. Optional delete functionality.

🔄

Replace Images

When editing an existing image, you can upload a new one to replace it or choose from the gallery. All settings are preserved.

🔗

Link Settings

Full control over image links: URL, target, and rel attributes (nofollow, sponsored, ugc). Same options as text links.

🎬

Native Video

Same model as images: URL inserts work out of the box; provide videoUploadUrl to also accept MP4 / WebM / OGG / MOV file uploads. Renders as a real <figure><video controls> with a chosen aspect ratio.

Setting Up Image Management

1

Configure Upload and Browse URLs

Pass the uploadUrl and browseUrl options. Both can point to the same unified script.

new Redactix({ selector: '.redactix', uploadUrl: '/redactix_images.php', browseUrl: '/redactix_images.php', // Enable delete button (requires PHP config) allowImageDelete: true });
2

Configure the PHP Script

The redactix_images.php script handles upload, browse, and delete operations. Configure settings at the top.

// redactix_images.php configuration $uploadDir = __DIR__ . '/uploads/'; $uploadUrlPrefix = '/uploads/'; $maxFileSize = 5 * 1024 * 1024; $allowDelete = false; // Set true to enable
3

Create the Uploads Directory

Make sure the uploads directory exists and is writable by the web server.

mkdir uploads chmod 755 uploads
4

Enable Video Uploads (optional)

The /video command is always available — URL inserts work without any extra setup. Pass videoUploadUrl (and optionally videoBrowseUrl) to also accept file uploads from the same PHP script:

// JS — point the /video modal's upload zone at the same script new Redactix({ selector: '.redactix', uploadUrl: '/redactix_images.php', browseUrl: '/redactix_images.php', videoUploadUrl: '/redactix_images.php', videoBrowseUrl: '/redactix_images.php' });
// PHP — flip the matching server-side flag $allowVideoUpload = true; $maxVideoSize = 50 * 1024 * 1024; // 50MB by default — also lift php.ini upload_max_filesize

Allowed extensions: mp4, webm, ogg, ogv, mov. Allowed MIME types: video/mp4, video/webm, video/ogg, video/quicktime. The user picks an aspect ratio (auto / 16:9 / 4:3 / 1:1 / 9:16) in the insert modal — the choice is written as inline aspect-ratio on the <video> tag so the player keeps its shape on the published page.

API Endpoints

POST Upload

Send form data with image file. Returns JSON with src, srcset, alt, title, caption.

GET ?action=browse

Returns JSON array of images with src, filename, size, and optional metadata.

POST action=delete

Deletes image by file parameter. Only works if $allowDelete is true in PHP config.

Browse Response

Includes allowDelete flag so the JS knows whether to show delete buttons in the gallery.

POST Video Upload

Send form data with a video file field (instead of image). Returns JSON with src and caption. Requires $allowVideoUpload on the PHP side.

POST action=browse&type=video

Returns a videos array (instead of images) with src, filename, size, sizeBytes, modified.

POST action=delete&type=video

Same shape as image delete; the type=video parameter routes to the video handler (which validates the extension against the video whitelist).

Security Note

The included redactix_images.php is a basic example. For production use, add your own security measures: authentication, CSRF tokens, rate limiting, and file storage outside web root. Enable delete functionality with caution.

Server Response Format

Upload endpoints should return JSON with these fields:

Image upload (POST with image field):

{ "success": true, "src": "/uploads/abc123.jpg", // Required "srcset": "", // Optional "alt": "", // Optional "title": "", // Optional "caption": "" // Optional }

Video upload (POST with video field):

{ "success": true, "src": "/uploads/abc123.mp4", // Required "caption": "" // Optional }

For quick local testing, redactix_images_demo.php mocks both: every image upload returns uploads/default.jpg, every video upload returns uploads/default.mp4 — your uploads folder stays clean.

Why I Built This

The WYSIWYG landscape is broken. Here's what's wrong with it.

💰

Enterprise Pricing

CKEditor, Imperavi... Great features, but licensing costs can reach thousands per year. Not everyone has that budget.

💔

Stuck in 2012

jQuery-based editors with outdated UX. Clunky toolbars, messy HTML output full of inline styles and deprecated tags.

📦

Framework Lock-in

Modern alternatives require React, Vue, or 100+ npm dependencies. Good luck integrating that into a simple PHP project.

🎯 The Goal

I wanted something functional on the level of Notion, but under the hood — just a simple <textarea>. 100% compatibility with any CMS. No third-party components. No complex state management. Just paste the files, initialize, and it works.

📄 Why HTML, Not Markdown?

Markdown is great for developers, but try configuring srcset, loading="lazy", or custom attributes for an image. How do you add rel="nofollow" to a link? What about tables with colspan?

Clean semantic HTML gives you full control over formatting. And if you really need Markdown — there are plenty of html-to-md converters out there.

💦 Clean Output

No style="font-family: Arial; font-size: 14px; color: rgb(0,0,0);" garbage. No <span> wrappers for every word. No Microsoft Office artifacts. Just pure, semantic HTML that your CMS will love.

✨ Built with Vibe Coding

This entire editor was vibe-coded with Claude Opus 4.5. I described what I wanted, iterated on feedback, and watched the magic happen.


Is it perfect? No. Does it work exactly how I need it? Absolutely.

$20 AI costs
7000+ lines of code
3 days of “work”
0 dependencies

Even this landing page was vibe-coded in just a couple of prompts 😅

Author photo

Fëdor Ananin

Wannabe founder, marketer, and ludocoder 🎰

I travel with my cat and try to make this world a little bit better.