
Neiki Editor is a vanilla JavaScript rich text editor that turns a textarea into a full-featured WYSIWYG HTML editor with a single JS call.
It supports configurable toolbars, light and dark themes, built-in localization for eight languages, and a plugin registration API for custom toolbar extensions.
Features:
- Fully configurable toolbar with support for button ordering, group separators, and responsive wrapping on smaller screens.
- Light and dark theme support with automatic persistence across page reloads.
- Built-in localization for English, Czech, Chinese, Spanish, German, French, Portuguese, and Japanese.
- Custom translation support for any language via static registration or inline config.
- Find and Replace with case-sensitive search and regular expression support.
- Floating toolbar appears above selected text for quick inline formatting access.
- Table insertion with a right-click context menu for adding rows, columns, and merging or splitting cells.
- Image insertion by URL, file upload, or direct drag-and-drop into the editor area.
- Autosave to localStorage with debounced writes, status bar feedback, and restore on page reload.
- Plugin API for registering custom toolbar buttons with SVG icons, tooltips, and initialization callbacks.
- HTML source view toggle for switching between visual mode and raw markup editing.
- Status bar displays live word count, character count, autosave state, and current block type.
- PHP integration helper with asset loading, editor rendering, and server-side HTML sanitization methods.
- Content export as HTML file download and browser print dialog support.
Use Cases:
- CMS content editors that need a self-hosted rich text field with no reliance on third-party services.
- Blog platforms that need a configurable toolbar scoped to specific formatting options per post type.
- Admin dashboards that require structured HTML content authoring with table editing support.
- PHP web applications that need a server-side render helper and sanitization layer for form-submitted HTML.
How to use it:
1. Load the Neiki Editor’s JavaScript and stylesheet:
<!– Load the editor stylesheet –>
<link rel=”stylesheet” href=”https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@latest/dist/neiki-editor.css”>
<!– Load the editor script –>
<script src=”https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@latest/dist/neiki-editor.js”></script>
2. Add a textarea to your webpage.
<textarea id="editor"> <h2>Weekly Product Digest</h2> <p>Write a polished update for the team.</p> </textarea>
3. Initialize Neiki Editor with default options.
const editor = new NeikiEditor('#editor');4. Cofigure your editor with the following options:
placeholder(string): Placeholder text displayed when the editor contains no content. Default:'Start typing...'.minHeight(number): Minimum editor height in pixels. Default:300.maxHeight(number|null): Maximum editor height in pixels. The editor adds vertical scrolling when content exceeds this value. Default:null.autofocus(boolean): Moves keyboard focus to the editor on initialization. Default:false.spellcheck(boolean): Activates the browser’s built-in spellcheck on editor content. Default:true.readonly(boolean): Puts the editor into read-only mode on load. Default:false.theme(string): Sets the initial color theme. Accepts'light'or'dark'. Default:'light'.language(string): Sets the UI language. Accepts'en','cs','zh','es','de','fr','pt', or'ja'. Default:'en'.translations(object|null): Custom translation key map merged on top of the selected built-in language pack. Default:null.toolbar(array): Controls which toolbar buttons appear and in what order. Insert the string'|'at any position to add a visual group separator.onChange(function|null): Callback invoked on every content change. Receives(content, editor)wherecontentis the current HTML string. Default:null.onSave(function|null): Callback invoked when the user triggers Save via Ctrl+S or the More menu. Receives(content, editor). Default:null.onFocus(function|null): Callback invoked when the editor gains keyboard focus. Receives(editor). Default:null.onBlur(function|null): Callback invoked when the editor loses focus. Receives(editor). Default:null.onReady(function|null): Callback invoked once after the editor finishes initializing. Receives(editor). Default:null.showHelp(boolean): Shows or hides the Help entry in the More menu (⋯). Default:true.
const editor = new NeikiEditor('#editor', {
// Placeholder text shown when the editor is empty
placeholder: 'Write your article content here...',
// Minimum editor height in pixels
minHeight: 400,
// Maximum height before scrolling kicks in (null = no limit)
maxHeight: 800,
// Focus the editor immediately after load
autofocus: false,
// Enable browser-native spellcheck
spellcheck: true,
// Set to true to block all editing input
readonly: false,
// 'light' or 'dark'
theme: 'light',
// UI language — see i18n section for all supported codes
language: 'en',
// Custom translation overrides merged with built-in keys
translations: null,
// Toolbar buttons in left-to-right order — '|' adds a visual separator
toolbar: [
'viewCode', 'undo', 'redo', 'findReplace', '|',
'bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript', 'removeFormat', '|',
'heading', 'fontFamily', 'fontSize', '|',
'foreColor', 'backColor', '|',
'alignLeft', 'alignCenter', 'alignRight', 'alignJustify', '|',
'indent', 'outdent', '|',
'bulletList', 'numberedList', 'blockquote', 'horizontalRule', '|',
'insertDropdown', '|',
'moreMenu'
],
// Fires on every content change — receives the HTML string and editor instance
onChange: function(content, editor) {
console.log('Content updated at', new Date().toISOString());
},
// Fires on Ctrl+S or More menu → Save
onSave: function(content, editor) {
console.log('Save requested — content length:', content.length);
},
// Fires when the editor gains focus
onFocus: function(editor) {
console.log('Editor is focused');
},
// Fires when the editor loses focus
onBlur: function(editor) {
console.log('Editor lost focus');
},
// Fires once after the editor finishes initializing
onReady: function(editor) {
console.log('Neiki Editor is ready');
},
// Show or hide the Help item inside the More menu
showHelp: true
});5. The toolbar array accepts any of the following string keys. Add '|' between keys to create visual separator groups.
- Text formatting:
bold,italic,underline,strikethrough,subscript,superscript,removeFormat - Text style:
heading,fontSize,fontFamily,foreColor,backColor - Alignment and lists:
alignLeft,alignCenter,alignRight,alignJustify,bulletList,numberedList,indent,outdent - Insert Dropdown (
insertDropdown): Renders a single Insert button that opens a dropdown with Link, Image, Table, Emoji, and Special Characters items. Use the individual keyslink,image,table,emoji, andspecialCharsto place these as standalone buttons. - Standalone tools:
undo,redo,findReplace,viewCode,blockquote,horizontalRule - More Menu (
moreMenu): Renders a⋯button pinned to the right that opens a dropdown with Save, Preview, Download, Print, Autosave, Clear All, Toggle Theme, Fullscreen, and Help.
6. API methods:
// Get the current editor content as an HTML string
const html = editor.getContent();
// Replace editor content with new HTML
editor.setContent('<p>Replaced content goes here.</p>');
// Get the editor content as plain text — all HTML tags stripped
const plain = editor.getText();
// Returns true when the editor has no content
const isEmpty = editor.isEmpty();
// Alias for getContent()
const html2 = editor.getHTML();
// Alias for setContent()
editor.setHTML('<p>Set via alias method.</p>');
// Get a structured JSON representation of the current content
const json = editor.getJSON();
// Populate the editor from a previously captured JSON structure
editor.setJSON(json);
// Move keyboard focus to the editor
editor.focus();
// Remove keyboard focus from the editor
editor.blur();
// Re-enable editing after the editor was disabled
editor.enable();
// Disable all editing input — puts the editor into read-only mode
editor.disable();
// Remove the editor instance and restore the original textarea element
editor.destroy();
// Toggle fullscreen mode on and off
editor.toggleFullscreen();
// Toggle between light and dark themes
editor.toggleTheme();
// Set a specific theme directly
editor.setTheme('dark');
// Manually trigger the onSave callback
editor.triggerSave();
// Open the content preview modal
editor.previewContent();
// Trigger the browser download dialog with content as an HTML file
editor.downloadContent();
// Remove all content from the editor
editor.clearAll();
// Execute a built-in formatting command by name
editor.execCommand('bold');
// Insert an HTML string at the current cursor position
editor.insertHTML('<mark>Highlighted passage</mark>');
// Wrap the current selection in a specified element with optional attributes
editor.wrapSelection('mark', { class: 'callout' });
// Remove a wrapping element from the current selection
editor.unwrapSelection('mark');
// Return the browser's Selection object for the current selection
const selection = editor.getSelection();7. The plugin system adds custom toolbar buttons with full access to the editor instance. Register plugins before or after initialization. They remain available across all instances on the page.
name(string, required): Unique plugin identifier. No spaces. Used internally to register and retrieve the plugin.icon(string, optional): SVG markup for the toolbar button icon. Omit this property to register a background-only plugin with no button.tooltip(string, optional): Text shown in the button’s tooltip on hover.action(function, optional): Function called when the toolbar button is clicked. Receives the editor instance as its argument.init(function, optional): Function called once during editor initialization. Receives the editor instance. Use this for one-time setup logic.
// Register a custom reading-time plugin with a toolbar button
NeikiEditor.registerPlugin({
// Unique identifier for this plugin — no spaces
name: 'reading-time-counter',
// SVG icon rendered as the toolbar button graphic
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>',
// Tooltip text shown when hovering the toolbar button
tooltip: 'Estimate Reading Time',
// Runs when the user clicks the toolbar button
action: function(editor) {
const words = editor.getText().trim().split(/\s+/).filter(Boolean).length;
const minutes = Math.ceil(words / 200);
alert('Estimated reading time: ' + minutes + ' minute(s)');
},
// Runs once when the editor initializes — useful for DOM setup or event listeners
init: function(editor) {
console.log('Reading time plugin initialized');
}
});
// Get an array of all registered plugin objects
const plugins = NeikiEditor.getPlugins();8. Themes and Localization:
// Initialize with dark theme
const editor = new NeikiEditor('#editor', { theme: 'dark' });
// Toggle between light and dark at runtime
editor.toggleTheme();
// Set a specific theme programmatically
editor.setTheme('light');
// Initialize with French UI
const editorFr = new NeikiEditor('#article-body', { language: 'fr' });
// Register custom translation keys for German — only override what you need
// English serves as the fallback for any key you leave out
NeikiEditor.addTranslation('de', {
'toolbar.bold': 'Fett (Strg+B)',
'toolbar.italic': 'Kursiv (Strg+I)',
'toolbar.undo': 'Rückgängig (Strg+Z)',
'toolbar.redo': 'Wiederholen (Strg+Y)'
});
const editorDe = new NeikiEditor('#article-body', { language: 'de' });
// Alternatively, pass translations inline in the config object
const editorInline = new NeikiEditor('#editor', {
language: 'de',
translations: {
de: {
'toolbar.bold': 'Fett (Strg+B)',
'toolbar.italic': 'Kursiv (Strg+I)'
}
}
});Framework Integration:
React:
import { useEffect, useRef } from 'react';
function ArticleEditor({ initialValue, onChange }) {
const textareaRef = useRef(null);
const editorRef = useRef(null);
useEffect(() => {
// Initialize the editor once the component mounts
editorRef.current = new NeikiEditor(textareaRef.current, {
onChange: (content) => onChange?.(content)
});
// Destroy the editor instance when the component unmounts
return () => editorRef.current?.destroy();
}, []);
return <textarea ref={textareaRef} defaultValue={initialValue} />;
}
Vue.js:
<template>
<textarea ref="editorEl"></textarea>
</template>
<script>
export default {
mounted() {
// Initialize after the component's DOM is ready
this.editor = new NeikiEditor(this.$refs.editorEl, {
onChange: (content) => {
this.$emit('update:modelValue', content);
}
});
},
beforeUnmount() {
// Remove the editor before Vue tears down the component
this.editor.destroy();
}
}
</script>
AJAX auto-save:
// Debounce saves so the server receives one request per 2-second idle window
const editor = new NeikiEditor('#article-body', {
onChange: debounce(function(content) {
fetch('/api/posts/draft', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content })
});
}, 2000)
});
PHP:
<?php require_once 'php/neiki-editor.php'; ?>
<!DOCTYPE html>
<html>
<head>
<!-- Output CDN <link> and <script> tags — call once per page -->
<?= NeikiEditor::assets() ?>
</head>
<body>
<form method="POST" action="publish.php">
<!-- Render the textarea and initialization script for the 'body' field -->
<?= NeikiEditor::render('body', $post->content, [
'minHeight' => 450,
'placeholder' => 'Write your post content here...'
]) ?>
<button type="submit">Publish</button>
</form>
</body>
</html>
// publish.php — always sanitize HTML before writing to the database
require_once 'php/neiki-editor.php';
$safe = NeikiEditor::sanitize($_POST['body']);
$db->query('UPDATE posts SET content = ? WHERE id = ?', [$safe, $postId]);







