A zero-dependency WYSIWYG rich-text editor built with vanilla JavaScript (ES2022+) — no jQuery, no runtime dependencies. Lightweight alternative to Summernote, Quill, TinyMCE, Froala, CKEditor, ProseMirror, Trix, and Slate — with official React and Vue 3 wrappers.
Fast. Lightweight. Reliable. Efficient.
Live Demo · Docs · Playground
- Features
- Installation
- Framework Wrappers
- Quick Start
- Plugin API
- API
- Options
- Toolbar Customisation
- Keyboard Shortcuts
- Mentions
- Project Structure
- Comparison
- License
- Text formatting — bold, italic, underline, strikethrough, superscript, subscript
- Paragraph styles — Normal, H1–H6, Blockquote, Code block
- Font family — customisable dropdown (10 families by default)
- Font size — configurable default via
defaultFontSize; applied to new content - Line height — dropdown from 1.0 to 3.0
- Text and highlight colour — native colour picker with last-used colour memory; custom brand swatches via
colorSwatches - Alignment — left, center, right, justify
- Lists — unordered and ordered, with indent / outdent; Tab/Shift+Tab in list context
- Checklist — interactive checkbox list; toggle items by clicking; converts to/from plain paragraphs on outdent
- Undo / redo — built-in history stack (configurable depth via
historyLimit,Ctrl+Z/Ctrl+Y) - Tab key — configurable spaces-per-tab; smart list indentation inside
<li> - RTL support — set
direction: 'rtl'to flip the editor layout and text direction
- Horizontal rule — inserts an
<hr>at the current caret position - Link dialog — URL, display text (auto-filled from selection), "Open in new tab" checkbox; edits existing links when caret is inside an
<a> - Image dialog — insert by URL with alt text, or file upload (base64 embed); enforces
maxImageSize; file input restricted to browser-renderable MIME types; supports customonImageUploadhandler for server-side upload - Image crop overlay — inline interactive crop tool triggered from the image tooltip; corner and edge drag handles; canvas-based crop export; CORS fallback warning
- Video dialog — paste a YouTube watch/short URL, Vimeo URL, or direct
.mp4 / .webm / .oggURL; configurable width; renders as responsive<iframe>or<video> - Table — interactive grid picker (up to 10x10); optional header row (
tableHeaderRow); floating tooltip for full row/column/cell management - Emoji picker — approximately 380 Unicode emoji across 7 categories (Smileys, People, Animals, Food, Travel, Objects, Symbols); keyword search; click to insert as plain UTF-8 character
- FA Icon picker — FontAwesome 6 Free Solid icons across 8 categories; keyword search; configurable style, size, and colour; inserts as
<i>element; auto-injects FA CDN if not on the page
- Find and Replace —
Ctrl+Fto find,Ctrl+Hfor find-and-replace; compact non-blocking floating panel (top-right); TreeWalker text matching;<mark>highlighting; case-sensitiveAatoggle; regex mode (.*toggle); icon-button Prev/Next navigation (↑ ↓); single and replace-all modes - Auto language detection — when selected text is formatted as a code block the editor automatically analyses the content and applies Prism.js syntax highlighting; 20 languages detected: JavaScript, TypeScript, Python, HTML, CSS, SCSS, JSON, SQL, Bash, Java, C#, PHP, Ruby, Go, Rust, C++, C, Kotlin, Swift, XML
Floating toolbars appear automatically when the user clicks on an editable element:
| Element | Actions |
|---|---|
| Link | Open in new tab, Edit (reopens dialog), Unlink |
| Image | Edit alt/URL (reopens dialog), Crop, Delete |
| Video | Edit (reopens dialog), Delete |
| Table cell | Row above/below, Delete row, Column left/right, Delete column, Merge cells, Unmerge cells, Cell selection mode, Column width, Row height, Border width/color, Cell background colour, Cell padding, Sort ↑↓, Export CSV, Toggle header row, Delete table |
| Code block | Copy code, Language selector (20 languages + SCSS), Line numbers toggle, Delete block |
Right-click inside the editor opens a context menu with: Undo, Redo, Cut, Copy, Paste, Bold, Italic, Underline, Copy Format, Paste Format, Remove Format, and a colour palette for quick text/highlight colour changes.
- Built-in locales — English (
en), Vietnamese (vi), Japanese (ja), Simplified Chinese (zh), French (fr), German (de), Spanish (es), Korean (ko) - Custom locale — pass any partial locale object to override individual strings
- Per-instance language — set a different
langper editor instance on the same page - Auto-fallback — unknown codes or missing keys fall back to English
- Toolbar — fully configurable button groups; overflow strategy:
wrap(default) orscroll; on viewports ≤ 640 px the toolbar automatically switches to a single horizontally-scrollable row regardless of thetoolbarOverflowsetting; FontAwesome icons with built-in SVG fallback - Sticky toolbar —
stickyToolbar: truepins the toolbar to the viewport top; configurable offset for fixed nav bars - Dark / light / auto theme —
theme: 'dark','light'(default), or'auto'(follows OSprefers-color-scheme); full SCSS variable coverage; dark styles propagate to all floating elements (dialogs, tooltips, colour pickers) - Draggable dialogs — all dialogs (Link, Image, Video, Emoji, Icon, Find & Replace) can be repositioned by dragging their title bar; position is clamped to the viewport
- Image resizer — drag handle on selected image to resize proportionally
- Video resizer — drag handle on selected video embed to resize
- Statusbar — live word and character count; drag handle to resize editor height; limit warnings when
maxCharsormaxWordsis reached - Code view — toggle raw HTML; sanitised before applying back to the editor
- Fullscreen — expands the editor to fill the viewport
- Placeholder — CSS
::beforepseudo-element, zero DOM node cost - Read-only mode —
readOnly: truerenders a non-editable preview with toolbar hidden; toggle at runtime viaeditor.setDisabled() - Auto-save —
autoSave: truepersists content tolocalStorageon every change; key configurable viaautoSaveKey - Auto-save restore — when
autoSaveandautoSaveRestoreare bothtrue, a dismissible banner prompts the user to restore or discard a previously saved draft on load; configurable age window viaautoSaveRestoreTimeout - Bubble toolbar —
bubbleToolbar: trueshows a compact floating toolbar above selected text; default buttons: bold, italic, underline, strikethrough, link, text colour, highlight colour, remove format, inline code; each colour button displays a live colour-strip indicator and opens an inline colour palette (matching the context menu); button set configurable viabubbleToolbarItems - Markdown shortcuts —
markdownShortcuts: true(default) converts Markdown syntax typed in the editor into HTML in real time:#→ H1–H3,>→ blockquote,-/*→ unordered list,1.→ ordered list,[ ]→ checklist,---→ HR,```→ code block; inline:**bold**,*italic*,~~strikethrough~~,`code` - Custom focus ring —
focusColoraccepts any CSS colour string to override the default blue focus ring - Spellcheck — browser spellcheck enabled by default (
spellcheck: true)
- No jQuery — pure vanilla ES2022, zero runtime dependencies
- Bootstrap friendly — optional Bootstrap 4/5 styling (
useBootstrap: true) - FontAwesome ready — auto-detects FA on the page; falls back to built-in SVG icons
- Plugin API — first-class plugin system:
AutumnNote.use(plugin),context.getPlugin(name), global button registry (registerButton), per-instance installation,AsnPlugin<T>TypeScript interface - Tree-shakeable — ES module build; all core utilities individually exported
- TypeScript definitions — bundled
types/index.d.tswith full JSDoc coverage - @mention autocomplete — type
@(or any custom trigger) to open a floating dropdown backed by a user-suppliedonSearchfunction (callback orasync/Promise); inserts a non-editable mention chip; customisable chip HTML viaonInsert
- All HTML (pasted content,
setHTML(), or code-view output) passes through a DOM-based sanitiser that strips<script>,<object>,<embed>, and allon*event handler attributes <iframe>elements are permitted insetHTML()with src restricted to trusted CDN hosts;srcdocis strippedjavascript:anddata:URLs are rejected in links and images- Clipboard paste sanitises rich content to remove XSS vectors before inserting
pasteStripAttributesoption stripsclass,style, anddata-*from pasted HTML
npm install autumnnoteimport AutumnNote from 'autumnnote';
import 'autumnnote/dist/autumnnote.css';jsDelivr (Recommended):
<script src="https://cdn.jsdelivr.net/npm/autumnnote"></script>unpkg:
<script src="https://unpkg.com/autumnnote"></script><link rel="stylesheet" href="dist/autumnnote.css" />
<script src="dist/autumnnote.umd.js"></script>FontAwesome icons — the editor auto-detects FontAwesome on the page and falls back to built-in SVG icons when absent. To enable FA icons, include the FA stylesheet:
<!-- FontAwesome 6 Free (recommended) --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css">
Official React and Vue 3 wrappers are available as separate packages in this monorepo (managed with pnpm workspaces).
npm install autumnnote autumnnote-react
import 'autumnnote/dist/autumnnote.css';import { useRef } from 'react';
import AutumnNoteEditor from 'autumnnote-react';
function MyEditor() {
const editorRef = useRef(null);
return (
<AutumnNoteEditor
ref={editorRef}
options={{ placeholder: 'Start typing…', height: 300, bubbleToolbar: true }}
/>
);
}
// Access the editor instance:
editorRef.current.getHTML();
editorRef.current.invoke('editor.setHTML', '<p>Hello!</p>');The ref is forwarded to the underlying Context instance via useImperativeHandle. Pass a key prop to force remount when options change.
npm install autumnnote autumnnote-vue
import 'autumnnote/dist/autumnnote.css';<script setup>
import { ref } from 'vue';
import AutumnNoteEditor from 'autumnnote-vue';
const editorRef = ref(null);
</script>
<template>
<AutumnNoteEditor
ref="editorRef"
:options="{ placeholder: 'Start typing…', height: 300 }"
/>
</template>Access the editor instance via editorRef.value.editor.value (the editor reactive ref exposed by defineExpose).
import AutumnNote from 'autumnnote';
const editor = AutumnNote.create('#my-editor', {
placeholder: 'Start typing…',
height: 300,
onChange(html) {
console.log(html);
},
});<div id="my-editor"><p>Hello!</p></div>
<script src="dist/autumnnote.umd.js"></script>
<script>
const editor = AutumnNote.create('#my-editor');
</script>const editor = AutumnNote.create('#my-editor', {
useBootstrap: true,
bootstrapVersion: 5,
toolbarButtonClass: 'btn btn-sm btn-light',
});const editor = AutumnNote.create('#my-editor', { theme: 'dark' });const preview = AutumnNote.create('#preview', { readOnly: true });
preview.setHTML(savedHtml);const editor = AutumnNote.create('#my-editor', {
autoSave: true,
autoSaveKey: 'my-draft',
});const editor = AutumnNote.create('#my-editor', {
onImageUpload(files) {
const fd = new FormData();
fd.append('file', files[0]);
fetch('/api/upload', { method: 'POST', body: fd })
.then(r => r.json())
.then(({ url }) => editor.insertImage(url, files[0].name));
},
});const editor = AutumnNote.create('#my-editor', {
bubbleToolbar: true,
bubbleToolbarItems: ['bold', 'italic', 'underline', 'strikethrough', 'link', 'removeFormat'],
});const editor = AutumnNote.create('#my-editor', {
mention: {
onSearch(query, callback) {
const users = [
{ id: 1, label: 'Alice' },
{ id: 2, label: 'Bob' },
{ id: 3, label: 'Charlie' },
];
callback(users.filter(u => u.label.toLowerCase().includes(query.toLowerCase())));
},
onInsert(item) {
// optional: return custom HTML for the mention chip
return `<span class="mention" data-id="${item.id}">@${item.label}</span>`;
},
},
});Plugins package editor extensions — custom modules, toolbar buttons, and event handlers — into a reusable, distributable object.
import AutumnNote from 'autumnnote';
const WordCountPlugin = {
name: 'word-count',
version: '1.0.0',
// Buttons registered BEFORE create() — usable by name in toolbar config
buttons: [{
name: 'wordCountBtn',
icon: 'hashtag',
tooltip: 'Word count',
action: (ctx) => alert(`${ctx.getWordCount()} words`),
}],
// Called after all built-in modules initialise
install(ctx, options) {
ctx.on('change', () => console.log('words:', ctx.getWordCount()));
return { getMax: () => options.maxWords };
},
uninstall(ctx) { /* cleanup */ },
};
// Global — applied to every future editor instance
AutumnNote.use(WordCountPlugin, { maxWords: 500 });
const editor = AutumnNote.create('#editor', {
toolbar: [['bold', 'italic', 'wordCountBtn']], // 'wordCountBtn' resolved from registry
});
editor.getPlugin('word-count').getMax(); // → 500Per-instance installation:
const editor = AutumnNote.create('#editor');
editor.use(WordCountPlugin, { maxWords: 200 });
editor.invoke('toolbar.rebuild'); // re-render toolbar with new buttons| Method | Description |
|---|---|
AutumnNote.use(plugin, opts?) |
Install globally. Buttons registered immediately; install() called after modules init. |
AutumnNote.hasPlugin(name) |
Returns true if plugin registered globally. |
AutumnNote.registerButton(def) |
Register a single button globally by name. |
context.use(plugin, opts?) |
Install on this instance only. |
context.getPlugin<T>(name) |
Returns the public API from plugin.install(). |
See the full Plugin API docs →
| Method | Description |
|---|---|
AutumnNote.create(selector, options?) |
Creates editor instance(s). selector can be a CSS string, Element, NodeList, or Element[]. Returns a Context or Context[]. |
AutumnNote.destroy(selector) |
Destroys editor(s) and restores the original element. |
AutumnNote.getInstance(selector) |
Returns the Context for a given element, or null. |
AutumnNote.defaults |
Global default options object — mutate before calling create() to apply project-wide settings. |
| Method | Description |
|---|---|
editor.getHTML() |
Returns the current HTML. Zero-width spaces from the icon picker are stripped automatically. |
editor.setHTML(html) |
Sets HTML content (sanitised). <iframe> elements are preserved. |
editor.getText() |
Returns plain text with no markup. |
editor.clear() |
Clears all content, resets to an empty <p>. |
editor.clearHistory() |
Resets the undo/redo stack. |
editor.getUndoCount() |
Returns the number of available undo steps. |
editor.getRedoCount() |
Returns the number of available redo steps. |
editor.getWordCount() |
Returns the current word count. |
editor.getCharCount() |
Returns the current character count. |
editor.getTableOfContents() |
Returns an array of { level, text, element } heading objects. |
editor.setDisabled(bool) |
Disables (true) or re-enables (false) the editor and toolbar. |
editor.destroy() |
Removes the editor, disposes all modules, and restores the original element. |
editor.on(event, fn) |
Subscribes to an editor event. Returns an unsubscribe function. |
editor.off(event, fn) |
Removes a previously registered listener. |
editor.invoke('module.method', ...args) |
Calls any registered module method by dot-separated name. |
| Name | Payload | Description |
|---|---|---|
change |
html: string |
Fired after every content mutation. Debounced internally. |
focus |
context |
Editor gained focus. |
blur |
context |
Editor lost focus. |
init |
context |
Fired once after the editor has fully initialised. |
imageUpload |
files: FileList |
Fired when images are dropped or pasted (when onImageUpload is provided). |
imageError |
{ file, message } |
Fired when an image is rejected (e.g. over maxImageSize). |
paste |
{ text, html } |
Fired after every paste event. |
selectionChange |
context |
Fired when the cursor or selection changes. |
destroy |
context |
Fired just before the editor is destroyed. |
charLimitReached |
context |
Fired when maxChars is hit. |
wordLimitReached |
context |
Fired when maxWords is hit. |
| Option | Type | Default | Description |
|---|---|---|---|
placeholder |
string |
'' |
Placeholder text shown when the editor is empty. |
height |
number |
200 |
Initial editor height in pixels. |
minHeight |
number |
100 |
Minimum resizable height in pixels. |
maxHeight |
number |
0 |
Maximum resizable height in pixels. 0 = unlimited. |
focus |
boolean |
false |
Automatically focus the editor on creation. |
resizable |
boolean |
true |
Show the resize handle at the bottom of the editor. |
toolbar |
Array[] |
all buttons | Toolbar layout. See Toolbar Customisation. |
toolbarOverflow |
string |
'wrap' |
Toolbar overflow strategy: 'wrap' or 'scroll'. |
useBootstrap |
boolean |
false |
Apply Bootstrap CSS classes to toolbar buttons. |
bootstrapVersion |
number |
5 |
Bootstrap major version (4 or 5). |
toolbarButtonClass |
string |
'btn btn-sm btn-light' |
CSS classes for toolbar buttons when useBootstrap is true. |
useFontAwesome |
boolean |
true |
Use FA icons when FontAwesome is detected on the page. |
fontAwesomeClass |
string |
'fas' |
FA prefix: 'fas' for FA 5, 'fa-solid' for FA 6. |
pasteAsPlainText |
boolean |
false |
Strip all formatting when pasting. |
pasteCleanHTML |
boolean |
true |
Sanitise HTML on paste. |
pasteStripAttributes |
boolean |
false |
Strip class, style, and data-* attributes from pasted HTML. |
markdownPaste |
boolean |
true |
Convert pasted Markdown shortcuts to HTML as you type. |
allowImageUpload |
boolean |
true |
Allow drag/paste/file upload in the image dialog. |
maxImageSize |
number |
5 |
Maximum image file size in megabytes. |
tabSize |
number |
4 |
Number of spaces inserted when Tab is pressed. |
historyLimit |
number |
100 |
Maximum undo steps to retain. |
defaultFontFamily |
string |
'Arial' |
Font shown by default in the font-family dropdown. |
defaultFontSize |
string |
'14px' |
Default font size applied to new content. |
fontFamilies |
string[] |
10 fonts | Font families available in the font-family dropdown. |
stickyToolbar |
boolean |
false |
Pin the toolbar to the viewport top when the page is scrolled. |
stickyToolbarOffset |
number |
0 |
Top offset in pixels for the sticky toolbar (e.g. height of a fixed nav bar). |
theme |
string |
'light' |
Colour theme: 'light' or 'dark'. |
readOnly |
boolean |
false |
Start the editor in non-editable (read-only) mode with toolbar hidden. |
spellcheck |
boolean |
true |
Enable browser spellcheck in the editable area. |
direction |
string |
'ltr' |
Text direction: 'ltr' or 'rtl'. |
autoSave |
boolean |
false |
Persist content to localStorage on every change. |
autoSaveKey |
string |
'autumnnote-autosave' |
localStorage key used when autoSave is enabled. |
maxChars |
number |
0 |
Maximum character count. 0 = unlimited. Shows warning in statusbar. |
maxWords |
number |
0 |
Maximum word count. 0 = unlimited. Shows warning in statusbar. |
tableHeaderRow |
boolean |
false |
Insert a <thead> header row when creating new tables. |
codeHighlight |
boolean |
true |
Auto-load Prism.js for syntax highlighting inside <pre><code> blocks. |
codeHighlightCDN |
string |
cdnjs Prism 1.29.0 | Base CDN URL for Prism assets. |
colorSwatches |
string[] |
[] |
Custom brand colour swatches prepended to the colour picker palette. |
focusColor |
string |
null |
Custom focus ring colour (any valid CSS colour). Overrides the default blue. |
lang |
string | object |
'en' |
UI display language. Built-in codes: 'en', 'vi', 'ja', 'zh', 'fr', 'de', 'es', 'ko'. Pass a partial locale object for custom overrides. |
markdownShortcuts |
boolean |
true |
Convert Markdown-style syntax typed in the editor to HTML in real time (block and inline rules). |
bubbleToolbar |
boolean |
false |
Show a mini floating toolbar above the text selection for quick formatting. |
bubbleToolbarItems |
string[] |
['bold','italic','underline','link','foreColor','removeFormat'] |
Buttons shown in the bubble toolbar. Available names: 'bold', 'italic', 'underline', 'strikethrough', 'link', 'foreColor', 'removeFormat', 'inlineCode'. |
autoSaveRestore |
boolean |
false |
When autoSave is also true, show a restore banner on load if a draft exists. |
autoSaveRestoreTimeout |
number |
7 |
Max draft age in days before it is auto-discarded. 0 = no expiry. |
onAutoSaveRestore |
Function |
null |
(html, context) => void — called after the user restores a draft. |
mention |
object |
null |
@mention configuration object. Set mention.onSearch to activate. See Mentions. |
onChange |
Function |
null |
(html: string) => void — called on every content change. |
onFocus |
Function |
null |
(context) => void — called when the editor gains focus. |
onBlur |
Function |
null |
(context) => void — called when the editor loses focus. |
onInit |
Function |
null |
(context) => void — called once after the editor is initialised. |
onImageUpload |
Function |
null |
(files: FileList) => void — custom upload handler. Overrides base64 embed. |
onImageError |
Function |
null |
({ file, message }) => void — called when an image is rejected. |
onPaste |
Function |
null |
({ text, html }) => void — called after every paste event. |
onSelectionChange |
Function |
null |
(context) => void — called when cursor or selection changes. |
onDestroy |
Function |
null |
(context) => void — called just before the editor is destroyed. |
onCharLimitReached |
Function |
null |
(context) => void — called when maxChars is hit. |
onWordLimitReached |
Function |
null |
(context) => void — called when maxWords is hit. |
The toolbar option accepts an array of groups. Each group is an array of button definition objects exported from the package:
import AutumnNote, {
boldBtn, italicBtn, underlineBtn, strikeBtn,
superscriptBtn, subscriptBtn,
alignLeftBtn, alignCenterBtn, alignRightBtn, alignJustifyBtn,
ulBtn, olBtn, checklistBtn, indentBtn, outdentBtn,
undoBtn, redoBtn,
hrBtn, linkBtn, imageBtn, videoBtn,
emojiBtn, iconBtn, tableBtn,
fontFamilyBtn, paragraphStyleBtn, lineHeightBtn,
foreColorBtn, backColorBtn,
findBtn, findReplaceBtn,
codeviewBtn, fullscreenBtn, shortcutsBtn,
} from 'autumnnote';[
[paragraphStyleBtn, fontFamilyBtn, lineHeightBtn],
[undoBtn, redoBtn],
[boldBtn, italicBtn, underlineBtn, strikeBtn],
[superscriptBtn, subscriptBtn],
[foreColorBtn, backColorBtn],
[alignLeftBtn, alignCenterBtn, alignRightBtn, alignJustifyBtn],
[ulBtn, olBtn, checklistBtn, indentBtn, outdentBtn],
[hrBtn, linkBtn, imageBtn, videoBtn, tableBtn, emojiBtn, iconBtn],
[codeviewBtn, fullscreenBtn, shortcutsBtn],
]AutumnNote.create('#editor', {
toolbar: [
[undoBtn, redoBtn],
[boldBtn, italicBtn, underlineBtn],
[ulBtn, olBtn, checklistBtn],
[linkBtn, imageBtn],
[findBtn, findReplaceBtn],
],
});Pass an empty array for a toolbar-less editor (keyboard shortcuts still work):
AutumnNote.create('#editor', { toolbar: [] });| Export | Tooltip |
|---|---|
paragraphStyleBtn |
Paragraph Style (dropdown) |
fontFamilyBtn |
Font Family (dropdown) |
lineHeightBtn |
Line Height (dropdown) |
undoBtn / redoBtn |
Undo / Redo |
boldBtn / italicBtn / underlineBtn / strikeBtn |
Text style |
superscriptBtn / subscriptBtn |
Super / Subscript |
foreColorBtn / backColorBtn |
Text colour / Highlight colour |
alignLeftBtn / alignCenterBtn / alignRightBtn / alignJustifyBtn |
Alignment |
ulBtn / olBtn / checklistBtn |
Lists |
indentBtn / outdentBtn |
Indentation |
hrBtn |
Horizontal Rule |
linkBtn |
Insert / Edit Link |
imageBtn |
Insert Image |
videoBtn |
Insert Video |
tableBtn |
Insert Table (grid picker) |
emojiBtn |
Insert Emoji |
iconBtn |
Insert FA Icon |
findBtn |
Find (Ctrl+F) |
findReplaceBtn |
Find & Replace (Ctrl+H) |
codeviewBtn |
HTML Code View |
fullscreenBtn |
Fullscreen |
shortcutsBtn |
Keyboard Shortcuts dialog |
Object.assign(AutumnNote.defaults, {
height: 400,
theme: 'dark',
fontFamilies: ['Inter', 'Roboto', 'Georgia', 'Courier New'],
colorSwatches: ['#e74c3c', '#f39c12', '#2ecc71', '#3498db'],
});| Keys | Action |
|---|---|
Ctrl + Z |
Undo |
Ctrl + Shift + Z / Ctrl + Y |
Redo |
Ctrl + B |
Bold |
Ctrl + I |
Italic |
Ctrl + U |
Underline |
Ctrl + F |
Open Find dialog |
Ctrl + H |
Open Find & Replace dialog |
Shift + Enter |
Insert line break |
Tab |
Insert spaces / indent list item |
Shift + Tab |
Outdent list item |
Shift + ? |
Open Keyboard Shortcuts dialog |
The number of spaces inserted by
Tabis controlled by thetabSizeoption.
The mention option object activates @mention autocomplete. Only onSearch is required; all other fields are optional.
| Field | Type | Default | Description |
|---|---|---|---|
onSearch |
Function |
— | (query, callback) => void — called when the user types after the trigger character. Pass an array of { id, label, avatar? } to the callback. |
onInsert |
Function |
null |
(item) => string | null — return custom HTML for the inserted mention chip. Return null to use the built-in chip. |
trigger |
string |
'@' |
Character that opens the dropdown. |
minChars |
number |
0 |
Minimum characters after the trigger before onSearch is called. 0 = open immediately. |
maxResults |
number |
8 |
Maximum items shown in the dropdown. |
debounce |
number |
200 |
Debounce delay in milliseconds for onSearch calls. |
mentionClass |
string |
'an-mention' |
CSS class applied to the inserted mention chip. |
allowSpaces |
boolean |
false |
Allow spaces in the query string before the dropdown closes. |
AutumnNote.create('#editor', {
mention: {
trigger: '@',
minChars: 1,
onSearch(query, callback) {
fetch(`/api/users?q=${encodeURIComponent(query)}`)
.then(r => r.json())
.then(users => callback(users)); // [{ id, label, avatar? }]
},
},
});src/
├── js/
│ ├── core/
│ │ ├── dom.js DOM utilities (createElement, on, closest, ...)
│ │ ├── range.js Selection and Range API helpers
│ │ ├── func.js General helpers (mergeDeep, debounce, ...)
│ │ ├── key.js Keyboard key constants
│ │ ├── lists.js Array helpers
│ │ ├── env.js Browser/platform detection
│ │ ├── markdown.js Lightweight Markdown to HTML converter
│ │ └── sanitise.js DOM-based HTML and URL sanitiser
│ ├── editing/
│ │ ├── History.js Undo/redo stack (configurable depth)
│ │ ├── Style.js execCommand style wrappers
│ │ ├── Table.js Table creation and cell manipulation
│ │ └── Typing.js Tab/Enter/ArrowKey behaviour and FA icon caret handling
│ ├── module/
│ │ ├── Editor.js Core editing commands, getHTML/setHTML, sanitiser
│ │ ├── Toolbar.js Toolbar UI, button rendering (SVG + FA), dropdowns, colour picker
│ │ ├── Buttons.js Button/dropdown/colorpicker definitions and defaultToolbar
│ │ ├── Statusbar.js Word and character count, drag-to-resize, limit warnings
│ │ ├── Clipboard.js Paste sanitisation (HTML clean, plain-text, Markdown modes)
│ │ ├── ContextMenu.js Right-click context menu with colour palette
│ │ ├── Placeholder.js CSS-based placeholder
│ │ ├── Codeview.js HTML source view toggle
│ │ ├── Fullscreen.js Fullscreen mode
│ │ ├── FindReplace.js Find and Replace dialog (Ctrl+F / Ctrl+H)
│ │ ├── LinkDialog.js Link insert/edit dialog
│ │ ├── LinkTooltip.js Floating toolbar for links (open/edit/unlink)
│ │ ├── ImageDialog.js Image insert dialog (URL + file upload with MIME filtering)
│ │ ├── ImageTooltip.js Floating toolbar for images (edit/crop/delete)
│ │ ├── ImageResizer.js rAF-based drag handle to resize images
│ │ ├── ImageCropOverlay.js Inline crop tool (corner/edge handles, canvas export)
│ │ ├── VideoDialog.js Video embed dialog (YouTube, Vimeo, direct file)
│ │ ├── VideoTooltip.js Floating toolbar for video embeds (edit/delete)
│ │ ├── VideoResizer.js rAF-based drag handle to resize video embeds
│ │ ├── TableTooltip.js Floating toolbar for tables (row/col/merge/unmerge/select mode)
│ │ ├── CodeTooltip.js Floating toolbar for code blocks (copy/delete)
│ │ ├── EmojiDialog.js Unicode emoji picker (~380 emoji, 7 categories)
│ │ ├── IconDialog.js FontAwesome icon picker (FA 6 Free Solid, 8 categories)
│ │ ├── ShortcutsDialog.js Keyboard shortcuts reference dialog (Shift+?)
│ │ ├── BubbleToolbar.js Mini floating toolbar above text selection
│ │ ├── MarkdownShortcuts.js Inline Markdown-to-HTML input rules
│ │ ├── AutoSaveRestore.js Draft restore banner for localStorage drafts
│ │ └── Mention.js @mention autocomplete with floating dropdown
│ ├── Context.js Editor instance hub: module registry and event bus
│ ├── settings.js Default options (AsnOptions)
│ ├── renderer.js DOM layout builder
│ └── index.js Public entry point + AutumnNote factory
└── styles/
├── _variables.scss SCSS design tokens (colours, spacing, radii, transitions)
└── autumnnote.scss Main stylesheet
This project uses pnpm workspaces to manage the core library alongside official framework wrappers:
autumn-note-ce/
├── pnpm-workspace.yaml # workspace root
├── src/ # core library source
├── packages/
│ ├── react/ # autumnnote-react
│ │ └── src/index.jsx
│ └── vue/ # autumnnote-vue
│ └── src/AutumnNote.vue
└── test/ # Vitest test suite
pnpm install # install all workspace packages
npm run dev # start Vite dev server with HMR
npm run build # build core ES + UMD + CSS to dist/
pnpm --filter autumnnote-react build # build React wrapper
pnpm --filter autumnnote-vue build # build Vue wrapper
npm test # run Vitest test suite once
npm run test:watch # run tests in watch mode
npm run lint # ESLint
npm run typecheck # TypeScript type check (tsconfig.json)Build output in dist/:
| File | Format | Use |
|---|---|---|
autumnnote.es.js |
ES Module | import in bundlers (tree-shakeable) |
autumnnote.umd.js |
UMD | <script> tag / CommonJS |
autumnnote.css |
CSS | Styles for both builds |
The table below compares AutumnNote against popular WYSIWYG editors: Summernote, Quill, and TinyMCE. Comparison is based on publicly documented feature sets.
| Feature | Summernote | Quill | TinyMCE | AutumnNote |
|---|---|---|---|---|
| jQuery dependency | Required | Required | Optional | None |
| Runtime dependencies | Several | Several | 1–2 | Zero |
| JavaScript standard | ES5 / legacy | ES6 mix | ES6 | ES2022 |
| Module format | IIFE / AMD | IIFE | CommonJS + IIFE | ES Module + UMD |
| Build tool | Grunt | Gulp | Rollup | Vite |
| TypeScript definitions | External / partial | Partial | Yes | Yes (bundled) |
| HTML sanitisation | Basic | Whitelist-only | Moderate | DOM-based (XSS-safe) |
| Iframe support in setHTML | No | No | Restricted | Yes (host-trusted) |
| Dark theme | No | No | Yes | Yes (built-in) |
| RTL text direction | No | Partial | No | Yes |
| Built-in i18n locales | No | No | Partial | Yes (8 languages) |
| Custom locale object | No | No | No | Yes |
| Checklist (todo list) | No | No | No | Yes |
| Find and Replace | No | No | No | Yes (Ctrl+F / Ctrl+H) |
| Emoji picker | No | No | No | Yes (~380 emoji) |
| FA icon picker | No | No | No | Yes (FA 6, searchable) |
| Video embeds | No | No | No | Yes (YouTube, Vimeo, direct) |
| Image crop tool | No | No | No | Yes (inline) |
| Image / video resize | No | No | No | Yes (drag handles) |
| Inline tooltips | No | No | Partial | Yes (link, image, video, table, code) |
| Table cell merge/unmerge | No | No | Yes | Yes |
| Table cell selection mode | No | No | No | Yes |
| Context menu | No | No | No | Yes (with colour palette) |
| Sticky toolbar | No | No | No | Yes |
| Auto-save to localStorage | No | No | No | Yes |
| Character / word limits | No | No | No | Yes |
| Custom focus ring colour | No | No | No | Yes |
| Read-only mode | No | Partial | Yes | Yes |
| Custom colour swatches | No | No | No | Yes |
| Code view (HTML source) | No | Yes | Yes | Yes (sanitised) |
| Syntax highlighting | No | No | Partial | Yes (Prism.js via CDN) |

