Rich Editor

A powerful, block-based rich text editor with tables, images, formatting, and mobile-optimized UX.

Installation

Step 1: Install the rich-editor component

npx shadcn@latest add https://ui-v4-livid.vercel.app/r/styles/new-york-v4/rich-editor.json

This automatically installs all required shadcn components, npm packages, and editor files.

Step 2: Configure the theme provider

The rich editor includes dark mode toggle functionality. Wrap your app with the ThemeProvider from next-themes.

Usage

import { EditorProvider, createInitialState } from "@/lib"
import { createEmptyContent } from "@/lib/empty-content"
import { Editor } from "@/components/Editor"
import { ContainerNode } from "@/lib/types"

export default function MyEditor() {
  // Create initial content
  const initialState = createInitialState({
    id: "root",
    type: "container",
    children: createEmptyContent(),
    attributes: {}
  })

  return (
    <EditorProvider initialState={initialState}>
      <Editor />
    </EditorProvider>
  )
}

Features

Block-based architecture

Drag & drop blocks with intuitive organization

📝

Rich text formatting

Bold, italic, underline, strikethrough, colors, and font sizes

📊

Advanced tables

Drag columns/rows, resize, markdown import, and cell formatting

🖼️

Image management

Multi-select with Ctrl+Click, drag & drop, custom upload handlers

🎨

Custom Tailwind classes

Built-in class picker with live preview

🔗

Smart links

Easy link insertion with automatic protocol handling

⌨️

Keyboard shortcuts

Full keyboard navigation and formatting shortcuts

📱

Mobile optimized

Sheet drawers, touch-friendly controls, automatic keyboard management

🌙

Dark mode

Full dark mode support out of the box

🔄

Undo/Redo

Complete history management with Ctrl+Z/Ctrl+Y

📤

HTML export

Export your content to clean HTML

Zustand-powered

Optimized state management with selective re-renders

Keyboard Shortcuts

Ctrl/Cmd + BToggle bold
Ctrl/Cmd + IToggle italic
Ctrl/Cmd + UToggle underline
Ctrl/Cmd + ZUndo
Ctrl/Cmd + Shift + ZRedo
Shift + EnterCreate nested block
Ctrl/Cmd + KInsert link
Ctrl + ClickMulti-select images
Delete/BackspaceDelete selected blocks

API Reference

EditorProvider

Wraps your editor and provides the Zustand store context for all editor operations. Based on Zustand for optimal performance.

PropTypeDefaultDescription
initialContainerContainerNode-Initial content structure (alternative to initialState)
initialStateEditorState-Complete initial state including history and metadata
onChange(state: EditorState) => void-Callback fired on state changes
debugbooleanfalseEnable debug logging
childrenReactNode-Editor components to render

Editor

The main editor component that renders the editing interface.

PropTypeDefaultDescription
readOnlybooleanfalseView-only mode - renders content without editing capabilities
onUploadImage(file: File) => Promise<string>-Custom image upload handler - should return the uploaded image URL
notionBasedbooleantrueEnable Notion-style features (cover image, first header spacing)
onNotionBasedChange(notionBased: boolean) => void-Callback when notion mode is toggled

Hooks

The editor provides several hooks to access and manipulate state. All hooks must be used inside an EditorProvider.

useEditorDispatch

Returns the dispatch function to trigger editor actions

const dispatch = useEditorDispatch()

useContainer

Returns the current content container (root node)

const container = useContainer()

useActiveNodeId

Returns the ID of the currently focused block

const activeNodeId = useActiveNodeId()

useSelection

Returns the current text selection information

const selection = useSelection()

useBlockNode

Returns a specific node by ID with automatic re-renders

const node = useBlockNode(nodeId)

Customization

Custom Image Upload

Provide your own upload handler to integrate with your backend:

async function handleUpload(file: File): Promise<string> {
  const formData = new FormData()
  formData.append("image", file)

  const response = await fetch("/api/upload", {
    method: "POST",
    body: formData,
  })

  const data = await response.json()
  return data.url
}

<Editor onUploadImage={handleUpload} />

Export to HTML

Export your content to HTML for storage or display:

import { serializeToHtml } from "@/lib"
import { useContainer } from "@/lib"

function ExportButton() {
  const container = useContainer()
  
  const handleExport = () => {
    const html = serializeToHtml(container)
    console.log(html)
  }
  
  return <button onClick={handleExport}>Export</button>
}

Using Actions

Dispatch actions to modify editor content:

import { EditorActions, useEditorDispatch } from "@/lib"

function MyComponent() {
  const dispatch = useEditorDispatch()
  
  const addParagraph = () => {
    const newNode = {
      id: `p-${Date.now()}`,
      type: "p",
      content: "New paragraph",
      attributes: {}
    }
    
    dispatch(EditorActions.insertNode(
      newNode, 
      "target-node-id", 
      "after"
    ))
  }
  
  return <button onClick={addParagraph}>Add Paragraph</button>
}

Notes

  • The editor uses Zustand for state management, providing optimal performance with selective re-renders.
  • The editor uses Framer Motion for animations. Make sure it's installed.
  • Images are stored as base64 by default. Provide a custom upload handler for production use.
  • The editor is mobile-responsive and uses Sheet components on smaller screens.
  • All colors and classes use Tailwind CSS and follow shadcn/ui design patterns.
  • The editor supports notion-based mode with cover images and special first-header styling.

Credits

Created by Mina Massoud