A headless rich text editor framework with a first-class extension API. Zero runtime dependencies.
- ✓Open-source license (MIT)
- ✓Actively maintained (<30d)
- ✓Clear description
- ✓Topics declared
- ✓Documented (README)
git clone https://github.com/amrelaco/matra my-project && cd my-projectResumen de Templates
# 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(oLo que la gente pregunta sobre matra
¿Qué es amrelaco/matra?
+
amrelaco/matra es templates para el ecosistema de Claude AI. A headless rich text editor framework with a first-class extension API. Zero runtime dependencies. Tiene 0 estrellas en GitHub y su última actualización registrada es del 2026-09-08.
¿Cómo se instala matra?
+
Puedes instalar matra clonando el repositorio (https://github.com/amrelaco/matra) o siguiendo las instrucciones del README en GitHub. ClaudeWave también te ofrece bloques de instalación rápida en esta misma página.
¿Es seguro usar amrelaco/matra?
+
Nuestro agente de seguridad ha analizado amrelaco/matra y le ha asignado un Trust Score de 95/100 (tier: Verified). Revisa el desglose completo de comprobaciones superadas y flags en esta página.
¿Quién mantiene amrelaco/matra?
+
amrelaco/matra es mantenido por amrelaco. La última actividad registrada en GitHub es del 2026-09-08, con 0 issues abiertos.
¿Hay alternativas a matra?
+
Sí. En ClaudeWave puedes explorar templates similares en /categories/templates, ordenados por popularidad o actividad reciente.
Despliega matra en tu cloud
Lleva este repo a producción en minutos. Cada plataforma genera su propio entorno con variables de entorno editables.
¿Mantienes este repo? Añade un badge a tu README
Pega el badge en tu README de GitHub para mostrar que está auditado por ClaudeWave. Cada badge enlaza de vuelta a esta página y muestra el Trust Score actual.
[](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>Más Templates
CLI tool for configuring and monitoring Claude Code
AWS AI Stack – A ready-to-use, full-stack boilerplate project for building serverless AI applications on AWS
Scaffold production-ready full-stack apps in TypeScript, Rust, Python, Go, and Java with a visual builder and CLI. Choose your frontend, backend, database, auth, AI, payments, and DevOps integrations, all wired together.
From Claude Artifact to deployable React app — in seconds!
CLAUDE.md best practices
No description provided.