Skip to main content
ClaudeWave

A headless rich text editor framework with a first-class extension API. Zero runtime dependencies.

TemplatesOfficial Registry0 stars0 forksTypeScriptMITUpdated today
ClaudeWave Trust Score
95/100
Verified
Passed
  • Open-source license (MIT)
  • Actively maintained (<30d)
  • Clear description
  • Topics declared
  • Documented (README)
Last scanned: 9/9/2026
Use as a project template
Method: Clone
Terminal
git clone https://github.com/amrelaco/matra my-project && cd my-project
1. Clone the template into a new project directory.
2. Follow the README setup (install dependencies, set environment variables).
3. Open it with Claude Code and start building.
Use cases

Templates overview

# Matra

A headless rich text editor framework with a first-class extension API.

- **No engine leakage** — the document model is plain JSON; no ProseMirror type appears in a public signature
- **Plain objects, plain functions** — no `this`, no classes, no inheritance chains
- **Inferred types** — adding an extension adds its commands, fully typed, with no module augmentation
- **Async-safe** — position mapping is built in, so a late AI response cannot corrupt the document

See [DESIGN.md](./DESIGN.md) for the API rationale, [CHANGELOG.md](./CHANGELOG.md)
for what changed when, and [CONTRIBUTING.md](./CONTRIBUTING.md) before a pull
request.

## Packages

| Package | Purpose | Licence |
|---|---|---|
| `@matrajs/core` | Engine, document model, extension API, starter kit | MIT |
| `@matrajs/react` | `useEditor`, `useEditorState`, `useEditorFocus`, `EditorContent` | MIT |
| `@matrajs/vue` | `useEditor`, `useEditorState`, `useEditorFocus`, `EditorContent` | MIT |
| `@matrajs/svelte` | `matra` — a `use:` action, the editor, and a state store | MIT |
| `@matrajs/solid` | `createMatra` — the editor, a `mount` ref, and a state signal | MIT |
| `@matrajs/ai` | Streaming edits that survive concurrent typing | Commercial |
| `@matrajs/collab` | Authority, step rebasing, remote cursors | Commercial |
| `@matrajs/versions` | Snapshots, a real diff between them, restore as one undo step | Commercial |

Matra is মাত্রা — the horizontal line that runs across the top of Bengali
script and holds a word together. Packages live under the `@matrajs` scope,
matching matrajs.com.

**Installing a binding installs the engine with it.** For a React application
`pnpm add @matrajs/react` is the entire install: one package, and no
third-party dependency arrives behind it.

## Quick start

```ts
import { createEditor, starterKit } from '@matrajs/core'

const editor = createEditor({
  extensions: starterKit,
  content: '<p>Hello</p>',
})

editor.mount(document.querySelector('#editor')!)
editor.commands.toggleBold()
```

Every command comes from the array you passed. Nothing else is on `editor.commands`,
and calling something that is not there is a compile error.

## The packages in detail

Eight packages, one version number, released together. Every other package
depends on `@matrajs/core` and on nothing else, so installing a binding
installs the whole editor — there is no second package to remember, no
`@matrajs/pm` to keep in step, and no peer range to resolve by hand.

---

### `@matrajs/core` — MIT

The engine, and the only package that is not optional. The document model,
transforms, position mapping, editor state and the editable view are written
here, with **zero runtime dependencies**.

```sh
pnpm add @matrajs/core
```

**Entry points**

