Package Exports
- mui-tiptap
Readme
mui-tiptap: A customizable Material UI styled WYSIWYG rich text editor, using Tiptap.
- ✨ Styled based on your own MUI theme (colors, fonts, light vs dark mode, etc.)
- 🛠️ Built on powerful Tiptap and ProseMirror foundations (extensible, real-time collaborative editing, cross-platform support, etc.)
Features:
- 🧰 An all-in-one
RichTextEditor
component to get started immediately (no other components or hooks needed!), or individual modular components to customize to your needs - 😎 Built-in styles for Tiptap’s extensions (text formatting, lists, tables, Google Docs-like collaboration cursors; you name it!)
- ▶️ Composable and extendable menu buttons and controls for the standard Tiptap extensions
- 🖼️
ResizableImage
extension for adding and resizing images directly in the editor - ⚓
HeadingWithAnchor
extension for dynamic GitHub-like anchor links for every heading you add - 🔗
LinkBubbleMenu
so adding and editing links is a breeze - 🔳
TableImproved
extension that fixes problems in the underlying TiptapTable
extension - 📝
TableBubbleMenu
for interactively editing your rich text tables - 🏗️ General-purpose
ControlledBubbleMenu
for building your own custom menus, solving some shortcomings of the TiptapBubbleMenu
- And more!
Demo
Try it yourself in this CodeSandbox live demo!
Installation
npm install mui-tiptap
or
yarn add mui-tiptap
There are peer dependencies on @mui/material
and @mui/icons-material
(and their @emotion/
peers), react-icons
, and @tiptap/
packages. These should be installed automatically by default if you’re using npm 7+ or pnpm. Otherwise, if your project doesn’t already use those, you can install them with:
npm install @mui/material @mui/icons-material @emotion/react @emotion/styled react-icons @tiptap/react @tiptap/extension-heading @tiptap/extension-image @tiptap/extension-link @tiptap/extension-table @tiptap/pm @tiptap/core
or
yarn add @mui/material @mui/icons-material @emotion/react @emotion/styled react-icons @tiptap/react @tiptap/extension-heading @tiptap/extension-image @tiptap/extension-link @tiptap/extension-table @tiptap/pm @tiptap/core
Get started
🚧 More documentation coming soon!
Use the all-in-one component
The simplest way to render a rich text editor is to use the <RichTextEditor />
component:
import { Button } from "@mui/material";
import StarterKit from "@tiptap/starter-kit";
import { RichTextEditor, type RichTextEditorRef } from "mui-tiptap";
import { useRef } from "react";
function App() {
const rteRef = useRef<RichTextEditorRef>(null);
return (
<div>
<RichTextEditor
ref={rteRef}
content="<p>Hello world</p>"
extensions={[StarterKit]} // Or any extensions you wish!
/>
<Button onClick={() => console.log(rteRef.current?.editor?.getHTML())}>
Show HTML
</Button>
</div>
);
}
Use its renderControls
prop if you'd like to render buttons in a menu bar atop the editor (e.g., for toggling text styles, inserting a table, adding a link, and more). See src/demo/Editor.tsx
for a more thorough example of this.
Create and provide the editor
yourself
If you need more customization, you can instead define your editor using Tiptap’s useEditor
hook, and lay out your UI using a selection of mui-tiptap
components (and/or your own components).
Pass the editor
to mui-tiptap
’s <RichTextEditorProvider>
component at the top of your component tree. From there, provide whatever children to the provider that fit your needs.
The easiest is option is the <RichTextField />
component, which is what RichTextEditor
uses under the hood:
import { useEditor } from "@tiptap/react";
import StarterKit from "@tiptap/starter-kit";
import {
MenuButtonBold,
MenuButtonItalic,
MenuControlsContainer,
MenuDivider,
MenuSelectHeading,
RichTextEditorProvider,
RichTextField,
} from "mui-tiptap";
function App() {
const editor = useEditor({
extensions: [StarterKit],
content: "<p>Hello <b>world</b>!</p>",
});
return (
<RichTextEditorProvider editor={editor}>
<RichTextField
controls={
<MenuControlsContainer>
<MenuSelectHeading />
<MenuDivider />
<MenuButtonBold />
<MenuButtonItalic />
{/* Add more controls of your choosing here */}
</MenuControlsContainer>
}
/>
</RichTextEditorProvider>
);
}
Or if you want full control over the UI, instead of RichTextField
, you can build the editor area yourself and then just use the <RichTextContent />
component where you want the (styled) editable rich text content to appear. RichTextContent
is the MUI-themed version of Tiptap's EditorContent
component.
Render read-only rich text content
Use the <RichTextReadOnly />
component and just pass in your HTML or ProseMirror JSON, like:
<RichTextReadOnly content="<p>Hello world</p>" extensions=[...] />
This component will skip creating the Tiptap editor
if content
is empty, which can help performance.
Tips and suggestions
Defining your editor extensions
Extensions that need to be higher precedence (for their keyboard shortcuts, etc.) should come later in your extensions array. (See Tiptap's general notes on extension plugin precedence and ordering here.) For example:
- Put the
TableImproved
(orTable
) extension first in the array.- As noted in the underlying
prosemirror-tables
package, the table editing plugin should have the lowest precedence, since it depends on key and mouse events but other plugins will likely need to take handle those first. For instance, if you want to indent or dedent a list item inside a table, you should be able to do that by pressing tab, and tab should only move between table cells if not within such a nested node.
- As noted in the underlying
- Put the
Blockquote
extension after theBold
extension, soBlockquote
’s keyboard shortcut takes precedence. - Put the
Mention
extension after list-related extensions (TaskList
,TaskItem
,BulletList
,OrderedList
,ListItem
, etc.) so that pressing "Enter" on a mention suggestion will select it, rather than create a new list item (when trying to @mention something within an existing list item).
Other extension tips:
If you'd like
Subscript
andSuperscript
extensions to be mutually exclusive, so that text can't be both superscript and subscript simultaneously, use theexcludes
configuration parameter to exclude each other.As described in this Tiptap issue. For instance:
const CustomSubscript = Subscript.extend({ excludes: "superscript", }); const CustomSuperscript = Superscript.extend({ excludes: "subscript", });
Contributing
Get started here.