Skip to main content

Overview

The @kuzenbo/tiptap package provides a fully-featured rich text editor built on Tiptap. Includes toolbar controls, bubble menus, slash commands, mentions, and extensive customization options.
This package is currently in preview and not yet published to npm. The API may change before the stable release.

Installation

Once published, install with:
npm install @kuzenbo/tiptap @kuzenbo/core @kuzenbo/theme react react-dom

Components

TiptapEditor

Main editor component with full functionality

TiptapEditor.Toolbar

Customizable formatting toolbar

TiptapEditor.BubbleMenu

Context menu for selected text

TiptapEditor.FloatingMenu

Floating menu for empty lines

SlashMenu

Command palette with /commands

MentionMenu

@mention autocomplete

Basic Usage

Simple Editor

import { TiptapEditor } from '@kuzenbo/tiptap/ui/tiptap-editor';
import { useState } from 'react';

export function BasicEditor() {
  const [content, setContent] = useState('<p>Start typing...</p>');

  return (
    <TiptapEditor
      content={content}
      onUpdate={({ editor }) => setContent(editor.getHTML())}
    />
  );
}

Editor with Toolbar

import { TiptapEditor } from '@kuzenbo/tiptap/ui/tiptap-editor';
import '@kuzenbo/tiptap/styles.css';

export function EditorWithToolbar() {
  return (
    <TiptapEditor defaultContent="<p>Hello world</p>">
      <TiptapEditor.Toolbar>
        <TiptapEditor.BoldControl />
        <TiptapEditor.ItalicControl />
        <TiptapEditor.UnderlineControl />
        <TiptapEditor.StrikethroughControl />
        <TiptapEditor.Separator />
        <TiptapEditor.H1Control />
        <TiptapEditor.H2Control />
        <TiptapEditor.H3Control />
        <TiptapEditor.Separator />
        <TiptapEditor.BulletListControl />
        <TiptapEditor.OrderedListControl />
        <TiptapEditor.CodeBlockControl />
      </TiptapEditor.Toolbar>
      <TiptapEditor.Content />
    </TiptapEditor>
  );
}
import { TiptapEditor } from '@kuzenbo/tiptap/ui/tiptap-editor';
import { SlashMenu } from '@kuzenbo/tiptap/ui/menus/slash-menu';
import { MentionMenu } from '@kuzenbo/tiptap/ui/menus/mention-menu';

export function FullEditor() {
  return (
    <TiptapEditor
      variant="outlined"
      placeholder="Type / for commands..."
    >
      <TiptapEditor.Toolbar>
        <TiptapEditor.BoldControl />
        <TiptapEditor.ItalicControl />
        <TiptapEditor.UnderlineControl />
        <TiptapEditor.CodeControl />
        <TiptapEditor.Separator />
        <TiptapEditor.LinkControl />
        <TiptapEditor.ColorControl />
        <TiptapEditor.HighlightControl />
        <TiptapEditor.Separator />
        <TiptapEditor.AlignLeftControl />
        <TiptapEditor.AlignCenterControl />
        <TiptapEditor.AlignRightControl />
        <TiptapEditor.AlignJustifyControl />
        <TiptapEditor.Separator />
        <TiptapEditor.InsertTableControl />
        <TiptapEditor.UndoControl />
        <TiptapEditor.RedoControl />
      </TiptapEditor.Toolbar>
      
      <TiptapEditor.BubbleMenu>
        <TiptapEditor.BoldControl />
        <TiptapEditor.ItalicControl />
        <TiptapEditor.LinkControl />
      </TiptapEditor.BubbleMenu>
      
      <TiptapEditor.Content />
      
      <SlashMenu />
      <MentionMenu />
    </TiptapEditor>
  );
}

Editor Hook

useKuzenboEditor

import { useKuzenboEditor } from '@kuzenbo/tiptap/editor/use-kuzenbo-editor';
import { TiptapEditor } from '@kuzenbo/tiptap/ui/tiptap-editor';

export function CustomEditor() {
  const editor = useKuzenboEditor({
    content: '<p>Initial content</p>',
    editable: true,
    onUpdate: ({ editor }) => {
      console.log('Content changed:', editor.getHTML());
    },
    extensions: [
      // Add custom extensions
    ],
  });

  if (!editor) return null;

  return <TiptapEditor.Content editor={editor} />;
}

Markdown Support

import { TiptapEditor } from '@kuzenbo/tiptap/ui/tiptap-editor';
import { markdownAdapter } from '@kuzenbo/tiptap/editor/markdown-adapter';

export function MarkdownEditor() {
  const editor = useKuzenboEditor({
    content: '# Hello\n\nThis is **markdown**',
    extensions: [markdownAdapter],
  });

  const getMarkdown = () => {
    return editor?.storage.markdown.getMarkdown();
  };

  return <TiptapEditor.Content editor={editor} />;
}

Slash Commands

import { TiptapEditor } from '@kuzenbo/tiptap/ui/tiptap-editor';
import { SlashMenu } from '@kuzenbo/tiptap/ui/menus/slash-menu';

const slashCommands = [
  {
    title: 'Heading 1',
    description: 'Large section heading',
    command: ({ editor }) => editor.chain().focus().setHeading({ level: 1 }).run(),
  },
  {
    title: 'Table',
    description: 'Insert a table',
    command: ({ editor }) => editor.chain().focus().insertTable().run(),
  },
];

export function EditorWithCommands() {
  return (
    <TiptapEditor>
      <TiptapEditor.Content />
      <SlashMenu commands={slashCommands} />
    </TiptapEditor>
  );
}

Mentions

import { TiptapEditor } from '@kuzenbo/tiptap/ui/tiptap-editor';
import { MentionMenu } from '@kuzenbo/tiptap/ui/menus/mention-menu';

const users = [
  { id: '1', name: 'John Doe', avatar: '/john.jpg' },
  { id: '2', name: 'Jane Smith', avatar: '/jane.jpg' },
];

export function EditorWithMentions() {
  return (
    <TiptapEditor>
      <TiptapEditor.Content />
      <MentionMenu
        items={users}
        onSelect={(user) => console.log('Mentioned:', user)}
      />
    </TiptapEditor>
  );
}

Custom Extensions

import { createTiptapExtensionsPreset } from '@kuzenbo/tiptap/editor/create-tiptap-extensions-preset';
import { TiptapEditor } from '@kuzenbo/tiptap/ui/tiptap-editor';
import CustomExtension from './CustomExtension';

const extensions = createTiptapExtensionsPreset({
  capabilities: {
    heading: true,
    bold: true,
    italic: true,
    link: true,
    codeBlock: true,
    table: true,
  },
  additionalExtensions: [CustomExtension],
});

export function EditorWithCustomExtensions() {
  return <TiptapEditor extensions={extensions} />;
}

Editor Variants

import { TiptapEditor } from '@kuzenbo/tiptap/ui/tiptap-editor';

// Outlined variant
<TiptapEditor variant="outlined" />

// Filled variant
<TiptapEditor variant="filled" />

// Ghost variant (no border)
<TiptapEditor variant="ghost" />

Controlled Editor

import { TiptapEditor } from '@kuzenbo/tiptap/ui/tiptap-editor';
import { useState } from 'react';

export function ControlledEditor() {
  const [content, setContent] = useState('<p>Initial</p>');

  return (
    <>
      <TiptapEditor
        content={content}
        onUpdate={({ editor }) => setContent(editor.getHTML())}
      />
      <button onClick={() => setContent('<p>Reset</p>')}>Reset</button>
    </>
  );
}

Read-only Mode

import { TiptapEditor } from '@kuzenbo/tiptap/ui/tiptap-editor';

export function ReadOnlyEditor() {
  return (
    <TiptapEditor
      content="<p>Read-only content</p>"
      editable={false}
    />
  );
}

Custom Styling

Import the base styles and customize:
import '@kuzenbo/tiptap/styles.css';

<TiptapEditor
  classNames={{
    root: 'custom-editor',
    content: 'custom-content',
    toolbar: 'custom-toolbar',
  }}
/>

Dependencies

  • @tiptap/core - Core Tiptap functionality
  • @tiptap/react - React integration
  • @tiptap/starter-kit - Essential extensions
  • @tiptap/extension-* - Additional extensions
  • @tiptap/markdown - Markdown support
  • @kuzenbo/core - Core components
  • tailwind-variants - Styling

Peer Dependencies

{
  "@kuzenbo/core": "^0.0.7",
  "@kuzenbo/theme": "^0.0.7",
  "react": "^19.0.0",
  "react-dom": "^19.0.0"
}

TypeScript

Fully typed editor components:
import { TiptapEditor } from '@kuzenbo/tiptap/ui/tiptap-editor';
import type { TiptapEditorProps } from '@kuzenbo/tiptap/ui/tiptap-editor';
import type { Editor } from '@tiptap/core';

const MyEditor: React.FC<{
  onSave: (html: string) => void;
}> = ({ onSave }) => {
  const handleUpdate = ({ editor }: { editor: Editor }) => {
    onSave(editor.getHTML());
  };

  return <TiptapEditor onUpdate={handleUpdate} />;
};

Next Steps

Core Components

Explore other UI components

Build docs developers (and LLMs) love