Rich Text Editor for Comment Sections
Give your users bold, italic, links, and lists in their comments — without a 400KB editor weighing down every page. Scribe's floating toolbar activates only on selection, keeping the comment UX clean and the bundle lean.
Built for comment UX
Inline-first by design
Scribe activates directly on any element. A floating toolbar appears only on selection — invisible until needed, clean comment UX.
Minimal footprint
Comment sections load on every page. At 50KB, Scribe adds almost no overhead. A 400KB editor in a comment box is overkill.
Lazy initialization
Initialize Scribe only when the user clicks "Reply" or focuses the comment box. Zero cost until the editor is actually needed.
XSS-safe by default
User-generated comment content is sanitized automatically. Bold and italic are fine — script tags and event handlers are stripped.
Basic Comment Box
The floating toolbar only shows on text selection — no clutter until the user needs formatting.
import { Scribe } from 'scribejs-editor';
import 'scribejs-editor/dist/scribe.css';
// Inline comment box — activates when user clicks
const editor = Scribe.init('#comment-box', {
toolbar: 'floating', // appears only on text selection
placeholder: 'Write a comment...',
minHeight: 60,
});
// Submit handler
document.querySelector('#submit-btn').addEventListener('click', async () => {
const html = editor.getHTML();
if (!html || editor.isEmpty()) return;
await fetch('/api/comments', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: html, postId }),
});
editor.setHTML(''); // clear after submit
editor.blur();
});React Component
A complete comment box React component with empty-state detection and loading state.
import { useRef, useEffect, useState } from 'react';
import { Scribe } from 'scribejs-editor';
export function CommentBox({ postId, onSubmit }) {
const containerRef = useRef(null);
const editorRef = useRef(null);
const [isEmpty, setIsEmpty] = useState(true);
const [loading, setLoading] = useState(false);
useEffect(() => {
editorRef.current = Scribe.init(containerRef.current, {
toolbar: 'floating',
placeholder: 'Add a comment...',
onChange: (html) => setIsEmpty(!html || html === '<p></p>'),
});
return () => editorRef.current?.destroy();
}, []);
const handleSubmit = async () => {
const html = editorRef.current?.getHTML();
if (!html) return;
setLoading(true);
try {
await onSubmit(html);
editorRef.current?.setHTML('');
setIsEmpty(true);
} finally {
setLoading(false);
}
};
return (
<div className="comment-box border rounded-lg p-3">
<div ref={containerRef} />
<div className="flex justify-end mt-2">
<button
onClick={handleSubmit}
disabled={isEmpty || loading}
className="btn-primary"
>
{loading ? 'Posting...' : 'Post Comment'}
</button>
</div>
</div>
);
}Threaded Replies
Lazy-initialize a reply editor only when the user clicks "Reply" — zero cost until actually needed.
import { Scribe } from 'scribejs-editor';
// Reply editor for threaded comments
// Initialize lazily when user clicks "Reply"
let replyEditor = null;
function openReply(commentId) {
const container = document.querySelector(`#reply-${commentId}`);
container.style.display = 'block';
if (!replyEditor) {
replyEditor = Scribe.init(container.querySelector('.editor'), {
toolbar: 'floating',
placeholder: 'Write a reply...',
autofocus: true,
});
} else {
replyEditor.focus();
}
}
function closeReply(commentId) {
document.querySelector(`#reply-${commentId}`).style.display = 'none';
replyEditor?.setHTML('');
}
async function submitReply(commentId) {
const html = replyEditor?.getHTML();
if (!html || replyEditor?.isEmpty()) return;
await fetch('/api/comments', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: html, parentId: commentId }),
});
closeReply(commentId);
refreshThread(commentId); // re-render replies
}Comment editor feature set
Rich comments without the bloat.
50KB on the wire. Floating toolbar. XSS-safe content. Everything your comment section needs.
Comment editor — common questions
How do I add rich text formatting to a comment section?
Use Scribe Editor with toolbar: 'floating'. When a user selects text in the comment box, a floating toolbar appears with bold, italic, link, and other formatting options. Call Scribe.init('#comment-box') on your comment element and submit the HTML from editor.getHTML() to your API.
Is Scribe Editor suitable for user-generated content in comments?
Yes. Scribe Editor has built-in XSS sanitization that automatically removes unsafe HTML tags, event handlers, and dangerous URL schemes from all content — including pasted text. This makes it safe to store and render user-generated comment content without additional sanitization libraries.
Can I initialize the comment editor lazily (only when the user clicks)?
Yes. Call Scribe.init() only when the user clicks 'Reply' or focuses the comment input. This means the editor has zero initialization cost on pages where users don't interact with comments, keeping page performance optimal.
Does Scribe support threaded replies in comment sections?
Yes. You can initialize one or multiple Scribe instances for nested comment reply boxes. Scribe's small footprint (50KB) makes it practical to have multiple editor instances on a single page without significant performance impact.
Also compare Scribe with:
Use Scribe in your framework:
Popular use cases: