npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@its-arun/tiptap-studio

v0.1.8

Published

A modern and feature-rich Rich Text Editor component built with TipTap for React

Readme

@its-arun/tiptap-studio

A modern and feature-rich Rich Text Editor component built with TipTap for React applications.

Tiptap Studio Demo

Features

  • 📝 Rich Text Editor with comprehensive formatting options
  • 🌓 Light/Dark theme support
  • 📱 Responsive design
  • 🎨 Customizable toolbar and styling
  • 🔧 TypeScript support
  • 📊 Content Rendering (SSR/CSR)
  • 🖼️ Image support with captions
  • 📋 Table support
  • 🎯 Link management
  • 📝 Code blocks with syntax highlighting
  • 🔍 Source code editing with CodeMirror
  • ⚙️ Configurable image upload/fetch endpoints

Installation

Basic Installation

For basic editor functionality:

npm install @its-arun/tiptap-studio @tiptap/react @tiptap/pm @tiptap/starter-kit

Full Feature Installation

For all features including UI components, syntax highlighting, etc.:

npm install @its-arun/tiptap-studio @tiptap/react @tiptap/pm @tiptap/starter-kit @tiptap/extension-character-count @tiptap/extension-code-block-lowlight @tiptap/extension-color @tiptap/extension-highlight @tiptap/extension-image @tiptap/extension-link @tiptap/extension-list-keymap @tiptap/extension-placeholder @tiptap/extension-subscript @tiptap/extension-superscript @tiptap/extension-table @tiptap/extension-table-cell @tiptap/extension-table-header @tiptap/extension-table-row @tiptap/extension-text-align @tiptap/extension-text-style @tiptap/extension-underline @radix-ui/react-dropdown-menu @radix-ui/react-popover @radix-ui/react-tooltip react-colorful react-icons lowlight

Peer Dependencies

Make sure you have the required peer dependencies installed:

npm install react react-dom

Basic Usage

import React, { useState } from "react";
import { TipTapEditor } from "@its-arun/tiptap-studio";
import "@its-arun/tiptap-studio/dist/styles.css"; // Import the styles

function MyEditor() {
  const [content, setContent] = useState("<p>Start writing...</p>");

  return (
    <TipTapEditor
      initialContent={content}
      onContentChange={(value) => setContent(value)}
      placeholder={{
        paragraph: "Start writing your content...",
        imageCaption: "Add a caption...",
      }}
    />
  );
}

export default MyEditor;

Advanced Usage

With Custom Configuration

import React, { useRef } from 'react';
import { TipTapEditor, type TiptapEditorRef } from '@its-arun/tiptap-studio';
import '@its-arun/tiptap-studio/dist/styles.css';

function AdvancedEditor() {
  const editorRef = useRef<TiptapEditorRef>(null);

  const handleSave = () => {
    const editor = editorRef.current?.getInstance();
    if (editor) {
      const html = editor.getHTML();
      const json = editor.getJSON();
      console.log({ html, json });
    }
  };

  return (
    <div>
      <TipTapEditor
        ref={editorRef}
        initialContent="<p>Advanced editor example</p>"
        output="html" // or "json"
        contentMinHeight={300}
        contentMaxHeight={600}
        hideMenuBar={false}
        hideStatusBar={false}
        hideBubbleMenu={false}
        onContentChange={(value) => console.log('Content changed:', value)}
      />
      <button onClick={handleSave}>Save Content</button>
    </div>
  );
}

With Custom Image Configuration

Configure your own image upload/fetch endpoints:

import React from 'react';
import { TipTapEditor, type ImageConfig } from '@its-arun/tiptap-studio';
import '@its-arun/tiptap-studio/dist/styles.css';

function EditorWithCustomImages() {
  const imageConfig: ImageConfig = {
    upload: {
      url: '/api/my-upload-endpoint',
      headers: {
        'Authorization': 'Bearer your-token',
      },
      formDataKey: 'image', // default is 'file'
    },
    fetch: {
      url: '/api/my-images-endpoint',
      headers: {
        'Authorization': 'Bearer your-token',
      },
    },
  };

  return (
    <TipTapEditor
      imageConfig={imageConfig}
      initialContent="<p>Editor with custom image handling</p>"
      onContentChange={(value) => console.log('Content:', value)}
    />
  );
}

