JSPM

  • Created
  • Published
  • Downloads 42109
  • Score
    100M100P100Q153299F
  • License MIT

A Material-UI (MUI) styled WYSIWYG rich text editor, using Tiptap

Package Exports

  • mui-tiptap

Readme

mui-tiptap logo

mui-tiptap: A customizable Material UI styled WYSIWYG rich text editor, using Tiptap.

npm mui-tiptap package npm type definitions GitHub Workflow Status project license

  • ✨ 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 Tiptap Table extension
  • 📝 TableBubbleMenu for interactively editing your rich text tables
  • 🏗️ General-purpose ControlledBubbleMenu for building your own custom menus, solving some shortcomings of the Tiptap BubbleMenu
  • And more!

Demo

Try it yourself in this CodeSandbox live demo!

mui-tiptap 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 (or Table) 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.
  • Put the Blockquote extension after the Bold extension, so Blockquote’s keyboard shortcut takes precedence.
    • Otherwise, the keyboard shortcut for Blockquote (Cmd+Shift+B) will mistakenly toggle the bold mark (due to its “overlapping” Cmd+b shortcut). (See related Tiptap issues here and here.)
  • 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 and Superscript extensions to be mutually exclusive, so that text can't be both superscript and subscript simultaneously, use the excludes 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.