@loristianne/react-richtext-editor
v0.2.0
Published
Full-featured rich text editor React component — headless core with optional built-in theme
Downloads
0
Readme
@loristianne/react-richtext-editor
Full-featured rich text editor React component — headless core with an optional built-in theme.
npm install @loristianne/react-richtext-editorQuick start
import { RichTextEditor } from '@loristianne/react-richtext-editor';
import '@loristianne/react-richtext-editor/theme.css'; // optional — omit for headless
function App() {
return (
<RichTextEditor
placeholder="Start writing…"
onChange={({ html, json, markdown }) => {
console.log({ html, json, markdown });
}}
/>
);
}The editor outputs HTML, JSON, and Markdown simultaneously — pick the format that suits your backend.
Features
- Bubble menu — select text to reveal a floating toolbar with Bold, Italic, Underline, Highlight, and Link
- Text formatting — bold, italic, underline, strikethrough, inline code
- Headings — H1 through H6
- Lists — bullet, ordered, and task lists (with checkboxes)
- Blocks — blockquotes, code blocks with syntax highlighting, horizontal rules
- Tables — resizable columns, cell selection
- Images — paste or upload from disk via
onImageUpload - Embeds — YouTube and Vimeo via the toolbar
- Links — inline link insertion with the toolbar
- Mentions — type
@to trigger a suggestion dropdown backed byonMentionQuery - Collaboration — real-time multiplayer via Yjs
- Headless mode — omit the theme CSS and bring your own styles
- Ref API — programmatic control:
setContent,getOutput,clearContent,focus
Props
interface RichTextEditorProps {
// Initial content — HTML string or Tiptap JSONContent
value?: string | JSONContent;
// Which formats to compute on every change (default: "all")
outputFormat?: 'html' | 'json' | 'markdown' | 'all';
// Called on every content change
onChange?: (output: EditorOutput) => void;
// Called when the editor loses focus
onBlur?: () => void;
// Placeholder text shown when the editor is empty
placeholder?: string;
// Set to false to render a read-only viewer (default: true)
editable?: boolean;
// Resolve an uploaded file to a URL; return a data-URL or a CDN URL
onImageUpload?: (file: File) => Promise<string>;
// Resolve @ mention suggestions from a query string
onMentionQuery?: (query: string) => Promise<MentionItem[]> | MentionItem[];
// Append extra Tiptap extensions
extensions?: Extensions;
// Enable real-time collaboration
collaboration?: CollaborationOptions;
// Class name applied to the root wrapper
className?: string;
// Auto-focus on mount
autofocus?: boolean | 'start' | 'end' | 'all' | number;
}Output type
interface EditorOutput {
html: string;
json: JSONContent;
markdown: string;
}MentionItem type
interface MentionItem {
id: string;
label: string;
[key: string]: unknown; // any extra fields are fine
}Ref API
Attach a ref to get programmatic control:
import { useRef } from 'react';
import { RichTextEditor, RichTextEditorRef } from '@loristianne/react-richtext-editor';
const ref = useRef<RichTextEditorRef>(null);
<RichTextEditor ref={ref} />
// Methods
ref.current?.setContent('<p>Hello world</p>');
ref.current?.getOutput(); // { html, json, markdown }
ref.current?.clearContent();
ref.current?.focus();
ref.current?.editor; // raw Tiptap Editor instanceHeadless usage
Skip the built-in component and drive the editor yourself:
import { useRichTextEditor, Toolbar, BubbleMenu } from '@loristianne/react-richtext-editor';
function MyEditor() {
const editor = useRichTextEditor({
content: '<p>Hello</p>',
onChange: ({ html }) => console.log(html),
});
return (
<div>
<Toolbar editor={editor} />
<BubbleMenu editor={editor} />
<EditorContent editor={editor} />
</div>
);
}You can also build your own extension set:
import { buildExtensions } from '@loristianne/react-richtext-editor';
const extensions = buildExtensions({
placeholder: 'Write something…',
onMentionQuery: async (q) => fetchUsers(q),
extraExtensions: [MyCustomExtension],
});Optional theme
Import the stylesheet to get a polished out-of-the-box appearance:
import '@loristianne/react-richtext-editor/theme.css';Omit it entirely to keep the editor unstyled (headless mode) — no CSS is injected automatically.
Customising via CSS variables
The theme exposes a set of custom properties you can override in your own stylesheet:
:root {
--rte-font-sans: system-ui, sans-serif;
--rte-font-mono: monospace;
--rte-font-size: 15px;
--rte-line-height: 1.65;
--rte-color-text: #1a1a1a;
--rte-color-muted: #6b7280;
--rte-color-border: #e5e7eb;
--rte-color-surface: #ffffff;
--rte-color-surface-elevated: #f9fafb;
--rte-color-accent: #2563eb;
--rte-color-accent-hover: #1d4ed8;
--rte-color-accent-light: #eff6ff;
--rte-color-code-bg: #f3f4f6;
--rte-color-code-text: #374151;
--rte-color-blockquote-bg: #f9fafb;
--rte-radius: 6px;
--rte-toolbar-height: 44px;
--rte-editor-min-height: 200px;
--rte-selection-bg: #bfdbfe;
}Real-time collaboration
Pass a Yjs document and an optional provider to enable multiplayer:
import * as Y from 'yjs';
import { WebsocketProvider } from 'y-websocket';
const ydoc = new Y.Doc();
const provider = new WebsocketProvider('wss://my-server', 'room-1', ydoc);
<RichTextEditor
collaboration={{
doc: ydoc,
provider,
user: { name: 'Alice', color: '#2563eb' },
}}
/>Exports
| Export | Description |
|---|---|
| RichTextEditor | Main component (toolbar + bubble menu + content area) |
| Toolbar | Standalone toolbar — use with useRichTextEditor |
| BubbleMenu | Standalone floating menu — use with useRichTextEditor |
| useRichTextEditor / useEditor | Hook that returns a Tiptap Editor instance |
| useRteEditor | Alias for useRichTextEditor |
| buildExtensions | Build the default extension set, optionally extended |
| getEditorOutput | Extract { html, json, markdown } from an Editor |
| VimeoExtension | Tiptap extension for Vimeo embeds |
| toVimeoEmbedUrl | Utility — convert a Vimeo URL to an embed URL |
| Collaboration | Re-exported from @tiptap/extension-collaboration |
Types: RichTextEditorProps, RichTextEditorRef, EditorOutput, MentionItem, OutputFormat, CollaborationOptions, UseRichTextEditorOptions, ToolbarProps, BubbleMenuProps, BuildExtensionsOptions
Requirements
- React 18+
- No CSS-in-JS — just import the optional theme stylesheet
License
MIT
