React Development Tips · Next.js Tutorials · Frontend Development

SHIFT + ENTER in React Quill to create a new line 100% working

Sandipan Das
Sandipan Das · full-stack developer, Kolkata, IndiaPublished · Updated · 3 min read
SHIFT + ENTER in React Quill to create a new line 100% working — cover image

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.

Share:X·LinkedIn·Email

Related

  • Article

    ISR in Next.js: The Perfect SEO & Performance Strategy

    ISR in Next.js came out of a project I was working on once — a dynamic blog where content was king. It had great design, engaging posts, and an audience hungry for fresh updates. But something wasn’t right. I noticed…

    · 3 min read · by Sandipan Das
  • Next.js Tutorials

    How I Made My Next.js App Resilient After Vercel's API Fetching Time Limits

    A few months ago I was working on a Next.js project for a client, well before Vercel's API fetching time limits became my problem. Everything was going smoothly — pages were loading fast, data arrived when it was supposed to,…

    · 5 min read · by Sandipan Das
  • Next.js Tutorials

    Fix typescript error: Unexpected any and empty object issues

    To fix typescript error messages on a new project, start here. Two ESLint errors show up on every fresh Next.js and TypeScript project: Unexpected any , and the warning that {} allows any non-nullish value. Both come from next/typescript .…

    · 2 min read · by Sandipan Das

If you'd rather not do it yourself

Everything above works. You just need the time for it.

Follow the steps and fix it yourself. Or send the site to the team and get on with your day.

900+ projects completed since 2017, from Kolkata, India.