Rich Text Chat & Messaging Input
Add a compact messaging input with Scribe: floating toolbar, send-on-Enter wired via a keydown handler, message HTML via getHTML(), and XSS-safe rendering of received messages — all under 50KB with zero dependencies.
Everything a chat input needs
Compact, minimal input
A floating toolbar keeps the chat input small until the user selects text. Inline formatting via editor.bold() and editor.italic() — no bulky chrome in a single-line-ish input.
Send on Enter
Wire a keydown handler: Enter sends the message and clears the input via setHTML(""), while Shift+Enter inserts a newline. Pull the message HTML with getHTML().
XSS-safe rendering
Messages from other users are untrusted HTML. Scribe sanitizes HTML it processes — strip <script>, inline event handlers, and dangerous markup before rendering received messages into the DOM.
Custom plugins for extras
Scribe has a plugin architecture. TODO(verify): @mentions are not confirmed as a built-in feature — implement mention autocomplete as a custom plugin rather than assuming it ships by default.
Compact Input with Send-on-Enter
Wire a keydown handler so Enter sends and Shift+Enter inserts a newline, then read the message with getHTML().
import { Scribe } from 'scribejs-editor';
import 'scribejs-editor/dist/scribe.css';
// A compact chat input: a floating toolbar keeps the surface minimal
const editor = Scribe.init('#chat-input', {
toolbar: 'floating',
autofocus: true,
placeholder: 'Type a message...',
});
// Send on Enter, newline on Shift+Enter
const inputEl = document.querySelector('#chat-input');
inputEl.addEventListener('keydown', (e) => {
if (e.key !== 'Enter' || e.shiftKey) return; // Shift+Enter -> newline
e.preventDefault();
send();
});
function send() {
if (editor.isEmpty()) return;
const html = editor.getHTML(); // sanitized HTML for the message payload
socket.emit('message', { html });
editor.setHTML(''); // clear the input after sending
}
document.querySelector('#send-btn').addEventListener('click', send);
// Inline formatting from compact toolbar buttons
document.querySelector('#bold').addEventListener('click', () => editor.bold());
document.querySelector('#italic').addEventListener('click', () => editor.italic());XSS-Safe Rendering of Received Messages
Incoming message HTML is untrusted. Run it through Scribe's sanitization before inserting it into the DOM.
import { Scribe } from 'scribejs-editor';
// Incoming messages arrive as HTML from other users — never trust it raw.
// Run it back through Scribe's sanitizer before rendering into the DOM.
function renderIncoming(rawHtml, containerEl) {
// Scribe sanitizes HTML it sets; using a throwaway instance gives you
// the same XSS-safe cleaning pipeline for received content.
const sanitizer = Scribe.init(document.createElement('div'));
sanitizer.setHTML(rawHtml); // strips <script>, on* handlers, etc.
const safeHtml = sanitizer.getHTML(); // clean, safe markup
sanitizer.destroy();
const bubble = document.createElement('div');
bubble.className = 'message-bubble';
bubble.innerHTML = safeHtml; // safe: already sanitized by Scribe
containerEl.appendChild(bubble);
}
socket.on('message', (msg) => {
renderIncoming(msg.html, document.querySelector('#messages'));
});
// TODO(verify): @mentions are NOT a confirmed built-in Scribe feature.
// If you want @mention autocomplete, build it as a custom Scribe plugin
// that listens for keydown, shows a picker, and inserts a mention node.
// Do not assume mentions ship by default.React Chat Input Component
A complete React component with send-on-Enter, Shift+Enter newline, and input clearing.
import { useRef, useEffect, useCallback } from 'react';
import { Scribe } from 'scribejs-editor';
export function ChatInput({ onSend }) {
const containerRef = useRef(null);
const editorRef = useRef(null);
useEffect(() => {
const el = containerRef.current;
editorRef.current = Scribe.init(el, {
toolbar: 'floating',
autofocus: true,
placeholder: 'Type a message...',
});
// Send on Enter; Shift+Enter inserts a newline
const onKeyDown = (e) => {
if (e.key !== 'Enter' || e.shiftKey) return;
e.preventDefault();
submit();
};
el.addEventListener('keydown', onKeyDown);
return () => {
el.removeEventListener('keydown', onKeyDown);
editorRef.current?.destroy();
};
}, []);
const submit = useCallback(() => {
const editor = editorRef.current;
if (!editor || editor.isEmpty()) return;
onSend(editor.getHTML()); // sanitized HTML message
editor.setHTML('');
}, [onSend]);
return (
<div className="chat-input-row">
<div ref={containerRef} className="chat-input" />
<button onClick={submit}>Send</button>
</div>
);
}Everything a messaging input expects
Ship your chat input today.
Compact input, send-on-Enter, XSS-safe rendering. Everything in 50KB — free under MIT.
Chat & messaging input — common questions
How do I build a rich text chat input in JavaScript?
Initialize Scribe Editor with toolbar: 'floating' for a compact input, add a keydown handler so Enter sends and Shift+Enter inserts a newline, and read the message with editor.getHTML(). Clear the input after sending with editor.setHTML(''). Scribe is under 50KB gzipped with zero dependencies and works with React, Vue 3, Svelte, and Vanilla JS.
How do I wire send-on-Enter with Shift+Enter for a newline?
Add a keydown listener on the editor element. If e.key is 'Enter' and e.shiftKey is false, call e.preventDefault() and send the message; otherwise let the default behavior insert a newline. On send, grab editor.getHTML() and then call editor.setHTML('') to clear the input.
How do I render received chat messages safely?
Treat incoming message HTML as untrusted. Scribe sanitizes the HTML it processes, so you can run received content through its sanitization (for example by setting it on an instance and reading getHTML()) to strip script tags and inline event handlers before inserting the message into the DOM. This gives XSS-safe rendering of user messages.
Does Scribe Editor support @mentions out of the box?
TODO(verify): @mentions are not a confirmed built-in Scribe feature. Scribe does provide a plugin architecture, so the recommended approach is to build mention autocomplete as a custom plugin — listen for the trigger character on keydown, show a picker, and insert a mention node. Do not assume mentions ship by default.
Also compare Scribe with:
Use Scribe in your framework:
Popular use cases: