React Development Tips · Next.js Tutorials · Frontend Development
SHIFT + ENTER in React Quill to create a new line 100% working

Shift + Enter in React Quill does not give you a plain line break out of the box — press Enter and you get a whole new paragraph instead. This post shows how to rebind it: disable the default Enter binding, add your own for Shift + Enter, and insert a break. Working code below, from an editor I shipped.
The detail to watch is not the binding. It is that a line break rendered from stored HTML collapses unless the container sets white-space. Both halves are in this post.
Before the code, make sure React Quill is installed:
npm install react-quill
# or
yarn add react-quill
Step 1: Import React Quill
Import it dynamically with SSR off — Quill reaches for the DOM as it loads, so rendering it on the server throws:
// = = = = =
// IMPORT REACT QUILL
// = = = = =
const ReactQuill = dynamic(
async () => {
const { default: RQ } = await import("react-quill");
RQ.Quill.register("modules/handleImageUpload", RQ);
return function forwardRef({ forwardedRef, ...props }: { forwardedRef: React.Ref<any>; [key: string]: any }) {
return <RQ ref={forwardedRef} {...props} />;
};
},
{
ssr: false,
loading: () => <p>Loading ...</p>,
}
);
Step 2: Shift + Enter in React Quill — customise the key behaviour
The change lives in Quill's keyboard module. You switch the default Enter binding off and register your own for Shift + Enter:
const PostEditIndex = () => {
// REACT QUILL STEP 2:
const quillRef = useRef<any>(null);
const modules = useMemo(
() => ({
keyboard: {
bindings: {
// Disable the default Enter behavior (inserts a new paragraph)
enter: false,
// Define a custom binding for "Shift + Enter" to insert a line break
customShiftEnter: {
key: 13, // Enter key code
shiftKey: true,
handler: () => {
const quill = quillRef.current.getEditor();
const keyboard = quill.getModule("keyboard");
delete keyboard.bindings[13];
const range = quill.getSelection();
let position = range ? range.index : 0;
quill.insertText(position, "\n", "break"); // create a line break with p tag
return;
},
},
},
},
toolbar: {
container: [
// YOUR REST CONTAINER
["break"],
],
},
clipboard: {
matchVisual: false,
},
}),
[]
);
const formats = ["header", "font", "size", "bold", "italic", "underline", "break"];
What that block does, line by line:
- customShiftEnter is our own binding, registered alongside Quill's built-in ones.
- The handler reaches the Quill instance through the ref and grabs its keyboard module.
- Setting enter to false switches off the default new-paragraph behaviour.
- Shift + Enter now inserts a line break instead.
- The default binding for key code 13 is deleted so the two do not fight.
- Finally the break is inserted at the cursor, and the cursor moves past it.
Note the useMemo. Rebuild that modules object on every render and Quill re-registers the bindings each time, which is how you end up with an editor that gets slower the longer you type.
Step 3: Using the Customised Editor
Now wire it up:
<ReactQuill
defaultValue={.text}
style={styles}
onChange={(value: string) => {
setValue("text", value);
}}
modules={modules}
formats={formats}
forwardedRef={quillRef}
className="text-text"
/>
And this is the part that makes the break show up on the page. Where you print the saved content, the container has to preserve the break:
<div className="" style={{ whiteSpace: "pre-line" }} dangerouslySetInnerHTML={{ __html: text ?? "" }} />
Without it the editor looks right and the published page does not. That one style is the first thing I check when a break goes missing.
Conclusion, and what I would do today
This works, and if you are on React Quill it is the fix. But I will be honest about where I landed: I have since moved my own editor from Quill to Tiptap, because I wanted to extend the editor's model of a document directly rather than rebind keys around it. If you are starting a new project and you can still choose, choose an editor whose document model you can extend properly. If you have an existing Quill editor and just need Shift + Enter to behave, the code above is the shortest path there.
Questions
Questions this post answers
Why does Shift + Enter not create a line break in React Quill?
- Out of the box React Quill has no binding for it — press Enter and you get a whole new paragraph instead. You have to switch the default Enter binding off in Quill's keyboard module and register your own for Shift + Enter, which inserts a break at the cursor.
Why does the line break collapse when I display the saved content?
- A line break rendered from stored HTML collapses unless the container sets white-space. Set it where you print the saved content, or the editor will look right and the published page will not. I lost an evening to that once.
Why wrap the Quill modules object in useMemo?
- Rebuild that object on every render and Quill re-registers the bindings each time, which is how you end up with an editor that gets slower the longer you type.
Why import React Quill dynamically with SSR off?
- Quill reaches for the DOM as it loads, so rendering it on the server throws. Import it dynamically with SSR off and it stays on the client, where it can find a document.