| Export | What it is |
|---|---|
| `createEditor(options)` | Builds an editor. The `extensions` array decides everything else about it. |
| `buildSchema(extensions)` | The schema alone, for validating a document with no view and no DOM. |
| `pos(…)`, `range(…)` | Constructors for the two position types. |
| `starterKit` | Seventeen extensions in one array — document, paragraph, text, heading, blockquote, code block, bullet/ordered/list item, horizontal rule, hard break, bold, italic, strike, code, link, history. |
| 79 named extensions | Every entry in [Extensions](#extensions), each importable on its own. |
| Helpers | `tableOfContents(doc)`, `assignIds(doc)`, `commentRanges(doc)`, `activeSuggestion(editor)`, `searchEmoji(query)`, `youtubeId(url)`, `normalizeUrl(text)`, `fieldsIn(doc)`, `fillFieldsIn(doc, values)`, `hashtagsIn(doc)`, `parseDelimited(text)`, `dictationSupported()` — plain functions, not extensions. |
| `toMarkdown`, `fromMarkdown` | Pure string work, so they run in Node, in a worker and at the edge. |
| `…CSS` helpers | `placeholderCSS`, `commentCSS`, `taskListCSS`, `dragHandleCSS`, `suggestionCSS`, `searchCSS`, `lockedCSS`, `fieldsCSS`, `columnsCSS`, `footnotesCSS` and the rest — stylesheets to paste into an app rather than a stylesheet to import. |

**`EditorOptions`**

| Field | Type | Notes |
|---|---|---|
| `extensions` | `readonly AnyDef[]` | Declare it `as const`. The tuple is what makes the commands infer. |
| `content` | `DocNode \| string` | Document JSON, or HTML to parse. |
| `editable` | `boolean` | |
| `autofocus` | `boolean \| 'start' \| 'end'` | |
| `element` | `HTMLElement` | Mount as soon as the editor exists, instead of calling `mount` yourself. |

**The editor**

| Member | Signature | |
|---|---|---|
| `commands` | `CommandsOf<T> & CoreCommands` | Only what the extensions you passed provide. Anything else is a compile error. |
| `can` | same shape | Asks instead of does, so a button can be disabled rather than dead. |
| `batch(run)` | `=> boolean` | Several commands, one undo step. Rolls back entirely if any returns `false`. |
| `isActive(name, attrs?)` | `=> boolean` | Marks first, then nodes · `isActive('heading', { level: 2 })` reads naturally. |
| `getJSON()` | `=> DocNode` | |
| `getHTML()` | `=> string` | Answers without a DOM. |
| `getText()` | `=> string` | |
| `setContent(content)` | `=> void` | |
| `selection` | `Selection` | |
| `editable` / `setEditable(v)` | | |
| `on(event, fn)` | `=> () => void` | `change`, `focus`, `blur`, `selectionChange`. Returns its own unsubscribe. |
| `extensionState<S>(name)` | `=> S \| undefined` | How a toolbar reads a character count or a collab version without a global. |
| `mount(el)` / `destroy()` | | |
| `unsafe` | `{ view, state, schema }` | Excluded from semver. Needing it means the public API has a gap — open an issue. |

**Core commands**, present whatever you pass: `select`, `insert`, `replace`,
`remove`, `moveBlock`, `focus`. `insert` and `replace` accept blocks at a
caret inside a paragraph and split the paragraph around them, which is what
a rule or a table asked for at the caret means.

**What an extension may declare**, beyond commands, keys and input rules:

| Field | On | What it does |
|---|---|---|
| `attributes` | extension | Add attributes to nodes and marks defined elsewhere · `[{ types: ['paragraph', 'heading'], attrs: { indent: { default: 0, render, parse } } }]`. How `textAlign`, `indent` and `uniqueId` work without the paragraph knowing about them. |
| `handlePaste(ctx, { html, text, files })` | extension | Claim a paste before the editor parses it. Return `true` to keep it. |
| `handleDrop(ctx, { html, text, files, pos })` | extension | The same for something dropped from outside. Block drags inside the editor never reach it. |
| `filterChange(ctx)` | extension | Veto a change before it lands. Return `false` and the document, the selection and the undo history stay as they were · how `locked()` refuses a keystroke, a paste and a drag alike. `editor.can` asks it too. |
| `nodeViews` | extension | Render nodes defined elsewhere with your own DOM · `{ image: ({ node, getPos, editor }) => … }`. How `imageResize()` puts a handle on the stock image. |
| `decorations(ctx)` | extension | Draw over the document · highlights, widgets, a class on the current block. |
| `state` | extension | Reduced on every transaction · read with `editor.extensionState(name)`. |
| `code` | node | Whitespace inside is literal, so a pasted function keeps its line breaks. |
| `listItem` | node | Enter splits, Tab nests, Backspace at the start lifts. |
| `marks` | node | Which marks the text may carry · `''` for none. |
| `nodeView` | node | Render with your own DOM and keep it across edits. |

---

### `@matrajs/react` — MIT

```sh
pnpm add @matrajs/react
```

| Export | Signature |
|---|---|
| `useEditor(options)` | `Editor<T>` — created lazily on first render, destroyed on unmount. |
| `useEditorState(editor, select)` | `S` — a `useSyncExternalStore` subscription to `change` and `selectionChange`. |
| `useEditorFocus(editor)` | `boolean` |
| `EditorContent` | `{ editor }` plus every `div` attribute. |

```tsx
import { starterKit } from '@matrajs/core'
import { EditorContent, useEditor, useEditorState } from '@matrajs/react'

export function Notes() {
  const editor = useEditor({ extensions: starterKit, content: '<p>Hello</p>' })
  const bold = useEditorState(editor, (e) => e.isActive('bold'))

  return (
    <>
      <button onClick={() => editor.commands.toggleBold()} aria-pressed={bold}>
        Bold
      </button>
      <EditorContent editor={editor} className="prose" />
    </>
  )
}
```

Options are read once. Changing them later does not recreate the editor,
because tearing down a live document on a prop change loses the user's work —
use the commands instead. The mount is guarded on `unsafe.view`, so StrictMode's
double invoke cannot leave two views fighting over one element.

---

### `@matrajs/vue` — MIT

The same four names as React, returning refs.

```sh
pnpm add @matrajs/vue
```

| Export | Signature |
|---|---|
| `useEditor(options)` | `Editor<T>`, `markRaw`ped · works in a component or a bare effect scope. |
| `useEditorState(editor, select)` | `Readonly<Ref<S>>` |
| `useEditorFocus(editor)` | `Readonly<Ref<boolean>>` |
| `EditorContent` | Component with an `editor` prop. |

```vue
<script setup lang="ts">
import { starterKit } from '@matrajs/core'
import { EditorContent, useEditor, useEditorState } from '@matrajs/vue'

const editor = useEditor({ extensions: starterKit })
const bold = useEditorState(editor, (e) => e.isActive('bold'))
</script>

<template>
  <button :aria-pressed="bold" @click="editor.commands.toggleBold()">Bold</button>
  <EditorContent :editor="editor" />
</template>
```

The mount is guarded, so a `<KeepAlive>` remount does not attach a second view.

---

### `@matrajs/svelte` — MIT

Svelte already has the right shape — an action runs when the element exists and
is told when it goes away — so the binding is thin on purpose. Written with
stores rather than runes, so it behaves identically on Svelte 4 and 5.

```sh
pnpm add @matrajs/svelte
```

| Export | Signature |
|---|---|
| `matra(o
contenteditableeditorheadlessprosemirror-alternativerich-text-editortypescriptwysiwyg

What people ask about matra

What is amrelaco/matra?

+

amrelaco/matra is templates for the Claude AI ecosystem. A headless rich text editor framework with a first-class extension API. Zero runtime dependencies. It has 0 GitHub stars and its last recorded update is dated 2026-09-08.

How do I install matra?

+

You can install matra by cloning the repository (https://github.com/amrelaco/matra) or following the README instructions on GitHub. ClaudeWave also provides quick install blocks on this page.

Is amrelaco/matra safe to use?

+

Our security agent has analyzed amrelaco/matra and assigned a Trust Score of 95/100 (tier: Verified). See the full breakdown of passed checks and flags on this page.

Who maintains amrelaco/matra?

+

amrelaco/matra is maintained by amrelaco. The last recorded GitHub activity is dated 2026-09-08, with 0 open issues.

Are there alternatives to matra?

+

Yes. On ClaudeWave you can browse similar templates at /categories/templates, sorted by popularity or recent activity.

Deploy matra to your cloud

Ship this repo to production in minutes. Each platform spins up its own environment with editable env vars.

Maintain this repo? Add a badge to your README

Drop the badge into your GitHub README to show it's tracked on ClaudeWave. Each badge links back to this page and reflects the live Trust Score.

Featured on ClaudeWave: amrelaco/matra
[![Featured on ClaudeWave](https://claudewave.com/api/badge/amrelaco-matra)](https://claudewave.com/repo/amrelaco-matra)
<a href="https://claudewave.com/repo/amrelaco-matra"><img src="https://claudewave.com/api/badge/amrelaco-matra" alt="Featured on ClaudeWave: amrelaco/matra" width="320" height="64" /></a>

More Templates

matra alternatives