Frameworks
Svelte
npm i @matrajs/core @matrajs/svelte An editor
<script>
import { starterKit } from '@matrajs/core'
import { matra } from '@matrajs/svelte'
const { action, editor, state } = matra({
extensions: starterKit,
content: '<p>Hello.</p>',
})
</script>
<div use:action></div> matra() gives you three things: the action to put on the element, the
editor itself, and a state store that republishes whenever the document
or the selection changes.
The editor is created immediately, not on mount. editor.getJSON(),
editor.getText() and every command work before anything is on screen — which is
what a server render, a test, and a form that submits before the user scrolls to the editor
all need.
A toolbar that tells the truth
Subscribe to state and ask the editor. The pressed state comes from the document
rather than from what was last clicked, so it is right after an undo, after a paste, and after
somebody moves the caret with the arrow keys.
<script>
import { starterKit } from '@matrajs/core'
import { matra } from '@matrajs/svelte'
const { action, editor, state } = matra({ extensions: starterKit })
</script>
<div class="toolbar">
<button
onclick={() => editor.commands.toggleBold()}
aria-pressed={$state.isActive('bold')}
>Bold</button>
<button
onclick={() => editor.commands.toggleHeading(2)}
aria-pressed={$state.isActive('heading', { level: 2 })}
>H2</button>
<span>{$state.getText().length} characters</span>
</div>
<div use:action></div> Svelte 4 and Svelte 5
One package for both. The store is a plain svelte/store readable, which
$state reads identically under runes — a rune-only build would be a version boundary
in exchange for nothing.
What the action does
Two things you would otherwise write yourself, and one of them is easy to miss:
-
Mounts on attach and destroys on detach, so a route change or a
{#if}block leaves nothing behind. - Refuses to mount twice into one element. An action re-run by a hot reload, or a component rendered twice, would otherwise attach a second view to one element — which is two carets fighting over it, and it looks like a browser bug rather than yours.
Reaching the editor from elsewhere
editor is an ordinary object. Put it in a context, a store, or a prop — there is
nothing framework-shaped about it, and the paid extensions do not know Svelte exists.
import { setContext, getContext } from 'svelte'
// in the parent
setContext('editor', editor)
// in any child
const editor = getContext('editor') Styling
Matra ships no appearance. Styling covers what you style yourself, which extensions bring a stylesheet, and why a slash menu is state rather than a menu.