Skip to main content

How to Send a Message with Enter and Add a New Line with Shift+Enter in React

· 5 min read
Jagdish Kumawat
Founder @ Dewiride

Chat boxes have a convention: Enter sends the message, Shift+Enter starts a new line. A <textarea> does the opposite by default — Enter adds a line, and nothing sends. One onKeyDown handler swaps the two, and one more check keeps it from interrupting people who type with an input method, as for Japanese or Chinese. This guide builds the box and tests it in a real browser.

What Has to Happen​

A <textarea> inside a <form> never submits the form when you press Enter; it inserts a line break. To make Enter send, the handler has to stop that line break and submit the form itself — but only when Shift isn't held, and only when the keypress isn't part of composing characters.

That last condition matters for people who type with an input method editor (IME): they type sounds, pick characters from a list, and press Enter to confirm the choice. That Enter must not send the message. While a choice is being composed, the browser marks keyboard events with isComposing.

Prerequisites​

  • A React app. The examples use React 19.3.1 in a Next.js 16.4.6 app.

Step 1: Handle the Key​

app/composer/page.tsx
// Enter sends the message. Shift+Enter starts a new line.
function onKeyDown(event: KeyboardEvent<HTMLTextAreaElement>) {
if (
event.key === "Enter" &&
!event.shiftKey &&
!event.nativeEvent.isComposing
) {
event.preventDefault();
event.currentTarget.form?.requestSubmit();
}
}
CodeWhat it does
event.key === "Enter"Only the Enter key
!event.shiftKeyShift+Enter is left alone, so the textarea adds a line as usual
!event.nativeEvent.isComposingAn Enter that confirms characters in an input method is left alone too
event.preventDefault()Stops the line break that Enter would add
event.currentTarget.form?.requestSubmit()Submits the textarea's form the same way the Send button does, so the form's onSubmit runs and validation happens as usual

requestSubmit() is the right call here: unlike form.submit(), it fires the submit event that React's onSubmit listens to.

Step 2: Put It in a Form​

The complete page: a list of sent messages, and a form with the textarea and a Send button:

app/composer/page.tsx
"use client";

import { type FormEvent, type KeyboardEvent, useState } from "react";

export default function ComposerPage() {
const [draft, setDraft] = useState("");
const [sent, setSent] = useState<string[]>([]);

function onSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const text = draft.trim();
if (text) {
setSent((current) => [...current, text]);
setDraft("");
}
}

// Enter sends the message. Shift+Enter starts a new line.
function onKeyDown(event: KeyboardEvent<HTMLTextAreaElement>) {
if (
event.key === "Enter" &&
!event.shiftKey &&
!event.nativeEvent.isComposing
) {
event.preventDefault();
event.currentTarget.form?.requestSubmit();
}
}

return (
<main style={{ padding: 48, maxWidth: 560, fontFamily: "system-ui" }}>
<ul style={{ display: "grid", gap: 8, listStyle: "none" }}>
{sent.map((text, index) => (
<li
// biome-ignore lint/suspicious/noArrayIndexKey: messages never move.
key={index}
style={{
whiteSpace: "pre-wrap",
padding: "8px 12px",
borderRadius: 12,
background: "var(--surface)",
}}
>
{text}
</li>
))}
</ul>
<form
onSubmit={onSubmit}
style={{ display: "flex", gap: 8, marginTop: 16 }}
>
<textarea
aria-label="Message"
value={draft}
onChange={(event) => setDraft(event.target.value)}
onKeyDown={onKeyDown}
rows={3}
style={{ flex: 1, font: "inherit", padding: 8 }}
/>
<button type="submit">Send</button>
</form>
</main>
);
}

macOS-style code window titled app/composer/page.tsx showing the onKeyDown handler that checks for Enter without Shift and without isComposing, prevents the default line break and calls requestSubmit on the textarea's form

onSubmit trims the text and ignores empty messages, so Enter in an empty box does nothing. whiteSpace: "pre-wrap" makes the sent message keep its line breaks.

Step 3: Try It​

I drove the page in Chrome with real key presses: Line one, Shift+Enter, Line two. The box held both lines, and nothing was sent:

The composer with an empty message list and the textarea containing two lines, Line one and Line two, before pressing Enter

Then Enter. The message was sent with both lines, and the box was emptied:

The composer after pressing Enter, with the sent message showing Line one and Line two on separate lines above an empty textarea

The sent text was exactly "Line one\nLine two". Pressing Enter in the empty box first sent nothing.

Common Mistakes​

MistakeWhat happensDo this instead
Leaving out the isComposing checkPeople typing Japanese, Chinese or Korean send half-finished messagesSkip Enter while event.nativeEvent.isComposing is true
Calling form.submit()React's onSubmit doesn't run, and the page may reloadform.requestSubmit()
Sending from onKeyDown directly, outside the formEnter and the Send button behave differentlySubmit the form, and keep sending in onSubmit
Showing the message without white-space: pre-wrapThe line breaks disappear when it's displayedAdd pre-wrap to the message's style

Troubleshooting​

SymptomLikely causeFix
Enter still adds a linepreventDefault() isn't called, or the handler isn't on the textareaCheck onKeyDown={onKeyDown} on the <textarea>
Enter reloads the pageonSubmit doesn't call event.preventDefault()Prevent the form's default submit
Shift+Enter sendsThe shiftKey check is missingAdd !event.shiftKey

FAQ​

Why not use an input element?​

An <input> already submits on Enter, but it can't hold several lines. A textarea lets people write longer, structured messages.

Should Ctrl+Enter send instead?​

Some apps use it, so plain Enter can add lines. The convention in chat apps is Enter to send; whichever you pick, tell people with a hint near the box.

Conclusion​

An onKeyDown handler that checks for Enter without Shift and outside an input method, prevents the default line break and calls form.requestSubmit() gives a textarea chat-style keys: Enter sends through your normal onSubmit, Shift+Enter starts a new line, and composing characters still works.

Additional Resources​