Getting started

Styling

Matra ships no appearance at all. Mount an editor and you get a contenteditable element holding plain, unstyled HTML — a heading looks like a heading only because your stylesheet says so. That is what headless means, and it is the reason the same package makes a comment box and a document editor.

There are three separate things to know, and they are easy to conflate.

1 · The document is yours to style

The editor renders ordinary tags — <p>, <h2>, <ul>, <blockquote>, <table>. Style them the way you style any prose on your site. Scope it to the editor and you are done:

.matra-editor { outline: none; white-space: pre-wrap; }
.matra-editor h2 { font-size: 1.5rem; margin: 1.5rem 0 .5rem; }
.matra-editor p { margin: 0 0 .75rem; line-height: 1.6; }
.matra-editor blockquote { border-left: 2px solid #ccc; padding-left: 1rem; }
.matra-editor pre { background: #f4f4f4; padding: 1rem; white-space: pre-wrap; }

white-space: pre-wrap is not optional. HTML collapses runs of whitespace and drops a trailing one, so without it pressing space at the end of a line does nothing visible until the next character arrives — which reads as a dropped keystroke.

It is set twice on purpose. A pre carries the browser's own white-space: pre, which beats the value inherited from the editor around it, so a code block that does not restate it grows a horizontal scrollbar inside a document that is already scrolling. overflow-x: auto is the other way to answer that, and it is the worse one.

2 · Some extensions bring a stylesheet

An extension that renders something the document does not contain — a checkbox, a drag handle, a remote caret — cannot leave its appearance entirely to you, because there is no tag for you to target. Each of those exports the CSS it needs as a string. Paste it, inject it, or copy the rules out and rewrite them; nothing is minified and nothing is obfuscated.

ExportFromWhat it draws
placeholderCSS @matrajs/core The hint in an empty block
taskListCSS @matrajs/core Checkboxes, and the line through a done item
suggestionCSS @matrajs/core The highlight on a live query, and mention chips
dragHandleCSS @matrajs/core The handle that appears beside a block
commentCSS @matrajs/core The tint under a commented range
remoteCursorCSS @matrajs/collab Other people’s carets and their labels
versionDiffCSS @matrajs/versions What a preview marks as added or changed
import { placeholderCSS, taskListCSS, suggestionCSS } from '@matrajs/core'

const sheet = new CSSStyleSheet()
sheet.replaceSync(placeholderCSS + taskListCSS + suggestionCSS)
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet]

Or, if you would rather own them, open the file and copy the rules into your own stylesheet. They are a starting point, not a dependency — every one of them is a handful of lines and none of them is imported by the extension itself.

3 · A slash menu is state, not a menu

This is the one that catches people. suggestion() does not draw a menu. It watches for the trigger character, tracks the query as you type, exposes the range it covers, and gives you acceptSuggestion and cancelSuggestion. What it hands you is a piece of state:

const state = editor.extensionState('slash')
// { active: true, query: 'head', range: { from: 12, to: 17 } }

The list, its position, its keyboard behaviour and its appearance are yours. That is deliberate: a menu is the most opinionated thing in an editor and the thing every product wants to look like its own. The menu on this site is about three hundred lines of ordinary DOM code with no framework, and it is in the repository — copy it and change it rather than starting from nothing.

The same is true of a toolbar, a bubble menu and a comment sidebar. The editor gives you the state and the commands; the interface is the part you were going to design anyway.

What this costs you

An afternoon at the start, and no fighting later. The alternative — an editor that arrives with a theme — is a week saved on day one and then a month spent overriding selectors when the design changes. If you want a running start, every editor on the home page and the extensions page is a real one, and the CSS behind them is in the same repository.

Edit this page on GitHub