Frameworks
Plain JavaScript
No binding, no build step, no framework. The core is the whole thing · the bindings that exist for React, Vue, Svelte and Solid only wrap what is on this page.
npm i @matrajs/core An editor
import { createEditor, starterKit } from '@matrajs/core'
const editor = createEditor({
extensions: starterKit,
content: '<p>Hello.</p>',
element: document.querySelector('#editor'),
}) element mounts it immediately. Leave it out and you get an unmounted editor to
editor.mount(el) yourself later, which is what the bindings do when the element does
not exist until after a render.
Without a bundler
A <script type="module"> and a CDN is enough. Every editor on this site is
exactly that.
<div id="editor"></div>
<script type="module">
import { createEditor, starterKit } from 'https://esm.sh/@matrajs/core'
createEditor({
extensions: starterKit,
content: '<p>Hello.</p>',
element: document.querySelector('#editor'),
})
</script> A toolbar
The two things worth knowing without a binding: editor.on() returns its own unsubscribe,
and editor.isActive() reads the answer out of the document rather than out of a variable
you have to keep in step.
const bold = document.querySelector('#bold')
bold.addEventListener('click', () => {
editor.commands.toggleBold()
editor.focus()
})
const off = editor.on('selectionChange', () => {
bold.setAttribute('aria-pressed', String(editor.isActive('bold')))
}) editor.focus() after a command, always. A click on a button moves focus to the
button, and a command that runs with the caret gone looks like a command that did nothing.
Reading it back
editor.getJSON() // the document · store this
editor.getHTML() // markup
toMarkdown(editor.getJSON()) Store the JSON, not the HTML. HTML has to be parsed back through the schema on the way in, and anything the schema does not recognise is dropped · JSON round-trips exactly.
Tearing it down
off() // any listener you registered
editor.destroy() // the editor, its view and its plugins destroy() leaves the element where it was and safe to mount into again. Not calling
it on a page that swaps editors in and out is the one leak worth watching for.
On a server
None of this needs a browser. createEditor builds a document with no view, and
renderToHTML skips the editor entirely ·
both run in Node, in a worker and at the edge.
import { renderToHTML, starterKit } from '@matrajs/core'
// no editor, no DOM, no schema build
const html = renderToHTML(storedJson, starterKit)
One condition on a server: load JSON or Markdown, not an HTML string.
Reading HTML is a DOM job, so content: '<p>…</p>' needs a browser while
content: storedJson and content: fromMarkdown(text) do not. Writing
HTML out works either way.
TypeScript
Commands are inferred from the extensions you pass, so this is a compile error rather than a silent no-op at run time — with no generics to thread and nothing to declare.
const editor = createEditor({ extensions: [document, paragraph, text] })
editor.commands.toggleHeading(2)
// ^ no extension defines this