> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/pingdotgg/t3code/llms.txt
> Use this file to discover all available pages before exploring further.

# Keybindings

> Customize keyboard shortcuts in T3 Code

T3 Code supports fully customizable keyboard shortcuts through a JSON configuration file. You can override default keybindings or add new ones for project scripts.

## Configuration File

Keybindings are configured in:

```
~/.t3/keybindings.json
```

<Note>
  If you've set a custom `T3CODE_STATE_DIR`, the keybindings file will be located at `$T3CODE_STATE_DIR/keybindings.json`.
</Note>

## File Format

The configuration file must be a JSON array of keybinding rules:

```json keybindings.json theme={null}
[
  { "key": "mod+j", "command": "terminal.toggle" },
  { "key": "mod+d", "command": "terminal.split", "when": "terminalFocus" },
  { "key": "mod+n", "command": "chat.new", "when": "!terminalFocus" }
]
```

## Rule Schema

Each keybinding rule has the following structure:

<ParamField path="key" type="string" required>
  The keyboard shortcut string.

  Format: `modifier+modifier+key`

  Examples:

  * `mod+j`
  * `ctrl+shift+k`
  * `cmd+alt+d`
</ParamField>

<ParamField path="command" type="string" required>
  The command to execute when the shortcut is pressed.

  See [Available Commands](#available-commands) below.
</ParamField>

<ParamField path="when" type="string">
  Optional boolean expression that determines when the shortcut is active.

  See [When Conditions](#when-conditions) below.
</ParamField>

## Available Commands

### Terminal Commands

<AccordionGroup>
  <Accordion title="terminal.toggle">
    Open or close the terminal drawer.

    ```json theme={null}
    { "key": "mod+j", "command": "terminal.toggle" }
    ```
  </Accordion>

  <Accordion title="terminal.split">
    Split the current terminal into a new pane.

    Best used with `when: "terminalFocus"` to only activate when terminal is focused.

    ```json theme={null}
    { "key": "mod+d", "command": "terminal.split", "when": "terminalFocus" }
    ```
  </Accordion>

  <Accordion title="terminal.new">
    Create a new terminal tab.

    Best used with `when: "terminalFocus"` to avoid conflicts.

    ```json theme={null}
    { "key": "mod+t", "command": "terminal.new", "when": "terminalFocus" }
    ```
  </Accordion>

  <Accordion title="terminal.close">
    Close or kill the focused terminal.

    Best used with `when: "terminalFocus"` to only activate when terminal is focused.

    ```json theme={null}
    { "key": "mod+w", "command": "terminal.close", "when": "terminalFocus" }
    ```
  </Accordion>
</AccordionGroup>

### Chat Commands

<AccordionGroup>
  <Accordion title="chat.new">
    Create a new chat thread that preserves the active thread's branch and worktree state.

    Best used with `when: "!terminalFocus"` to avoid conflicts with terminal commands.

    ```json theme={null}
    { "key": "mod+n", "command": "chat.new", "when": "!terminalFocus" }
    ```
  </Accordion>

  <Accordion title="chat.newLocal">
    Create a new local chat thread for the active project without worktree context.

    ```json theme={null}
    { "key": "mod+shift+n", "command": "chat.newLocal", "when": "!terminalFocus" }
    ```
  </Accordion>
</AccordionGroup>

### Editor Commands

<AccordionGroup>
  <Accordion title="editor.openFavorite">
    Open the current project or worktree in your last-used external editor.

    ```json theme={null}
    { "key": "mod+o", "command": "editor.openFavorite" }
    ```
  </Accordion>
</AccordionGroup>

### Diff Commands

<AccordionGroup>
  <Accordion title="diff.toggle">
    Toggle the diff viewer.

    ```json theme={null}
    { "key": "mod+d", "command": "diff.toggle", "when": "!terminalFocus" }
    ```
  </Accordion>
</AccordionGroup>

### Project Script Commands

<AccordionGroup>
  <Accordion title="script.{id}.run">
    Run a project script by its ID.

    The script ID must match a script configured in your project settings. The ID must:

    * Be 1-24 characters long
    * Start with a lowercase letter or digit
    * Contain only lowercase letters, digits, and hyphens

    ```json theme={null}
    [
      { "key": "mod+shift+t", "command": "script.test.run" },
      { "key": "mod+shift+b", "command": "script.build.run" },
      { "key": "mod+shift+l", "command": "script.lint.run" }
    ]
    ```
  </Accordion>
</AccordionGroup>

## Key Syntax

### Modifiers

T3 Code supports the following modifier keys:

<ParamField path="mod" type="modifier">
  Platform-specific modifier:

  * `Cmd` on macOS
  * `Ctrl` on Windows/Linux

  This is the recommended modifier for cross-platform shortcuts.
</ParamField>

<ParamField path="cmd" type="modifier">
  Command key (⌘) on macOS, ignored on other platforms.

  Alias: `meta`
</ParamField>

<ParamField path="ctrl" type="modifier">
  Control key on all platforms.

  Alias: `control`
</ParamField>

<ParamField path="shift" type="modifier">
  Shift key on all platforms.
</ParamField>

<ParamField path="alt" type="modifier">
  Alt key (⌥ on macOS) on all platforms.

  Alias: `option`
</ParamField>

### Special Keys

<CodeGroup>
  ```json Letters and Numbers theme={null}
  { "key": "mod+a", "command": "..." }
  { "key": "mod+1", "command": "..." }
  ```

  ```json Function Keys theme={null}
  { "key": "f1", "command": "..." }
  { "key": "f12", "command": "..." }
  ```

  ```json Navigation theme={null}
  { "key": "mod+left", "command": "..." }
  { "key": "mod+right", "command": "..." }
  { "key": "mod+up", "command": "..." }
  { "key": "mod+down", "command": "..." }
  ```

  ```json Special Characters theme={null}
  { "key": "mod+space", "command": "..." }
  { "key": "escape", "command": "..." }
  { "key": "mod+plus", "command": "..." }
  ```
</CodeGroup>

<Note>
  To bind the `+` key itself, use a trailing `+` in your shortcut: `"mod+"` (note the empty modifier after the plus).
</Note>

## When Conditions

The `when` field accepts boolean expressions to control when a keybinding is active.

### Context Keys

<ParamField path="terminalFocus" type="boolean">
  True when a terminal pane has keyboard focus.
</ParamField>

<ParamField path="terminalOpen" type="boolean">
  True when the terminal drawer is open.
</ParamField>

<Note>
  Unknown context keys evaluate to `false`.
</Note>

### Operators

<CodeGroup>
  ```json NOT (!) theme={null}
  {
    "key": "mod+n",
    "command": "chat.new",
    "when": "!terminalFocus"
  }
  ```

  ```json AND (&&) theme={null}
  {
    "key": "mod+k",
    "command": "terminal.close",
    "when": "terminalOpen && terminalFocus"
  }
  ```

  ```json OR (||) theme={null}
  {
    "key": "escape",
    "command": "terminal.toggle",
    "when": "terminalOpen || terminalFocus"
  }
  ```

  ```json Parentheses theme={null}
  {
    "key": "mod+d",
    "command": "diff.toggle",
    "when": "!terminalFocus && (terminalOpen || !terminalOpen)"
  }
  ```
</CodeGroup>

## Precedence Rules

When multiple keybinding rules match a keyboard event:

1. **Rules are evaluated in array order** (top to bottom)
2. **The last matching rule wins** where both `key` matches AND `when` evaluates to `true`
3. **Precedence is across all commands**, not just within the same command

Example:

```json theme={null}
[
  { "key": "mod+n", "command": "terminal.new", "when": "terminalFocus" },
  { "key": "mod+n", "command": "chat.new", "when": "!terminalFocus" }
]
```

In this example:

* `mod+n` creates a new terminal when terminal is focused
* `mod+n` creates a new chat when terminal is not focused

## Default Keybindings

T3 Code ships with these default keybindings:

```json Default Keybindings theme={null}
[
  { "key": "mod+j", "command": "terminal.toggle" },
  { "key": "mod+d", "command": "terminal.split", "when": "terminalFocus" },
  { "key": "mod+n", "command": "terminal.new", "when": "terminalFocus" },
  { "key": "mod+w", "command": "terminal.close", "when": "terminalFocus" },
  { "key": "mod+d", "command": "diff.toggle", "when": "!terminalFocus" },
  { "key": "mod+n", "command": "chat.new", "when": "!terminalFocus" },
  { "key": "mod+shift+o", "command": "chat.new", "when": "!terminalFocus" },
  { "key": "mod+shift+n", "command": "chat.newLocal", "when": "!terminalFocus" },
  { "key": "mod+o", "command": "editor.openFavorite" }
]
```

<Note>
  When you override a command in your custom configuration, the default keybinding for that command is automatically removed.
</Note>

## Configuration Validation

T3 Code validates your keybindings configuration:

* **Invalid rules are ignored** - The server logs warnings for invalid entries
* **Invalid files are ignored** - If the entire file is malformed, defaults are used
* **Maximum 256 keybindings** - Exceeding the limit truncates oldest entries

### Common Validation Errors

<AccordionGroup>
  <Accordion title="Invalid key syntax">
    ```json theme={null}
    // ❌ Invalid - empty modifiers
    { "key": "mod++", "command": "terminal.toggle" }

    // ✅ Valid - trailing plus for + key
    { "key": "mod+", "command": "terminal.toggle" }
    ```
  </Accordion>

  <Accordion title="Invalid command format">
    ```json theme={null}
    // ❌ Invalid - unknown command
    { "key": "mod+x", "command": "unknown.command" }

    // ❌ Invalid - script ID too long
    { "key": "mod+s", "command": "script.very-long-script-id-exceeding-limit.run" }

    // ✅ Valid
    { "key": "mod+t", "command": "script.test.run" }
    ```
  </Accordion>

  <Accordion title="Invalid when expression">
    ```json theme={null}
    // ❌ Invalid - syntax error
    { "key": "mod+k", "command": "terminal.close", "when": "terminalFocus &&" }

    // ✅ Valid
    { "key": "mod+k", "command": "terminal.close", "when": "terminalFocus" }
    ```
  </Accordion>
</AccordionGroup>

## Live Reloading

Keybindings are watched for changes:

1. Edit `~/.t3/keybindings.json`
2. Save the file
3. Changes are automatically applied
4. Invalid changes are logged to the server console

<Note>
  No server restart required! Changes take effect immediately.
</Note>

## Example Configurations

### VS Code Style

```json VS Code-like keybindings theme={null}
[
  { "key": "ctrl+`", "command": "terminal.toggle" },
  { "key": "ctrl+shift+5", "command": "terminal.split", "when": "terminalFocus" },
  { "key": "ctrl+shift+`", "command": "terminal.new", "when": "terminalFocus" },
  { "key": "ctrl+k", "command": "chat.new", "when": "!terminalFocus" },
  { "key": "ctrl+p", "command": "editor.openFavorite" }
]
```

### Vim-inspired

```json Vim-style keybindings theme={null}
[
  { "key": "mod+shift+;", "command": "terminal.toggle" },
  { "key": "mod+shift+s", "command": "terminal.split", "when": "terminalFocus" },
  { "key": "mod+shift+n", "command": "chat.new", "when": "!terminalFocus" },
  { "key": "mod+shift+o", "command": "editor.openFavorite" }
]
```

### Project Scripts

```json Development workflow theme={null}
[
  { "key": "mod+shift+t", "command": "script.test.run" },
  { "key": "mod+shift+b", "command": "script.build.run" },
  { "key": "mod+shift+l", "command": "script.lint.run" },
  { "key": "mod+shift+d", "command": "script.dev.run" },
  { "key": "f5", "command": "script.debug.run" }
]
```

## Troubleshooting

### Keybinding Not Working

1. Check the server console for validation errors
2. Verify the command name is correct
3. Check if another rule with higher precedence is matching
4. Ensure the `when` condition is met

### File Not Being Watched

```bash theme={null}
# Check file location
echo ~/.t3/keybindings.json

# Verify state directory
# If you set T3CODE_STATE_DIR, use that instead
ls -la ~/.t3/

# Check file permissions
chmod 644 ~/.t3/keybindings.json
```

### Reset to Defaults

```bash theme={null}
# Delete your custom keybindings
rm ~/.t3/keybindings.json

# Restart the server
# Defaults will be automatically restored
```

## Limits and Constraints

<ResponseField name="Maximum keybindings" type="256">
  Configuration is truncated to the last 256 entries if exceeded.
</ResponseField>

<ResponseField name="Maximum key length" type="64 characters">
  Key strings longer than 64 characters are rejected.
</ResponseField>

<ResponseField name="Maximum when length" type="256 characters">
  When expressions longer than 256 characters are rejected.
</ResponseField>

<ResponseField name="Maximum expression depth" type="64 levels">
  When expressions deeper than 64 nested levels are rejected.
</ResponseField>

<ResponseField name="Maximum script ID length" type="24 characters">
  Script IDs in `script.{id}.run` commands must be 24 characters or less.
</ResponseField>

## Related

<CardGroup cols={2}>
  <Card title="Project Scripts" icon="play" href="/configuration/project-scripts">
    Configure project scripts for keybindings
  </Card>

  <Card title="Server Options" icon="terminal" href="/configuration/server-options">
    Server configuration and CLI flags
  </Card>
</CardGroup>
