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:

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.

Edit this page on GitHub