With Custom Provider

import React from "react";
import {
  TiptapProvider,
  useTiptapContext,
  ExtensionKit,
} from "@its-arun/tiptap-studio";

function CustomEditor() {
  const editorOptions = {
    extensions: ExtensionKit,
    content: "<p>Custom provider example</p>",
    editable: true,
  };

  return (
    <TiptapProvider editorOptions={editorOptions}>
      <CustomToolbar />
      <EditorContent />
    </TiptapProvider>
  );
}

function CustomToolbar() {
  const { editor } = useTiptapContext();

  return (
    <div>
      <button
        onClick={() => editor.chain().focus().toggleBold().run()}
        data-active={editor.isActive("bold")}
      >
        Bold
      </button>
      <button
        onClick={() => editor.chain().focus().toggleItalic().run()}
        data-active={editor.isActive("italic")}
      >
        Italic
      </button>
    </div>
  );
}

Props

TipTapEditor Props

| Prop | Type | Default | Description | | ------------------ | -------------------------- | ------------- | -------------------------------------------- | | initialContent | Content | undefined | Initial editor content (HTML string or JSON) | | onContentChange | (value: Content) => void | undefined | Callback when content changes | | output | "html" \| "json" | "html" | Output format for content changes | | placeholder | object | undefined | Placeholder text for different elements | | readonly | boolean | false | Make editor read-only | | disabled | boolean | false | Disable editor interactions | | hideMenuBar | boolean | false | Hide the top menu bar | | hideStatusBar | boolean | false | Hide the bottom status bar | | hideBubbleMenu | boolean | true | Hide the bubble menu | | contentMinHeight | string \| number | 200 | Minimum height of editor content | | contentMaxHeight | string \| number | undefined | Maximum height of editor content | | ssr | boolean | auto-detect | Enable server-side rendering support | | imageConfig | ImageConfig | undefined | Configure image upload/fetch endpoints |

ImageConfig Interface

interface ImageConfig {
  upload?: {
    url: string; // Upload endpoint URL
    headers?: Record<string, string>; // Optional headers
    formDataKey?: string; // Form data key (default: 'file')
  };
  fetch?: {
    url: string; // Fetch images endpoint URL
    headers?: Record<string, string>; // Optional headers
  };
}

Server-Side Rendering (SSR)

The editor automatically detects SSR environments and adjusts accordingly. You can also explicitly control SSR behavior:

// Explicitly enable SSR mode
<TipTapEditor ssr={true} />

// Explicitly disable SSR mode
<TipTapEditor ssr={false} />

// Auto-detect (default)
<TipTapEditor />

Next.js App Router Usage

When using the editor in Next.js with the App Router, you may need to import it dynamically to prevent SSR issues:

"use client";

import dynamic from "next/dynamic";

const TipTapEditor = dynamic(
  () => import("@its-arun/tiptap-studio").then((mod) => mod.TipTapEditor),
  {
    ssr: false,
    loading: () => <p>Loading editor...</p>,
  }
);

function MyEditor() {
  return (
    <TipTapEditor
      initialContent="<p>Start writing...</p>"
      onContentChange={(value) => console.log(value)}
    />
  );
}

Next.js Pages Router Usage

For the Pages Router, wrap your editor component:

import dynamic from "next/dynamic";

const TipTapEditor = dynamic(
  () => import("@its-arun/tiptap-studio").then((mod) => mod.TipTapEditor),
  {
    ssr: false,
  }
);

export default function MyPage() {
  return (
    <div>
      <TipTapEditor ssr={false} initialContent="<p>Start writing...</p>" />
    </div>
  );
}

Troubleshooting SSR Issues

If you encounter hydration errors like "SSR has been detected, and immediatelyRender has been set to true", try:

  1. Use dynamic imports: Import the editor dynamically with ssr: false
  2. Explicit SSR prop: Set ssr={false} on the TipTapEditor component
  3. Client-side only rendering: Wrap your editor in a component with 'use client' directive

