---
title: "Ctrl+J: the newline shortcut agent TUIs don't tell you about"
description: "Enter submits the prompt. Ctrl+J inserts a newline in kiro-cli and Cursor Agent; in the VS Code terminal the same shortcut hides the panel."
date: 2026-09-05
locale: en
tags: [tips]
url: https://escribano.dev/en/blog/016-ctrl-j-terminal-newline/
---
You want a multiline prompt in `kiro-cli` or the `agent` TUI: a list of requirements, a walkthrough of a diff, context that doesn't fit in one sentence. You hit Enter and it submits halfway.

**Ctrl+J** inserts the line in almost any terminal, including tmux. Until you press it inside VS Code's integrated terminal (or a fork like Cursor or Kiro IDE) and the panel disappears.

This post is short on purpose. It complements the [IAG tooling map](/blog/015-spec-driven-development-iag/) with a keyboard detail those CLIs barely document.

## The trick

Enter submits. Ctrl+J inserts the newline.

| Tool | Newline (always) | Others, if the terminal reports them |
|------|------------------|--------------------------------------|
| OpenCode | Shift+Enter / Ctrl+Enter (the app intercepts them) | (none) |
| Kiro CLI | Ctrl+J | Shift+Enter, Alt+Enter; `/editor` opens `$EDITOR` |
| Cursor Agent (`agent`) | Ctrl+J; `\` then Enter | Shift+Enter; Option+Enter after `/setup-terminal` |

Ctrl+J isn't a shortcut Kiro invented. It's the control character terminals have been emitting for decades. **There is no other `Ctrl+letter` that does the same thing**: J is the tenth letter of the Latin alphabet, and Ctrl+J is the only ASCII 10 (LF) byte. Ctrl+M is CR (the Enter that submits). Ctrl+I is Tab. There is no “Ctrl+K but for newline”.

What *does* exist are **other combinations the TUI treats as “new line”** without emitting LF:

| Combo | What goes down the wire | Works out of the box in |
|-------|-------------------------|-------------------------|
| Ctrl+J | LF (`\n`, ASCII 10) | Any real terminal; in VS Code the workbench keeps it |
| `\` + Enter | backslash then CR; the TUI turns it into a newline | Cursor Agent, including tmux and the VS Code terminal |
| Shift+Enter | a modified-key sequence (not LF) | iTerm2, Ghostty, Kitty, Warp, Zed |
| Alt+Enter / Option+Enter | usually ESC+CR or another sequence | Terminal.app, Ghostty; VS Code only after setup |

Shift+Enter and Alt+Enter **are not Ctrl+J under another name**. They depend on the emulator reporting the modifier. In gnome-terminal, in many IDEs, and inside tmux, Shift+Enter arrives as a plain Enter and submits the prompt. That's why Cursor and Kiro docs call Ctrl+J (and, in Agent, `\`+Enter) the universal option.

Kiro documents `/settings terminal` to auto-configure Shift+Enter. Cursor documents `/setup-terminal`. Both end up writing an emulator keymap (in VS Code, a `sendSequence`) so that combo stops being a disguised Enter.

## Why it works

`Ctrl+letter` isn't OS magic. In the teletype / VT100 model, **Ctrl plus a letter produces the byte `letter & 0x1F`**: the matching control character.

| Shortcut | Character | ASCII | Equivalent |
|----------|-----------|-------|------------|
| Ctrl+J | LF | 10 | `\n` |
| Ctrl+M | CR | 13 | Enter |
| Ctrl+I | HT | 9 | Tab |
| Ctrl+[ | ESC | 27 | Escape |
| Ctrl+D | EOT | 4 | EOF / close input |
| Ctrl+L | FF | 12 | Clear screen (often) |
| Ctrl+H | BS | 8 | Backspace (sometimes) |

Enter is **CR** (Ctrl+M). Ctrl+J is **LF**. Kiro and Agent, in the TUI, treat LF as newline: if the editor eats Ctrl+J first, the input never sees it. OpenCode intercepts Shift+Enter at the application layer and doesn't depend on that byte.

There are three layers, and any of them can keep the event:

```
Keyboard → terminal (bytes) → TUI (interpretation)
```

In a real emulator (WezTerm, Alacritty, iTerm2, GNOME Terminal) the three layers cooperate and Ctrl+J is LF. In an IDE's integrated terminal the middle layer is the workbench: it decides whether the shortcut is its own or belongs to the process inside.

Linux and macOS behave the same in a real terminal. On Windows, Windows Terminal plus WSL, Git Bash, or SSH do too. PowerShell, cmd, and any GUI text field **do not** follow these rules: Ctrl+J is not LF there.

## The clash in VS Code (and forks)

In Visual Studio Code, **Ctrl+J** (Cmd+J on macOS) is bound to `workbench.action.togglePanel`: show or hide the bottom panel. Cursor, Kiro IDE, and the rest of the forks inherit that keymap. If you run `kiro-cli` or `agent` **inside** that terminal, Ctrl+J never reaches the TUI. It reaches the workbench. The panel collapses.

On macOS the panel shortcut is usually **Cmd+J**, so **Ctrl+J may already work** in the integrated terminal. The ugly collision is Linux and Windows, where Ctrl+J *is* the panel toggle.

There is no second factory `Ctrl+…` that inserts LF and still uses Ctrl+J for the panel. In **Cursor Agent** you don't need one: type `\` and press Enter. I've checked it in Cursor's integrated terminal: it inserts the line, doesn't hide the panel, and doesn't touch the keymap. (Agent's docs also list it as working in tmux and over SSH.) In **Kiro**, without remapping, you're left with `/editor` (opens `$EDITOR`) or pasting text that's already multiline.

If you want Shift+Enter in that terminal, it isn't free: you have to configure the IDE (`/setup-terminal` in Agent, `/settings terminal` in Kiro), which ends in a `sendSequence`. The rest of this section is that: give Ctrl+J's LF back to the TUI, or send byte 10 with another key.

### Let Ctrl+J reach the TUI

VS Code intercepts a list of commands when the terminal has focus (`terminal.integrated.commandsToSkipShell`). `workbench.action.togglePanel` is on that list. Remove it, and Ctrl+J is LF again for the process:

```json
{
  "terminal.integrated.commandsToSkipShell": [
    "-workbench.action.togglePanel"
  ]
}
```

The panel still hides with ``Ctrl+` `` (the usual terminal shortcut). You only give up Ctrl+J while the cursor is in the terminal.

