Getting started
Framework Integration
React, Next.js App Router, Vue, and script-tag setup with client-only mounting and cleanup.
Framework integration
Lextrix is a DOM-based editor class, not a React/Vue component. Mount it on an element with new Lextrix(container, options) inside a client-only lifecycle hook.
npm package layout
| Import | When to use |
|---|---|
import Lextrix from 'lextrix' | Default — bundlers (Vite, Next, webpack) resolve the ESM build |
import { ChangeSet, registerSerializer, lxrPath } from 'lextrix' | Named APIs (ESM build, 2.0.1+) |
import 'lextrix/snow.css' | Theme stylesheet (required) |
Script tag lextrix.js | UMD global window.Lextrix — no named imports |
Always import a theme CSS file. The editor does not inject styles.
npm vs monorepo
| Need | npm (lextrix) | Monorepo (clone repo) |
|---|---|---|
| Editor + themes + serialization | Yes | Yes |
ChangeSet, registerSerializer, lxrPath | import { … } from 'lextrix' | Same, or lextrix-change / lextrix-core |
defineInlineTagFormat, custom blots | Register your own blot class + Lextrix.register() | Import from lextrix-formats |
| Headless serialize only | Use editor APIs, or clone repo for lextrix-serialize | import from 'lextrix-serialize' |
Lextrix requires a browser (document). Do not import it in server components or SSR routes without a client-only wrapper.
Toolbar / theme bugs? Read DOM mounting first — call editor.destroy() on remount; the auto toolbar lives inside the mount element (2.0.1+).
React (Vite, CRA, etc.)
Use a wrapper ref you clear on teardown. Pass a fresh inner div to new Lextrix().
'use client'; // Next.js App Router only — omit in plain Vite/React SPA
import { useEffect, useRef } from 'react';
import Lextrix from 'lextrix';
import 'lextrix/snow.css';
export default function LextrixEditor() {
const wrapperRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const wrapper = wrapperRef.current;
if (!wrapper) return;
const mount = document.createElement('div');
wrapper.appendChild(mount);
const editor = new Lextrix(mount, {
theme: 'snow',
placeholder: 'Start writing…',
modules: {
toolbar: [
['bold', 'italic', 'underline'],
[{ header: [1, 2, false] }],
[{ list: 'ordered' }, { list: 'bullet' }],
['link', 'clean'],
],
},
});
editor.setContents([{ insert: 'Hello Lextrix\n' }]);
return () => {
editor.destroy();
wrapper.replaceChildren();
};
}, []);
return <div ref={wrapperRef} className="lxr-mount" />;
}Do not render <Lextrix /> — Lextrix is a class, not a React component.
Theme switch (same wrapper pattern)
useEffect(() => {
const wrapper = wrapperRef.current;
if (!wrapper) return;
const mount = document.createElement('div');
wrapper.appendChild(mount);
const editor = new Lextrix(mount, { theme, modules: { toolbar: [...] } });
if (initialContents) editor.setContents(initialContents);
return () => {
editor.destroy();
wrapper.replaceChildren();
};
}, [theme]);See Themes.
Controlled content / serialization
useEffect(() => {
const wrapper = wrapperRef.current;
if (!wrapper) return;
const mount = document.createElement('div');
wrapper.appendChild(mount);
const editor = new Lextrix(mount, { theme: 'snow' });
if (initialMarkdown) editor.importContent(initialMarkdown, 'markdown');
const onChange = () => onSave(editor.exportContent('markdown'));
editor.on('text-change', onChange);
return () => {
editor.off('text-change', onChange);
editor.destroy();
wrapper.replaceChildren();
};
}, []);Next.js (App Router)
- Put the editor in a
'use client'component (see above). - Load it with
dynamicandssr: falseso Lextrix never runs on the server.
// app/playground/page.tsx
import dynamic from 'next/dynamic';
const LextrixEditor = dynamic(() => import('@/components/lextrix-editor'), {
ssr: false,
loading: () => <p>Loading editor…</p>,
});
export default function PlaygroundPage() {
return (
<main>
<h1>Lextrix playground</h1>
<LextrixEditor />
</main>
);
}Common Next.js mistakes
| Mistake | Result |
|---|---|
import Lextrix from 'lextrix' in a Server Component | document is not defined |
import { Lextrix } from 'lextrix' | undefined — use default import |
<Lextrix /> or <LextrixEditor /> when import failed | “Element type is invalid… got: undefined” |
| Forgetting theme CSS | Unstyled / broken toolbar |
| Clearing only mount div on teardown | Duplicate toolbars — DOM mounting |
MDX docs / playgrounds
Register your wrapper in the MDX provider:
import LextrixEditor from '@/components/lextrix-editor';
const components = { LextrixEditor };
<MDXProvider components={components}>{children}</MDXProvider>Then use <LextrixEditor /> in MDX — not <Lextrix />.
Vue 3
<script setup>
import { onMounted, onBeforeUnmount, ref } from 'vue';
import Lextrix from 'lextrix';
import 'lextrix/snow.css';
const wrapper = ref(null);
let editor = null;
onMounted(() => {
if (!wrapper.value) return;
const mount = document.createElement('div');
wrapper.value.appendChild(mount);
editor = new Lextrix(mount, {
theme: 'snow',
modules: { toolbar: [['bold', 'italic'], ['clean']] },
});
});
onBeforeUnmount(() => {
editor?.destroy();
wrapper.value?.replaceChildren();
editor = null;
});
</script>
<template>
<div ref="wrapper" />
</template>Script tag (no bundler)
Use the published package from npm or a CDN. After npm install lextrix, files are at the package root (e.g. node_modules/lextrix/lextrix.snow.css).
From npm (local static server):
<link rel="stylesheet" href="node_modules/lextrix/lextrix.snow.css" />
<div id="editor"></div>
<script src="node_modules/lextrix/lextrix.js"></script>
<script>
const editor = new Lextrix('#editor', { theme: 'snow' });
</script>From jsDelivr CDN (replace 2.0.3 with the version you want):
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/lextrix@2.0.3/lextrix.snow.css" />
<div id="editor"></div>
<script src="https://cdn.jsdelivr.net/npm/lextrix@2.0.3/lextrix.js"></script>
<script>
const editor = new Lextrix('#editor', { theme: 'snow' });
</script>UMD exposes window.Lextrix only. Named exports (ChangeSet, registerSerializer, …) require a bundler and the ESM entry (import Lextrix from 'lextrix').
Migrating from Quill
| Quill | Lextrix |
|---|---|
new Quill(el, opts) | new Lextrix(el, opts) |
quill.getContents() (Delta) | editor.getContents() (ChangeSet) |
quill.root.innerHTML | editor.exportContent('html') |
import 'quill/dist/quill.snow.css' | import 'lextrix/snow.css' |
| Delta | ChangeSet (same JSON shape, different name) |
See change-set.md and serialization.md.