React hook to handle chat textarea submit behaviors (Enter vs Mod+Enter) with IME-safe handling.

A small React Hook that brings the chat input behavior you’d expect:
1. Enter to submit. Shift + Enter to insert a line break.
2. Cmd/Ctrl + Enter to submit. Enter to insert a line break (preferred for Japanese input 🇯🇵).
It prevents accidental submissions while using an IME, works seamlessly with your own handlers, and normalizes Cmd vs. Ctrl differences across platforms.
``bash`
pnpm add use-chat-submitor
npm i use-chat-submitor
yarn add use-chat-submit
`tsx
import * as React from "react";
import { useChatSubmit } from "use-chat-submit";
export function ChatBox() {
const [text, setText] = React.useState("");
const { getTextareaProps } =
useChatSubmit({
onSubmit: (value) => {
console.log("submit:", value);
setText(""); // clear input after submit
},
mode: "mod-enter", // switch behavior based on user preference
});
return (
{...getTextareaProps({
value: text,
onChange: (e) => setText(e.target.value),
})}
/>
);
}
`
getTextareaProps() only attaches onKeyDown and ref, so you can freely pass any other props yourself.
`tsx`
// Equivalent behavior
{...getTextareaProps()}
value={text}
onChange={(e) => setText(e.target.value)}
/>
| options.mode | Enter | Shift+Enter | Cmd/Ctrl+Enter |mod-enter
| -------------- | ------- | ----------- | -------------- |
| | Line break | Line break | Submit |enter
| | Submit | Line break | Submit |
- Never submits while an IME is composing (two-step check with KeyboardEvent.isComposing on keydown/keyup).
-
- Safely composes with your handlers (user → library). Respects event.defaultPrevented and event.isPropagationStopped().modKey: "auto"
- Smooths out differences between Safari and Chrome in IME composition handling. Detects reliably using native keydown/keyup events.
- Normalizes Cmd vs. Ctrl with . Also exposes platform-aware shortcut hints for your UI.
#### Options (UseChatSubmitOptions)
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| onSubmit(value, ctx) | (value: string, ctx: { target: HTMLTextAreaElement }) => void | — (required) | Called on submit. Access the underlying textarea via ctx.target. |mode
| | "mod-enter" | "enter" | "mod-enter" | Key mapping behavior for Enter/Shift+Enter/Cmd/Ctrl+Enter. |modKey
| | "meta" | "ctrl" | "auto" | "auto" (recommended) | Which modifier counts as “mod”. Auto = Cmd (⌘) on macOS, Ctrl elsewhere. |allowEmptySubmit
| | boolean | false | Allow submitting an empty string. |stopPropagation
| | boolean | false | Call e.stopPropagation() when submitting. |enabled
| | boolean | "non-mobile" | true | Enable the behavior. "non-mobile" enables only on non‑mobile devices. |shortcutHintLabelStyle
| | "auto" | "symbols" | "text" | "auto" | Style for shortcut hint labels. |userAgentHint
| | string | — | Optional UA string for SSR to reduce detection lag. |
| Property | Type | Description |
| --- | --- | --- |
| getTextareaProps(userProps?) | (userProps?: React.TextareaHTMLAttributes | Safely composes props for
- Handler composition is “user → library”. If the user calls preventDefault(), internal logic does not run."\n"
- Leave line breaks to the browser’s default behavior (no manual insertion).enter
- In mode, preventDefault() repurposes Enter for submit (Shift+Enter still inserts a line break).disabled
- Does not submit when , readOnly, or when the value is empty with allowEmptySubmit=false and value.trim()==="".
- Only
MIT