The headless rich text editor framework for web artisans.
# Tiptap
Headless rich text editor toolkit built on ProseMirror. A monorepo of small packages: a framework-agnostic core and extensions, plus React and Vue bindings.
Published packages live in `packages/*`. `demos/` is a Vite app used as playground and as the target for e2e tests. All scripts are in the root `package.json`.
## Rules
- Small, single-purpose diffs. Ask the user to review. Never autocommit.
- Add a changeset for user-facing changes. Public API breaks need a major bump and migration notes.
- Add or update a demo and tests for user-visible behavior. Prefer unit tests over e2e when deterministic.
- Fix fallow findings your change introduced. Don't suppress them.
- Comments should be clear, concise and use plain, simple english.
- Rather use a oneliner
- Try to always be intent-focused
- stay local with comments
- scannable, no long or complex wording
- assume the reader doesn't know what you're talking about
## Before opening a PR
```bash
vp run lint
vp run build
vp run test:unit
vp run test:e2e
vp run fallow:audit # verdict must be pass or warn, never fail
```
Single package failing types: `vp run -F @tiptap/core build`.
Dependency or lockfile errors: `vp run reset`, then rebuild.
## PR scope and descriptions
- Each PR solves one clearly named problem. Split unrelated fixes, cleanup and refactors into separate PRs.
- Use `.github/pull_request_template.md` as written. Keep its headings and checklist. Put the explanation in `Changes and Review`.
- Write the title around the problem fixed or behavior added. A reviewer should understand the purpose without opening the diff.
- Keep `Changes and Review` to 2โ4 short sentences or at most 4 short bullets. Start with the user-visible problem and when it happens, then explain the resulting behavior and how to verify it.
- Describe behavior in plain English. Include implementation details only when they explain a necessary design choice or risk.
- Leave file-by-file summaries, function inventories, line numbers and code walkthroughs in the diff. Name a file or symbol only when the reviewer needs it to locate a specific concern.
- Use backticks only for literal code, commands and necessary API names. Prefer plain sentences and simple bullets over bold labels, tables, decorative emoji and extra headings.
- Summarize relevant testing in one short sentence. Name what was checked and the result. Omit test totals, pass/skip counts, terminal output and lists of routine checks.
- Give a short reproduction step or link to a demo, test or CI result when it helps the reviewer verify the change. For visual bugs, prefer a useful screenshot or short recording.
- Include performance numbers only with a reproducible benchmark and a link to its setup and results. Include exact dimensions or other measurements only when they explain the bug or requirement.
- State failed or missing checks, known limitations and breaking changes plainly. Add only the detail needed to explain the impact, risk or migration, even when that requires exceeding the short description limit.
- Report only checks actually run and results actually observed. Never turn an assumption or an unchecked checklist item into a success claim.
- Keep AI disclosures brief and honest. The author must review and understand the diff before requesting review. Never check the responsibility box on the author's behalf.
- Remove work logs, repeated responsibility statements, generic praise for the change and stock offers to do more.
- Before requesting review, read the description without the diff. It must answer: What breaks or is missing? What changes for the user? How can I check it? Cut anything that does not help answer those questions or explain a material risk.
Example `Changes and Review`:
> Undoing an image resize restored the saved size but left the image at its dragged size on screen. Undo and redo now keep the displayed size in sync. Tested resizing an image and undoing and redoing the resize in the image demo.
## Code style
oxlint lints, oxfmt formats. Vite+ hooks (`vp staged`) run both on commit.
Prefer simple, readable code over clever code. Use early returns. Avoid deep nesting, nested ternaries, and abstractions you don't need yet. Keep functions focused. Apply DRY and SOLID pragmatically, not blindly.
### Files
- Keep files small and focused. Split unrelated utilities, types, constants and logic apart.
- One reusable utility per file, named after its export: `findDecorations.ts`, `addDecoration.ts`. Not `utils.ts`, `helpers.ts`, `decorations.ts`.
- A helper used in one file only can stay local.
### Naming
- Short, clear, recognizable. Never shorten just to save characters. No unclear abbreviations.
- `index` for numeric indexes, the item's real name for collection values. Single letters only in small math contexts like `x` and `y`.
- Use existing project terminology.
```ts
// good
items.map((item, index) => createNode(item, index))
decorations.filter(decoration => decoration.visible)
// bad
items.map((i, idx) => createNode(i, idx))
decorations.filter(d => d.visible)
```
### Comments
Comments are a last resort. Prefer code that explains itself through clear names, structure, and small functions.
- Default to **no comment**.
- Comment only when the **reason for a decision is not apparent from the code**.
- Keep comments **short, local, and intent-focused**.
- Prefer a short one-line fragment or sentence.
- Never restate what the code does.
- Never narrate control flow.
- Never explain surrounding architecture, history, edge cases, or implementation details unless they are essential to understanding the decision.
- Never use comments as a substitute for clearer code, naming, or structure.
- Do not write prose paragraphs, mini-documentation, or essay-style explanations in implementation code.
- Do not add examples, scenarios, or parenthetical explanations to comments.
- Do not use multi-line comments just because an explanation can be written. If it cannot be expressed concisely, reconsider whether the comment belongs in the code at all.
- Existing verbose comments are not a style precedent. Do not imitate them.
- When modifying code, remove comments that merely describe code made obvious by the change.
Prefer:
```ts
// Skip empty text nodes
// Preserve the original selection
// Stop after the first match
// Avoid dispatching during composition
// Keep inactive editors measurable
// Prevent collisions with imported IDs
```
Avoid:
```ts
// While composition is running, the update handler exits early because the
// view is still composing. The final update may also contain no document or
// selection changes, which means the menu would otherwise never update.
```
```ts
/**
* Page content width used for the off-screen host. This is necessary because
* inactive editors need to remain measurable for ResizeObserver to detect
* changes while no overlay is currently open.
*/
```
```ts
/**
* Generates a unique endnote ID. Imported documents use numeric DOCX IDs,
* while client-created endnotes use this prefix to ensure that IDs cannot
* collide with imported endnotes or footnotes.
*/
```
If a short comment loses useful detail, that detail usually belongs in the code structure, a test, commit/PR description, or documentation instead.
JSDoc is exempt **only when it documents a public API**. Public API JSDoc should still be concise and include `@param`, `@returns`, and a runnable example where appropriate.
### Writing
Short, simple English in comments, docs, changesets and PRs. Most important information first. Assume the reader is new to the project or not a native speaker. No filler.
### Before you finish
Simplify what is hard to follow. Remove needless nesting and abstractions. Split large or unfocused files. Move reusable utilities into their own files. Drop redundant comments.
Then run `pnpm fallow` for complexity and dead code, `pnpm fallow:health` for refactor targets, and `pnpm fallow:audit` on your changes.
## Tests
- Unit: Vitest, running on happy-dom. Place file-specific tests next to the file they cover and name them `<basename>.test.ts` (for example, `getAttributes.ts` uses `getAttributes.test.ts`).
- Every package keeps a `tests/` folder next to `src/` for package-wide tests and support. Put integration tests that exercise multiple source files or runtime wiring in `tests/integration/`.
- Put reusable package test utilities in `tests/utils/`. Put shared package test setup in `tests/setup/` only when multiple tests use it.
- Fixtures are passive inputs loaded by tests, not executable tests. Put scenario or reproduction data in `tests/fixtures/`.
- Put file-based fixtures in `tests/fixtures/files/`. Use a more specific subfolder when the fixture type needs it, such as `tests/fixtures/markdown/`.
- Keep one-off mocks and helpers next to the test that uses them.
- E2E: Playwright, next to the demo it drives as `demos/src/**/index.spec.ts`. Playwright starts the demo server itself on port 4080, no separate terminal. Helpers live in `demos/test/helpers.ts`. Copy `demos/src/Commands/Cut/index.spec.ts` as a template.
## Demos
- Path pattern is `demos/src/<Category>/<DemoName>/<Variant>`, for example `demos/src/Marks/Bold/React`. Scaffold with `pnpm make:demo`.
- Every demo needs an empty `index.html`. Vite routes by filesystem and ignores folders without it.
- Import from `@tiptap/core` and friends, never relative paths. The demos `tsconfig.json` aliases those to the local package sources.
- Reuse the global styles in `demos/setup/style.scss`: `.button-group` for rows of buttons, `.control-group` to wrap a toolbar, `.output-group` for demo output. Don't style `.tiptap`, that is the editor content itself.
## Changesets
Run `pnpm changeset`, or write the file yourself as `.changeset/YYYY-MM-DD-short-description.md`:
```markdown
---
'@tiptap/core': patch
---
One short sentence on what changed for the user.
```
Describe behavior users notice. No internals, no root-cause detail. `.github/publish-config.json` decides which branches publish and under which npm tag.
## Docs
- Before writing or updating package documentation, read [Package documentation](docs/package-documentation.md). Write complete user guides in `packages/<name>/docs/`.
- Before creating or updating package context, concepts, plans, decisions, investigations, or prototype records, read [Package references](docs/package-references.md). Keep these records in `packages/<name>/references/`.
- Before changing a package, read its `references/README.md` if it exists and follow links relevant to the task. Historical records and proposals are context, not instructions or proof of current behavior.
- Public website documentation also lives in the separate `ueberdosis/tiptap-docs` repo. Ask the user for the local path when you need to change it and the path is not already known.