v3.30.3
- Unsetting one text style inside a blockquote no longer removes the other text styles in it.
- YouTube live URLs (
/live/<id>) now embed the video.
- Fix JSX runtime to properly render nested sibling elements by spreading children arrays into DOMOutputSpec
- Fix ReactNodeViewRenderer crash when contentComponent is not available
- Add the
AiInsertRevealextension (@tiptap/ai-toolkit/streaming-reveal) to fade in text as the AI streams it into a collaborative document.
v3.30.0
-
ceb0dac: New Decorations API
Finally the decorations API is here! Even though Decorations itself are nothing new in ProseMirror, the new API makes it much easier to use them in Tiptap without leaving your extensions.
Decorations change how the document looks without changing the document itself. Highlighting search results, marking spelling mistakes, showing collaborator cursors, putting a drag handle next to every block.
Until now you had to write a ProseMirror plugin by hand for this, keep the decoration set in plugin state, and map it forward on every transaction. Extensions can now declare decorations directly with a new
addDecorations()hook.addDecorations() { return { create: ({ state }) => // findMatches can be any function that returns an array of { from, to } ranges findMatches(state.doc).map(match => Decoration.Inline(match.from, match.to, { class: 'highlight' }), ), } }
There are three kinds.
Decoration.Inline()styles a range of text.Decoration.Node()puts attributes on a block's DOM element.Decoration.Widget()renders your own element at a single position.Every extension that declares decorations is collected into one plugin, so several extensions can decorate the same document without fighting over it.
Doing less work on every keystroke
By default decorations are rebuilt whenever the document changes. That is fine for small documents and wasteful for large ones, so there are two ways to narrow it down.
shouldUpdate()skips transactions you do not care about. If your decorations only depend on headings, ignore everything else.update: 'changedRanges'together withcreateInRange()only rescans the blocks that actually changed. On a long document this is the difference between scanning the whole thing on every keystroke and scanning one paragraph.For decorations driven by data outside the editor, like comments loaded from a server, use
update: 'manual'and refresh them yourself witheditor.commands.updateDecorations().React and Vue components as widgets
ReactWidgetRendererandVueWidgetRendererrender a real component into a widget decoration, inside your existing app context. Providers, context and stores work as usual.Widgets take a
key. Reuse the same key and the component instance stays mounted while the document changes around it, so local state such as an open menu, a counter or a half-typed input survives editing. Use a stable id from your own data, not a position or a list index, otherwise the component remounts and loses that state.Widgets also accept the ProseMirror options
side,relaxedSide,stopEventandignoreSelection.Documentation
- ceb0dac: Fix FloatingMenu not registering when the editor prop is provided synchronously, which prevented the menu from appearing
-
ceb0dac:
ListKeymapnow registers aTabshortcut that sinks a top-level textblock into the previous list's last item. Pressing Tab at the start of a paragraph right after a bullet/ordered/task list moves the paragraph inside the last list item. The handler does nothing when the cursor is already inside a list item (sinkListItemkeeps working), when there is no list before the paragraph, when the caret is mid-textblock, or when the selection is not a text selection (for example a gap cursor).@tiptap/corealso exposes a newgetPreviousBlockSibling($pos)helper that returns the block-level sibling before the cursor's textblock, or null at the first child of the block parent.
- ceb0dac: Parse block math that follows an ordered list item without a blank line, both after the list and indented inside the item, instead of pulling it into the item's text.
- ceb0dac: TaskItem: the checkbox label now also fills the wrapping label element, so accessibility audits no longer flag it as empty.
-
ceb0dac:
ListKeymapnow registers aTabshortcut that sinks a top-level textblock into the previous list's last item. Pressing Tab at the start of a paragraph right after a bullet/ordered/task list moves the paragraph inside the last list item. The handler does nothing when the cursor is already inside a list item (sinkListItemkeeps working), when there is no list before the paragraph, when the caret is mid-textblock, or when the selection is not a text selection (for example a gap cursor).@tiptap/corealso exposes a newgetPreviousBlockSibling($pos)helper that returns the block-level sibling before the cursor's textblock, or null at the first child of the block parent. -
ceb0dac: New Decorations API
Finally the decorations API is here! Even though Decorations itself are nothing new in ProseMirror, the new API makes it much easier to use them in Tiptap without leaving your extensions.
Decorations change how the document looks without changing the document itself. Highlighting search results, marking spelling mistakes, showing collaborator cursors, putting a drag handle next to every block.
Until now you had to write a ProseMirror plugin by hand for this, keep the decoration set in plugin state, and map it forward on every transaction. Extensions can now declare decorations directly with a new
addDecorations()hook.addDecorations() { return { create: ({ state }) => // findMatches can be any function that returns an array of { from, to } ranges findMatches(state.doc).map(match => Decoration.Inline(match.from, match.to, { class: 'highlight' }), ), } }
There are three kinds.
Decoration.Inline()styles a range of text.Decoration.Node()puts attributes on a block's DOM element.Decoration.Widget()renders your own element at a single position.Every extension that declares decorations is collected into one plugin, so several extensions can decorate the same document without fighting over it.
Doing less work on every keystroke
By default decorations are rebuilt whenever the document changes. That is fine for small documents and wasteful for large ones, so there are two ways to narrow it down.
shouldUpdate()skips transactions you do not care about. If your decorations only depend on headings, ignore everything else.update: 'changedRanges'together withcreateInRange()only rescans the blocks that actually changed. On a long document this is the difference between scanning the whole thing on every keystroke and scanning one paragraph.For decorations driven by data outside the editor, like comments loaded from a server, use
update: 'manual'and refresh them yourself witheditor.commands.updateDecorations().React and Vue components as widgets
ReactWidgetRendererandVueWidgetRendererrender a real component into a widget decoration, inside your existing app context. Providers, context and stores work as usual.Widgets take a
key. Reuse the same key and the component instance stays mounted while the document changes around it, so local state such as an open menu, a counter or a half-typed input survives editing. Use a stable id from your own data, not a position or a list index, otherwise the component remounts and loses that state.Widgets also accept the ProseMirror options
side,relaxedSide,stopEventandignoreSelection.Documentation
- ceb0dac: Fixed
insertContent,insertContentAtandsetContentfailing when prosemirror-model is loaded more than once.
- ceb0dac: StarterKit now pins its bundled
@tiptap/*dependencies to the exact version it was released with, so installing a specific StarterKit version gives you that version's extension set instead of the newest one.
-
ceb0dac: New Decorations API
Finally the decorations API is here! Even though Decorations itself are nothing new in ProseMirror, the new API makes it much easier to use them in Tiptap without leaving your extensions.
Decorations change how the document looks without changing the document itself. Highlighting search results, marking spelling mistakes, showing collaborator cursors, putting a drag handle next to every block.
Until now you had to write a ProseMirror plugin by hand for this, keep the decoration set in plugin state, and map it forward on every transaction. Extensions can now declare decorations directly with a new
addDecorations()hook.addDecorations() { return { create: ({ state }) => // findMatches can be any function that returns an array of { from, to } ranges findMatches(state.doc).map(match => Decoration.Inline(match.from, match.to, { class: 'highlight' }), ), } }
There are three kinds.
Decoration.Inline()styles a range of text.Decoration.Node()puts attributes on a block's DOM element.Decoration.Widget()renders your own element at a single position.Every extension that declares decorations is collected into one plugin, so several extensions can decorate the same document without fighting over it.
Doing less work on every keystroke
By default decorations are rebuilt whenever the document changes. That is fine for small documents and wasteful for large ones, so there are two ways to narrow it down.
shouldUpdate()skips transactions you do not care about. If your decorations only depend on headings, ignore everything else.update: 'changedRanges'together withcreateInRange()only rescans the blocks that actually changed. On a long document this is the difference between scanning the whole thing on every keystroke and scanning one paragraph.For decorations driven by data outside the editor, like comments loaded from a server, use
update: 'manual'and refresh them yourself witheditor.commands.updateDecorations().React and Vue components as widgets
ReactWidgetRendererandVueWidgetRendererrender a real component into a widget decoration, inside your existing app context. Providers, context and stores work as usual.Widgets take a
key. Reuse the same key and the component instance stays mounted while the document changes around it, so local state such as an open menu, a counter or a half-typed input survives editing. Use a stable id from your own data, not a position or a list index, otherwise the component remounts and loses that state.Widgets also accept the ProseMirror options
side,relaxedSide,stopEventandignoreSelection.Documentation
- ceb0dac: React node views no longer show the selected state when the selection covers a position the node view has moved away from.
- ceb0dac: Markdown with inline HTML such as an unclosed
<b>tag no longer parses into an invalid document. The tag is dropped and its text is kept.
- ceb0dac: Fixed table cell and header spans in the React static renderer.
- ceb0dac: Fix the
./schema-listexport map pointingtypesatdist/schema/, which is not emitted. Tools that read thetypescondition directly could not resolve@tiptap/pm/schema-list.
- ceb0dac: Deleting the last row or column of a table no longer moves the cursor outside the table when there is content below it.
- ceb0dac: Fixed the React
DragHandlebreaking drag-and-drop whenonNodeChangeis an inline callback, by no longer re-registering its plugin when a callback's identity changes.
- ceb0dac: Allow KaTeX 0.18 to be installed with the math extension
- ceb0dac: Fixed Backspace freezing after merging a paragraph into a blockquote.
-
ceb0dac: New Decorations API
Finally the decorations API is here! Even though Decorations itself are nothing new in ProseMirror, the new API makes it much easier to use them in Tiptap without leaving your extensions.
Decorations change how the document looks without changing the document itself. Highlighting search results, marking spelling mistakes, showing collaborator cursors, putting a drag handle next to every block.
Until now you had to write a ProseMirror plugin by hand for this, keep the decoration set in plugin state, and map it forward on every transaction. Extensions can now declare decorations directly with a new
addDecorations()hook.addDecorations() { return { create: ({ state }) => // findMatches can be any function that returns an array of { from, to } ranges findMatches(state.doc).map(match => Decoration.Inline(match.from, match.to, { class: 'highlight' }), ), } }
There are three kinds.
Decoration.Inline()styles a range of text.Decoration.Node()puts attributes on a block's DOM element.Decoration.Widget()renders your own element at a single position.Every extension that declares decorations is collected into one plugin, so several extensions can decorate the same document without fighting over it.
Doing less work on every keystroke
By default decorations are rebuilt whenever the document changes. That is fine for small documents and wasteful for large ones, so there are two ways to narrow it down.
shouldUpdate()skips transactions you do not care about. If your decorations only depend on headings, ignore everything else.update: 'changedRanges'together withcreateInRange()only rescans the blocks that actually changed. On a long document this is the difference between scanning the whole thing on every keystroke and scanning one paragraph.For decorations driven by data outside the editor, like comments loaded from a server, use
update: 'manual'and refresh them yourself witheditor.commands.updateDecorations().React and Vue components as widgets
ReactWidgetRendererandVueWidgetRendererrender a real component into a widget decoration, inside your existing app context. Providers, context and stores work as usual.Widgets take a
key. Reuse the same key and the component instance stays mounted while the document changes around it, so local state such as an open menu, a counter or a half-typed input survives editing. Use a stable id from your own data, not a position or a list index, otherwise the component remounts and loses that state.Widgets also accept the ProseMirror options
side,relaxedSide,stopEventandignoreSelection.Documentation
v3.29.0
- Add official RubyText extension for HTML ruby text annotations with non-editable annotations and mark-based document storage. The click-to-edit annotation editor can be replaced with a custom element via the
renderAnnotationEditoroption.
- Fix a TypeScript build error in
isAndroid()where comparingnavigator.platformagainst the literal'Android'with===could fail to compile under somelib.dom.d.tstypings ("types have no overlap"). Switched to the same.includes()pattern already used byisiOS(), which is not affected by this TypeScript narrowing issue. No runtime behavior change. - Fixed a bug where deleting an
AllSelection(for example right after Ctrl/Cmd+A) left a lingering "phantom" selection highlight over the emptied document instead of a text cursor.deleteSelectionnow collapses the selection to a cursor. - Fix input rules crashing when the matched text spans an inline atom node like a mention.
- Node view
getPos()now returnsundefinedinstead of throwing when the position cannot be resolved yet, for example when React 19 renders a node view component while the editor view is still updating. - Fixed
onContentErrorthrowing when callingeditor.commandsfrom inside the handler on initial load with invalid content. The editor now has a usable state (seeded from the stripped fallback document) beforeonContentErrorfires. - Fix
editor.$pos()returning the wrong node inside container nodes, for example the list item instead of the list. - Add
insertDefaultBlockto insert the default textblock allowed at a position. It accepts an optional position, attributes, content, and selection-update option. - Updated dependencies [e9942fc]
- @tiptap/pm@3.29.0
- Added a new
@tiptap/extension-find-and-replaceextension. It searches the document for a term, highlights all matches with decorations, and replaces the current or all matches. Supports case-sensitive, whole-word, and RE2-compatible regex search, plus commands to jump between results. Regex search avoids catastrophic backtracking but does not support lookarounds or backreferences.
- Bump
prosemirror-modelto^1.25.11, fixing pasting content copied from the editor inserting extra empty paragraphs (a regression introduced inprosemirror-view1.42.0).
- Fixed a bug where inserted hard breaks would not scroll the view on insertion via commands.
- Fixed a bug where pressing
ArrowUpin a code block that is the first node in the document did nothing, leaving no way to insert content above it. A new default block is now inserted above the code block, mirroring the existingArrowDownbehavior. The behavior can be disabled via the newexitOnArrowUpoption.
- Fix inserting a table with an empty cell or header (e.g. via
insertContent/insertContentAt) throwingRangeError: Invalid content for node tableCell/tableHeader: <>. Empty<td>/<th>elements are now backfilled with the cell's default block content, matching the behavior you already get fromsetContent. - Keep line breaks inside table cells when serializing to markdown. Hard breaks and paragraph breaks in a cell are now written as
<br>instead of being collapsed into a space, so they survive a parse/serialize round trip.
- Fixed blank lines being dropped after block elements (headings, tables, etc.) when parsing markdown. Blank lines were being absorbed into the block token instead of being preserved, causing content to lose a blank line on each parse/serialize cycle.
- Fixed
useEditorStatenot re-rendering components wheneditor.setEditable()changes the editor's editable state, since that call only emits anupdateevent and never atransaction.
- Fix
<node-view-content as="tbody">(and similar restricted-content elements) rendering with an extra wrapper<div>nested inside them, which broke tables in Vue node views. Note: keep<node-view-content>mounted (usev-show, notv-if) — conditionally remounting it can leave ProseMirror attached to the old element.
- Fix resizable images not synchronizing rendered attributes after updates
- Fix Markdown table serialization for merged cells by preserving the correct column layout for rowspan and colspan.
- Typing or pasting Markdown link syntax like
[Tiptap](https://tiptap.dev)or[Tiptap](https://tiptap.dev "Rich text editor")can now automatically be converted into a link. The behavior is opt-in, enable it with the newmarkdownLinksoption.
v3.28.0
- 8614730: Fix the cursor moving to the details summary after typing in content at the start of a document.
- @tiptap/core@3.28.0
- @tiptap/extension-text-style@3.28.0
- @tiptap/pm@3.28.0
- 8614730: Fix markdown parsing a line like
(216) 555-1234as an ordered list. A number followed by)mid-line is no longer treated as a list marker. - @tiptap/core@3.28.0
- @tiptap/pm@3.28.0
- 8614730: Batch React node view portal store notifications that happen in the same microtask to avoid nested update depth warnings when many node views mount together.
- 8614730: Bind onMount and onUnmount event handlers when initializing an Editor with useEditor hook.
- @tiptap/core@3.28.0
- @tiptap/pm@3.28.0
- 8614730: Bump @tiptap/y-tiptap version to ensure users use latest version
- @tiptap/core@3.28.0
- @tiptap/pm@3.28.0
- 8614730: Bump @tiptap/y-tiptap version to ensure users use latest version
- @tiptap/core@3.28.0
- @tiptap/pm@3.28.0
- 8614730: Bump @tiptap/y-tiptap version to ensure users use latest version
- Updated dependencies [8614730]
- @tiptap/extension-collaboration@3.28.0
- @tiptap/core@3.28.0
- @tiptap/extension-node-range@3.28.0
- @tiptap/pm@3.28.0
- 8614730: Allow string
widthandheightvalues like'100%'or'20rem'in the YouTube extension. Non-numeric values in parsed HTML are now preserved instead of being converted to numbers.
v3.27.4
- 0fde1e8: Fixed the
Selectionextension leaving the native browser selection visible on blur, where it overlapped the selection decoration. The native selection is now cleared on blur and restored on focus. - @tiptap/core@3.27.4
- @tiptap/pm@3.27.4
- 0fde1e8: Fix a crash when pressing backspace at the very start of the document with a leading image. The blockquote backspace handler dereferenced an undefined parent at the top (doc) level, throwing
TypeError: Cannot read properties of undefined (reading 'type'). It now bails out so backspace is a no-op at the document start. - 0fde1e8: Add
@tiptap/pmas a peer dependency so bundlers resolve ProseMirror packages from the app instead of duplicatingprosemirror-modelinside@tiptap/extension-blockquote. - @tiptap/core@3.27.4
- @tiptap/pm@3.27.4
- 0fde1e8: Fix
<col width>in a table's<colgroup>being ignored when parsing HTML. The width of the first column was always dropped because the cell index0failed a truthiness check, and header cells (<th>) never read the colgroup at all. Both table cells and table headers now fall back to the matching<col>element'swidthattribute when they have nocolwidthattribute of their own. - 0fde1e8: Fix pipe characters inside backtick inline code spans being incorrectly treated as table column delimiters in both leading-pipe and pipeless (no leading
|) GFM tables. Cells containing expressions like`||`or`a || b`now parse correctly instead of splitting into extra columns and losing their code formatting. - @tiptap/core@3.27.4
- @tiptap/pm@3.27.4
- 0fde1e8: Fix a markdown parsing bug where a plain bullet (
- item) nested under a task-list parent (- [ ]) was silently dropped from the parsed document. The task-list tokenizer's nested parser stopped at the first non-checkbox line and discarded everything after it; that remainder is now lexed and kept as sibling blocks (a bullet list or paragraph inside the parent task item), matching how mixed lists already behave at the top level. - @tiptap/core@3.27.4
- @tiptap/pm@3.27.4
- 0fde1e8: Add a
use clientdirective so@tiptap/reactcan be imported from React Server Components without crashing. Core symbols re-exported through@tiptap/reactnow cross the client boundary too, so import them from@tiptap/coredirectly in server code.
v3.27.3
- 94de762: Fix
deleteSelectionto delete content across all selection ranges instead of only the first range. This restores multi-cell table selections and other custom selections with multiple ranges. - @tiptap/pm@3.27.3
-
94de762: Fix markdown parsing bugs where block elements right after an ordered list item (with no blank line in between) were wrongly treated as lazy continuation of the list item, instead of terminating the list the way other markdown parsers do:
- Thematic breaks (
---,***,___,* * *) were swallowed into the list item as literal paragraph text — along with every line after them. They now terminate the list and become a horizontal rule. - Fenced code blocks (
```and~~~) were nested inside the list item. They now terminate the list and become a top-level code block. - Unindented bullet markers (
- item) were nested inside the ordered list item. They now terminate the ordered list and start a new top-level bullet list. Indented bullets still nest inside the item as before.
An indented
***/___inside item content is now also parsed as a horizontal rule inside the item instead of literal text. A---line directly below item paragraph text keeps its current behavior because it is a setext heading underline per CommonMark, not a thematic break. - Thematic breaks (
-
94de762: Fix indented ordered list items (e.g. one leading space before the marker, as happens when a top-level ordered list is itself nested inside another list) losing inline formatting during markdown parsing. The custom ordered-list markdown tokenizer built its nested structure with a hardcoded base indentation of 0, so an item whose actual indentation was non-zero never matched, causing the tokenizer to silently produce zero items and bail out — falling back to a path that left the item's content as literal, unparsed text instead of running it through inline tokenization (bold, italic, etc. were lost). The base indentation is now taken from the first collected item instead of being hardcoded.
-
Updated dependencies [94de762]
- @tiptap/core@3.27.3
- @tiptap/pm@3.27.3
- 94de762: Fixed placeholder flickering and disappearance on large documents. Replaced the viewport-based decoration scan with an incremental
StateField<DecorationSet>that only re-computes decorations for top-level nodes touched by each transaction. This eliminates the dependency on DOM measurement (posAtCoords),requestAnimationFramescheduling, and scroll listeners that caused flickering under collaboration, occlusion, and rapid edits.