Pattern
Composer × Actual CSS
A native <textarea> inside an Actual surface, with an actions
row around it. The textarea stays native — form value, IME, undo, mobile,
validation — while the surface owns border, focus, and states.
Switch themes: the composer follows the tokens, including
material.
Project note
The product case: a note with templates, dictation and send.
Enter stays a newline; sending goes through
Ctrl/Cmd+Enter or the send button.
Compact chat input
The same recipe tuned down: one row, smaller floor. The admini chat page uses this composition.
States
The surface reflects the control: aria-invalid turns the
border danger through --form-invalid-border, and a disabled
control quiets the whole surface.
Enter contract
The only JavaScript the composer needs. Plain Enter is a
newline; composition sessions (IME) never send.
input.addEventListener("keydown", (event) => {
if (event.key !== "Enter" || event.isComposing) return;
if (event.ctrlKey || event.metaKey) {
event.preventDefault();
form.requestSubmit();
}
// Plain Enter falls through: the textarea inserts a newline.
});