# How fzf Implements Multi-Select with the -m Option and TAB Key

> Learn how fzf implements multi-select using the -m option and TAB key to efficiently toggle selection state while navigating lists in your terminal.

- Repository: [Junegunn Choi/fzf](https://github.com/junegunn/fzf)
- Tags: internals
- Published: 2026-03-01

---

**fzf enables multi-selection via the `-m` or `--multi` flag, storing selected items in a `map[int32]selectedItem` and binding the TAB key to `actToggleDown` (and Shift-TAB to `actToggleUp`) to toggle selection state while navigating the list.**

The `junegunn/fzf` command-line fuzzy finder supports multi-select mode, allowing users to select multiple items using the **TAB** key when invoked with the `-m` option. This feature is implemented through a combination of command-line flag parsing in [`src/options.go`](https://github.com/junegunn/fzf/blob/main/src/options.go) and state management within the `Terminal` struct in [`src/terminal.go`](https://github.com/junegunn/fzf/blob/main/src/terminal.go). Understanding how fzf handles multi-select reveals the elegant Go patterns used to manage selection state, key bindings, and visual feedback.

## Enabling Multi-Select Mode

Multi-selection is activated by passing the `-m` or `--multi[=MAX]` flag. The default maximum number of selections is `math.MaxInt32`, effectively unlimited for practical purposes.

In **[`src/options.go`](https://github.com/junegunn/fzf/blob/main/src/options.go)** around line 96, the flag parser sets `opts.Multi` to the specified maximum:

```go
// src/options.go ~L96
// Parsing: -m, --multi[=MAX]
opts.Multi = max // int value, default math.MaxInt32

```

When `opts.Multi > 0`, the `Terminal` initialization creates a map to track selections at line 398 in **[`src/terminal.go`](https://github.com/junegunn/fzf/blob/main/src/terminal.go)**:

```go
// src/terminal.go ~L398
selected map[int32]selectedItem   // key: item index, value: selection metadata

```

Each entry stores the item's index and a timestamp recording when it was selected.

## Key Bindings for TAB and Shift-TAB

The default keymap binds the physical **Tab** key to the action `actToggleDown` and **Shift-Tab** to `actToggleUp`. These bindings are established in **[`src/terminal.go`](https://github.com/junegunn/fzf/blob/main/src/terminal.go)** at lines 28-30:

```go
// src/terminal.go ~L28-L30
add(tui.Tab, actToggleDown)      // Tab key
add(tui.ShiftTab, actToggleUp)    // Shift-Tab key

```

Both actions are no-ops unless multi-select is active (`t.multi > 0`), ensuring the keys perform standard completion when `-m` is not used.

## The Toggle Logic

When a user presses **Tab**, the `doAction` method in `Terminal` receives `actToggleDown` and enters the switch case at lines 31-40 in **[`src/terminal.go`](https://github.com/junegunn/fzf/blob/main/src/terminal.go)**:

```go
// src/terminal.go ~L31-L40
case actToggleDown:
    if t.multi > 0 && t.merger.Length() > 0 && toggle() {
        t.vmove(-1, true)   // move cursor up after toggling
        req(reqList)
    }
case actToggleUp:
    if t.multi > 0 && t.merger.Length() > 0 && toggle() {
        t.vmove(1, true)    // move cursor down after toggling
        req(reqList)
    }

```

The `toggle` closure (defined locally around lines 32-38) retrieves the current item and invokes `t.toggleItem`:

```go
// src/terminal.go ~L32-L38 (within handleEvent)
toggle := func() bool {
    current := t.currentItem()
    if current != nil && t.toggleItem(current) {
        req(reqInfo)
        return true
    }
    return false
}

```

### Selecting and Deselecting Items

The `toggleItem` method at lines 56-61 in **[`src/terminal.go`](https://github.com/junegunn/fzf/blob/main/src/terminal.go)** implements the state transition:

```go
// src/terminal.go ~L56-L61
func (t *Terminal) toggleItem(item *Item) bool {
    if _, found := t.selected[item.Index()]; !found {
        return t.selectItem(item)   // not selected → select
    }
    t.deselectItem(item)          // already selected → deselect
    return true
}

```

**`selectItem`** (lines 22-34) enforces the maximum limit (`t.multi`) and adds the item to the map:

```go
// src/terminal.go ~L22-L34
func (t *Terminal) selectItem(item *Item) bool {
    if len(t.selected) >= t.multi {
        return false // max reached
    }
    t.selected[item.Index()] = selectedItem{
        index: item.Index(),
        time:  time.Now(),
    }
    t.version++
    return true
}

```

**`deselectItem`** (lines 43-46) removes the entry:

```go
// src/terminal.go ~L43-L46
func (t *Terminal) deselectItem(item *Item) {
    delete(t.selected, item.Index())
    t.version++
}

```

Both functions increment `t.version` to trigger a UI redraw.

## Visual Feedback

When items are selected, fzf provides immediate visual feedback through markers and status indicators.

### Selection Markers

The **selection marker** (default `>` or `┃`) is configured via `--marker` and `--marker-multi-line`. During rendering at line 3457 in **[`src/terminal.go`](https://github.com/junegunn/fzf/blob/main/src/terminal.go)**, the `selected` map is consulted to apply the marker and highlight color to active lines.

### Status Counter

The status line displays the current selection count in the format `(+3/5)`, indicating three items selected out of a maximum of five. This is assembled at line 2936:

```go
// src/terminal.go ~L2936
fmt.Sprintf(" (%d/%d)", len(t.selected), t.multi)

```

## Summary

- **Activation**: Pass `-m` or `--multi[=MAX]` to enable multi-select; parsed in [`src/options.go`](https://github.com/junegunn/fzf/blob/main/src/options.go) around line 96.
- **State Storage**: Selected items are tracked in a `map[int32]selectedItem` defined at line 398 of [`src/terminal.go`](https://github.com/junegunn/fzf/blob/main/src/terminal.go).
- **Key Bindings**: **TAB** triggers `actToggleDown` and **Shift-TAB** triggers `actToggleUp`, bound at lines 28-30.
- **Toggle Logic**: The `toggleItem` method (lines 56-61) switches between `selectItem` (lines 22-34) and `deselectItem` (lines 43-46), enforcing the `t.multi` limit.
- **Cursor Movement**: After toggling, `vmove(-1)` or `vmove(1)` shifts the cursor to allow rapid selection of adjacent items.
- **UI Feedback**: Selection markers and the `(+count/max)` status indicator are rendered by checking the `selected` map.

## Frequently Asked Questions

### How do I enable multi-select mode in fzf?

Pass the `-m` or `--multi` flag when invoking fzf. You can optionally specify a maximum number of selections with `--multi=MAX` (e.g., `fzf -m5` limits you to five items). This flag is parsed in [`src/options.go`](https://github.com/junegunn/fzf/blob/main/src/options.go) and initializes the selection map in the Terminal struct.

### What is the difference between TAB and Shift-TAB in fzf multi-select?

**TAB** (`actToggleDown`) selects the current item and moves the cursor up one line, while **Shift-TAB** (`actToggleUp`) selects the current item and moves the cursor down one line. This "select and continue" behavior allows rapid selection of multiple adjacent items without changing direction. Both keys call the `toggleItem` method to update the `selected` map.

### How does fzf enforce the maximum selection limit?

When `selectItem` is invoked (around line 22 in [`src/terminal.go`](https://github.com/junegunn/fzf/blob/main/src/terminal.go)), it checks if `len(t.selected) >= t.multi`. If the limit is reached, the function returns `false` and the new item is not added to the `selected` map. The default maximum is `math.MaxInt32`, effectively unlimited for most use cases.

### Where does fzf store the selected items during a session?

Selected items are stored in a `map[int32]selectedItem` named `selected`, defined at line 398 in [`src/terminal.go`](https://github.com/junegunn/fzf/blob/main/src/terminal.go). The map uses the item's index as the key and stores a struct containing the index and selection timestamp. This map is checked during rendering to display selection markers and the status counter showing `(+count/max)`.