@its-arun/tiptap-studio v0.1.6
@its-arun/tiptap-studio
A modern and feature-rich Rich Text Editor component built with TipTap for React applications.

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-kitFull 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 lowlightPeer Dependencies
Make sure you have the required peer dependencies installed:
npm install react react-domBasic 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:
- Use dynamic imports: Import the editor dynamically with
ssr: false - Explicit SSR prop: Set
ssr={false}on the TipTapEditor component - 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: falsefor any errors- Descriptive
errormessages 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