A standalone rich text editor built on Tiptap v2.
Ships as a single IIFE bundle — no framework required.
Load via <script src="editor.js"></script> and call TiptapEditor.createEditor().
Load the script, drop a container element in your HTML, then call TiptapEditor.createEditor().
<script src="https://artworx-tiptap-editor.pages.dev/editor.js"></script> <div id="editor"></div>
const editor = TiptapEditor.createEditor( document.getElementById('editor'), { content: '<p>Hello world!</p>', placeholder: 'Start writing…', onChange: (html) => console.log(html), } ) // Instance methods editor.getHTML() // → HTML string editor.setContent(html) // replace content editor.setEditable(false) // toggle read-only editor.destroy() // cleanup
Live result:
Default setup — all buttons enabled. Fires onChange on every keystroke.
HTML output
Using toolbar: ['bold', 'italic', 'underline', 'bulletList', 'orderedList', 'link'] — only the specified buttons, in that order.
Using simpleToolbar: true — hides headings, blockquote, code block, and horizontal rule. Good for comments or short-form content.
Using editable: false — toolbar is hidden, content is not editable.
Using wrapperClass: 'dark-theme' and CSS variable overrides to restyle the editor per-instance.
Override just a few --te-* CSS variables via wrapperClass to rebrand the editor. Only 6 variables changed here:
.purple-theme.tiptap-editor-wrapper { --te-accent: #605CA8; --te-accent-hover: #4e4a8f; --te-ring: #9b97d3; --te-ring-shadow: rgba(96, 92, 168, 0.15); --te-btn-active-bg: #ededf7; --te-btn-active-hover-bg: #dddcf0; }
Live result:
Pass { custom: true, name, icon, title, command } objects inside the toolbar array to inject your own buttons anywhere.
Buttons with no command render as non-interactive <span> display widgets.
When isActive returns a string, the element's content is updated live on every transaction.
const imgIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>' TiptapEditor.createEditor(el, { toolbar: [ 'bold', 'italic', 'link', { custom: true, name: 'insert-image', icon: imgIcon, title: 'Insert Image', group: 'extra', command: (editor) => { const url = window.prompt('Image URL') if (url) editor.chain().focus().insertContent( `<img src="${url}" alt="" style="max-width:100%">` ).run() }, }, { custom: true, name: 'word-count', icon: 'Words: 0', title: '', group: 'extra', className: 'tiptap-wordcount', // no command → renders as <span>, not <button> isActive: (editor) => { const text = editor.getText().trim() const count = text === '' ? 0 : text.split(/\s+/).length return `Words: ${count}` // string → updates innerHTML }, }, ], })
Live result:
Using fixedToolbar: true — the toolbar sticks to the top of the viewport as you scroll down the page.