### Send LF with another key

If you don't want to touch the toggle, send the byte yourself. `\u000a` is LF (the same 10 as Ctrl+J). **Don't use `\u000d`**: that's CR, the Enter that submits the prompt.

```json
{
  "key": "ctrl+enter",
  "command": "workbench.action.terminal.sendSequence",
  "args": { "text": "\u000a" },
  "when": "terminalFocus"
}
```

Pick a combo the IDE isn't already using. Ctrl+Enter is sometimes taken; Alt+J or Ctrl+Shift+J are usually free. Same recipe in Cursor and Kiro IDE: the command has the same name.

You can also rebind Ctrl+J **only when the terminal is focused** to that `sendSequence`. Then Ctrl+J in the editor still folds the panel, and in the terminal it inserts a line. It's still a keymap change; it's the most precise one if you live in the IDE.

### Don't fight the IDE

If `\`+Enter isn't enough or you're on Kiro: open the TUI in WezTerm, Alacritty, or whichever emulator you already use. There Ctrl+J is LF and there's no panel to hide. Pasting multiline text from the clipboard also works in almost every TUI.

The agent TUI and Cursor's sidebar chat are not the same surface. This clash only shows up when the agent runs **in the integrated terminal**.

## Closing

Enter submits because it's CR. Ctrl+J inserts a line because it's LF: there is no equivalent `Ctrl+letter`. Shift+Enter and Alt+Enter are TUI shortcuts, not terminal ones, and they fail where the emulator doesn't report the modifier. In Agent, `\`+Enter fills that gap without fighting VS Code. Everywhere else, either you give LF back to the TUI, or you write the prompt where the editor doesn't steal the keyboard.
