Portable Text Editor
Headless, schema-driven rich text editor for React, built on the Portable Text specification.
Repository Health
Technical Analysis
@portabletext/editor is the official editor for Portable Text, the JSON-based rich text specification originally built for Sanity’s structured content model. It is headless by design: the library owns the editing state machine, selection model, and content mutations, while every visual element (toolbars, block rendering, decorators) is supplied by the host application through a declared schema and a set of render props.
Under the hood the editor is built as an internal engine with a behavior system, letting consumers override or extend how the editor responds to keyboard input, pasting, drag-and-drop, and other events without forking the library. It ships alongside a family of companion packages in the same monorepo (schema, toolbar, plugins, HTML/Markdown converters) that cover the full authoring surface for Portable Text content.
What You Get
EditorProviderandPortableTextEditablecomponents for wiring a schema-driven editor into any React tree- A behavior engine (
defineBehavior) for intercepting and customizing keyboard, paste, and mutation events without forking core logic - Plugin hooks (
EventListenerPlugin,NodePlugin, and others) for reacting to editor mutations and node lifecycle events - Selector utilities (
useEditorSelector) for reading editor state efficiently in React components - Companion packages in the same monorepo for HTML/Markdown conversion, keyboard shortcuts, and toolbar hooks
Common Use Cases
- Building a custom rich text editor for a headless CMS authoring experience
- Embedding structured block content editing (text, images, custom objects) inside an existing product UI
- Implementing collaborative or real-time editing on top of Portable Text as the underlying document format
- Converting between Portable Text and HTML/Markdown for import/export flows
Under The Hood
Architecture
The editor is assembled incrementally rather than defined as one monolithic class: createEditor() in src/engine/create-editor.ts builds a base object wiring core operations (apply, normalizeNode, select, setSelection), and plugin layers (like withDOM) attach DOM- and React-specific behavior afterward, with self-referencing delegates cast through any until the object satisfies the full Editor interface. Editing rules live in a dedicated behavior system under src/behaviors/, where abstract behavior types (behavior.abstract.keyboard.ts, behavior.abstract.insert.ts, behavior.abstract.delete.ts, and others) are composed into concrete behavior.core.*.ts modules, letting consumers intercept or override editing events declaratively instead of monkey-patching engine internals. Editor lifecycle and mutation flow are modeled as an XState machine (editor/editor-machine), while engine/path, engine/point, and engine/range modules provide the addressing primitives (paths, points, ranges, and their live refs) that the behavior and normalization layers operate on. A separate renderers/ layer translates the internal document model into the React tree the host application supplies via schema-defined render props.
Tech Stack
The project is a TypeScript pnpm workspace built with Turborepo, with @portabletext/editor as one of roughly twenty packages (schema, toolbar, plugins, HTML/Markdown converters, testing utilities). The editing state machine runs on xstate and @xstate/react; scroll-into-view-if-needed handles caret-visibility scrolling and debug provides namespaced logging. Builds go through @sanity/pkg-utils (pkg-utils build --strict --check), tests run on Vitest including a browser project (@vitest/browser, vitest-browser-react) executed against Chromium/Firefox/WebKit via Playwright, and releases are managed with Changesets. React is a peer dependency (^19.2.8), reflecting the library’s design as a component/hook layer over the engine rather than a bundled framework.
Code Quality
The packages/editor/src tree contains on the order of 90 test files, and the CI pipeline (.github/workflows/) runs a dedicated test.yml and test-browser.yml alongside separate check-types.yml, check-lint.yml (oxlint), check-format.yml (oxfmt/Prettier), check-knip.yml (dead-code/unused-dependency detection), and check-commits.yml (conventional commit enforcement) jobs. Strict TypeScript is used throughout with explicit typed interfaces for the editor engine, and Husky pre-commit hooks plus Renovate keep tooling and dependencies current. The combination of a typed engine, browser-level test coverage, and multiple independent static-analysis gates points to a rigorously maintained codebase rather than a lightly-tested library.
What Makes It Unique Most React rich text editors (Slate, Lexical, ProseMirror wrappers) expose an imperative plugin API layered over a proprietary document model. This library instead formalizes editing rules as declarative, composable “Behaviors” with dedicated abstract types per interaction category (keyboard, paste, delete, split, style), and drives the whole editor lifecycle through an inspectable XState machine rather than ad hoc imperative state updates. It is also built natively around Portable Text, an openly specified JSON rich text format with its own ecosystem of renderers and converters, rather than inventing another editor-specific document schema — giving content produced by this editor portability outside of Sanity’s own tooling.
Used by 11 apps in this directory
Sanity
CMS
Open-source headless CMS with a fully customizable React Studio, real-time collaborative editing, structured content modeling, and GROQ query language
Sanity
CMS
Open-source headless CMS with a fully customizable React Studio, real-time collaborative editing, structured content modeling, and GROQ query language
Sanity
CMS
Open-source headless CMS with a fully customizable React Studio, real-time collaborative editing, structured content modeling, and GROQ query language
Sanity
CMS
Open-source headless CMS with a fully customizable React Studio, real-time collaborative editing, structured content modeling, and GROQ query language
Sanity
CMS
Open-source headless CMS with a fully customizable React Studio, real-time collaborative editing, structured content modeling, and GROQ query language
Sanity
CMS
Open-source headless CMS with a fully customizable React Studio, real-time collaborative editing, structured content modeling, and GROQ query language
Sanity
CMS
Open-source headless CMS with a fully customizable React Studio, real-time collaborative editing, structured content modeling, and GROQ query language
Sanity
CMS
Open-source headless CMS with a fully customizable React Studio, real-time collaborative editing, structured content modeling, and GROQ query language
Sanity
CMS
Open-source headless CMS with a fully customizable React Studio, real-time collaborative editing, structured content modeling, and GROQ query language