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.jsonThis 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 boldCtrl/Cmd + IToggle italicCtrl/Cmd + UToggle underlineCtrl/Cmd + ZUndoCtrl/Cmd + Shift + ZRedoShift + EnterCreate nested blockCtrl/Cmd + KInsert linkCtrl + ClickMulti-select imagesDelete/BackspaceDelete selected blocksAPI Reference
EditorProvider
Wraps your editor and provides the Zustand store context for all editor operations. Based on Zustand for optimal performance.
| Prop | Type | Default | Description |
|---|---|---|---|
| initialContainer | ContainerNode | - | Initial content structure (alternative to initialState) |
| initialState | EditorState | - | Complete initial state including history and metadata |
| onChange | (state: EditorState) => void | - | Callback fired on state changes |
| debug | boolean | false | Enable debug logging |
| children | ReactNode | - | Editor components to render |
Editor
The main editor component that renders the editing interface.
| Prop | Type | Default | Description |
|---|---|---|---|
| readOnly | boolean | false | View-only mode - renders content without editing capabilities |
| onUploadImage | (file: File) => Promise<string> | - | Custom image upload handler - should return the uploaded image URL |
| notionBased | boolean | true | Enable 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
useContainer
Returns the current content container (root node)
useActiveNodeId
Returns the ID of the currently focused block
useSelection
Returns the current text selection information
useBlockNode
Returns a specific node by ID with automatic re-renders
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.