Self-Hosted WYSIWYG Editor in Vanilla JS – Neiki Editor
It supports configurable toolbars, light and dark themes, built-in localization for eight languages, and a plugin registration API for custom toolbar extensions.
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) where content is 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.
bold, italic, underline, strikethrough, subscript, superscript, removeFormatheading, fontSize, fontFamily, foreColor, backColoralignLeft, alignCenter, alignRight, alignJustify, bulletList, numberedList, indent, outdentinsertDropdown): Renders a single Insert button that opens a dropdown with Link, Image, Table, Emoji, and Special Characters items. Use the individual keys link, image, table, emoji, and specialChars to place these as standalone buttons.undo, redo, findReplace, viewCode, blockquote, horizontalRulemoreMenu): 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)'
}
}
}); 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]);
The post Self-Hosted WYSIWYG Editor in Vanilla JS – Neiki Editor appeared first on CSS Script.
CalendarJS is a feature-rich JavaScript calendar library that allows you to create calendars, date pickers,…
LANSING, MI (WOWO) A Michigan township official is urging communities to update zoning policies as…
A critical vulnerability in Flowise and multiple AI frameworks has been discovered by OX Security,…
Vercel has disclosed a significant security incident after threat actors gained unauthorized access to internal…
HAMMOND, IND. (WOWO) Indiana officials have approved a lease amendment that will allow more frequent…
HAMMOND, IND. (WOWO) Indiana officials have approved a lease amendment that will allow more frequent…
This website uses cookies.