composed · Composed pattern
RichTextEditor
@devalok/shilp-sutra/composed/rich-text-editorView in Storybook Hand-curated previews ship in rolling waves. See it live in Storybook →
Reference
Exports: RichTextEditor, RichTextViewer
Props
RichTextEditor
content: string (HTML string)
placeholder: string (default: "Start writing...")
onChange: (html: string) => void
className: string
editable: boolean (default: true)
onImageUpload?: (file: File) => Promise<string> — return URL. If omitted, images paste as base64
onFileUpload?: (file: File) => Promise<{ url: string; name: string; size: number }> — enables file attachments
mentions?: MentionItem[] — static list for @mention autocomplete
onMentionSearch?: (query: string) => Promise<MentionItem[]> — async search, takes precedence over static mentions
toolbar?: ToolbarItem[] — whitelist of toolbar items to show. Omit to show all.
onMentionSelect?: (item: MentionItem) => void — called when a mention is selected
ToolbarItem: 'bold' | 'italic' | 'underline' | 'strike' | 'highlight' | 'h2' | 'h3' | 'blockquote' | 'bulletList' | 'orderedList' | 'taskList' | 'codeBlock' | 'link' | 'image' | 'file' | 'hr' | 'alignLeft' | 'alignCenter' | 'alignRight' | 'emoji' | 'undo' | 'redo'
MentionItem: { id: string; label: string; avatar?: string }
RichTextViewer
content: string (REQUIRED, HTML string)
className: string
Defaults
RichTextEditor: placeholder="Start writing...", editable=true
Example
<RichTextEditor content={html} onChange={setHtml} placeholder="Write your message..." />
<RichTextEditor
content={html}
onChange={setHtml}
mentions={[{ id: '1', label: 'Aarav' }]}
onImageUpload={async (file) => uploadAndReturnUrl(file)}
onFileUpload={async (file) => ({ url: uploadUrl, name: file.name, size: file.size })}
/>
<RichTextViewer content={savedHtml} />
Composability
- Two exports — editor + viewer. RichTextEditor for composition; RichTextViewer for read-only rendering of saved HTML. Both share the same prose styling so round-trip display matches the editor.
- TipTap v3 bundled — no
@tiptap/*install needed. Consumers can't mix in arbitrary TipTap extensions without forking. - Toolbar whitelist via
toolbarprop: Pass an array of ToolbarItem names to show only those buttons. Omit to show all. Dividers auto-collapse between empty groups. - Image upload: Without
onImageUpload, pasted/dropped images are inlined as base64 (HTML bloats fast). Provide the handler to upload and return a URL. - Mentions: Static
mentionsarray OR asynconMentionSearch(which takes precedence). The viewer always renders mentions correctly from saved HTML — no mention props needed on the viewer side. - For chat composition specifically (AI + team chat with streaming / slash commands), use RichChatInput — it's built on the same foundation but pre-configured for chat UX.
- Pairs with MarkdownViewer — many teams use RichTextEditor for compose (WYSIWYG), but render saved content as markdown for simpler serialization. Convert HTML ↔ markdown at the storage boundary.
Gotchas
- Tiptap is bundled — no need to install
@tiptap/*packages separately - Emoji picker requires
@emoji-mart/react+@emoji-mart/datapeers - Images without
onImageUploadare stored as base64 in HTML — large images bloat content - Mention rendering in viewer always works (no mention props needed, just the HTML)
- Features: bold, italic, underline, strikethrough, highlight, headings, blockquote, lists, task lists, code, links, images, file attachments, mentions, emoji, text alignment, horizontal rule
Changes
v0.33.0
- Added
emojiSetprop —EmojiSettype for consistent emoji art style rendering - Changed TipTap v2 → v3 upgrade (useEditorState, immediatelyRender: false for SSR, ListKit)
- Added EmojiNode + createEmojiSuggestion(set) registered internally
v0.30.0
- Added
toolbarprop — whitelist ofToolbarItemnames to control which toolbar buttons appear. Dividers render only between groups that have visible items.ToolbarItemtype exported from barrel.
v0.18.0
- Fixed Use ref to track internal changes, prevent update loop
v0.9.0
- Changed All
@tiptap/*packages moved from peerDependencies to bundled build-time dependencies — consumers no longer need to install tiptap separately
v0.8.0
- Fixed Emoji picker now renders above the editor (not clipped by overflow)
- Fixed Link/image URL injection prevented via protocol validation
- Fixed Escape key in emoji picker no longer closes parent dialogs
- Fixed Tiptap peer deps tightened to
>=2.27.2 <3.0.0
v0.7.0
- Added Initial release — full-featured tiptap-based rich text editing with toolbar, mentions, emoji, image, alignment
v0.1.1
- Fixed Added content sync effect so editor updates when
contentprop changes externally