Use Case

    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.

    < 50KB
    Per page cost
    Lazy
    Init on demand
    XSS-Safe
    User content

    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

    Bold, italic, underline for emphasis
    Links for sharing URLs in comments
    Ordered & unordered lists
    Floating toolbar — only on selection
    isEmpty() check before submit
    setHTML("") to clear after post
    Built-in XSS sanitization
    Lazy init — zero cost until focused
    Multiple instances (threaded replies)
    Placeholder text support
    minHeight / maxHeight config
    MIT license — free forever

    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.