Image Upload/Fetch API

Default API Endpoints

By default, the editor expects these endpoints:

  • Upload: POST /api/images - Upload a single image file
  • Fetch: GET /api/images - Get list of available images

API Response Format

Both endpoints must return responses in the following standardized format:

Upload Response Format

{
  "success": boolean,
  "data": {
    "id": string,
    "url": string,
    "filename": string,
    "mimeType": string,
    "size": number,
    "existing": boolean
  },
  "error"?: string
}

Fetch Response Format

{
  "success": boolean,
  "data": [
    {
      "id": number,
      "url": string,
      "filename": string,
      "mimeType": string,
      "size": number
    }
  ],
  "error"?: string
}

Custom API Implementation

You can implement your own image handling endpoints. Here are examples:

Upload Endpoint Example

// pages/api/images/route.js or app/api/images/route.js
export async function POST(request) {
  try {
    const formData = await request.formData();
    const file = formData.get("file"); // default formDataKey is 'file'

    if (!file) {
      return Response.json(
        {
          success: false,
          error: "No file provided",
        },
        { status: 400 }
      );
    }

    // Your upload logic here
    const result = await uploadToYourService(file);

    return Response.json({
      success: true,
      data: {
        id: result.id,
        url: result.url,
        filename: file.name,
        mimeType: file.type,
        size: file.size,
        existing: false,
      },
    });
  } catch (error) {
    return Response.json(
      {
        success: false,
        error: error.message || "Upload failed",
      },
      { status: 500 }
    );
  }
}

Fetch Endpoint Example

// pages/api/images/route.js or app/api/images/route.js
export async function GET(request) {
  try {
    // Your fetch logic here
    const images = await getImagesFromYourService();

    return Response.json({
      success: true,
      data: images.map((img) => ({
        id: img.id,
        url: img.url,
        filename: img.filename,
        mimeType: img.mimeType,
        size: img.size,
      })),
    });
  } catch (error) {
    return Response.json(
      {
        success: false,
        data: [],
        error: error.message || "Failed to fetch images",
      },
      { status: 500 }
    );
  }
}

Error Handling

The editor automatically handles API errors and displays user-friendly messages. Make sure your API returns:

  • success: false for any errors
  • Descriptive error messages for debugging
  • Appropriate HTTP status codes (400 for client errors, 500 for server errors)

Available Exports

// Main components
import {
  TipTapEditor,
  TiptapProvider,
  SourceEditor,
  TipTapClientRenderer,
  TipTapServerRenderer
} from '@its-arun/tiptap-studio';

// Hooks
import {
  useTiptapEditor,
  useTiptapContext
} from '@its-arun/tiptap-studio';

// Utils
import {
  isNodeSelected,
  isTextSelected,
  getNodeContainer,
  ExtensionKit
} from '@its-arun/tiptap-studio';

// UI Components
import {
  BubbleMenu,
  Tooltip,
  Input
} from '@its-arun/tiptap-studio';

// Types
import type {
  TiptapEditorProps,
  TiptapEditorRef,
  UseTiptapEditorOptions,
  ImageConfig,
  Editor,
  Content
} from '@its-arun/tiptap-studio';

Styling

The package includes default styles that you need to import:

import "@its-arun/tiptap-studio/dist/styles.css";

You can also customize the appearance using CSS variables:

:root {
  --rte-editor-font: "Inter", sans-serif;
  --rte-editor-font-size: 16px;
  --rte-editor-line-height: 1.6;
  --rte-editor-min-height: 200px;
  --rte-editor-max-height: 600px;
}

Tech Stack

  • React 18+
  • TipTap
  • CodeMirror (for source editing)
  • TypeScript
  • Radix UI (for UI components)
  • SCSS + CSS Variables

Credits

This package is based on the excellent work from next-tiptap by @ndtrung341. We've transformed their Next.js TipTap editor implementation into a reusable React component package.

License

MIT