# Cursor Tab: get multi-line edits right and verify them

URL: https://developertoolkit.ai/en/cursor-ide/build/tab-autocomplete/
Description: Get reliable multi-line edits from Cursor Tab: seed the pattern, accept or reject fast, and let types, tests and lint check what you accepted.

Cursor Tab is the editor's built-in completion model: it predicts the next edit near the cursor, including multi-line changes and jumps to the next place that needs a related change, and the Tab key accepts it. Tab is most reliable when the pattern to continue is already visible, and accepted code still needs a type, lint or test gate.

You press Tab, get a plausible 12-line handler, and accept it. Two days later a reviewer finds that it returns `"User not found"` from the posts endpoint, because Tab copied the pattern faithfully and the one detail that mattered came along with it. Speed was never the problem; knowing which suggestions to trust, and catching the ones you should not have trusted, is.

This page is for **developers** who still type code in Cursor's editor and want Tab to carry the mechanical part of it. Feature-sized work belongs to [Cursor's agent](/en/cursor-ide/build/agent-chat/); Tab is for the edits you would otherwise type yourself.

**Settings and shortcuts can differ in your build**

Written for the Cursor 3.x line as of 2026-09-26. Setting labels and the partial-accept binding move between builds; confirm them in Keyboard Shortcuts and Cursor Settings.

## What you'll walk away with from these Tab tips

- A working model of what Tab predicts, and what that means for where you put your cursor and which files you edit first.
- A two-example seeding technique and an intent-comment recipe that make the first suggestion the right one more often.
- Three copy-paste prompts that hand off from Tab to inline edit and Agent: one to rewrite a scaffold, one to check a Tab-written batch, one to prove Tab-written tests can fail.
- A set of cheap gates (exhaustiveness checks, `satisfies`, watch-mode tests) that catch a wrong suggestion without you rereading every accepted line.
- A failure-mode table for the ways Tab goes wrong, with the recovery for each.

## How does Cursor Tab decide what to suggest?

Tab works from what is near the cursor and from your last edit. The subsections below explain what it predicts, how to put the pattern in its view, and which jobs it suits.

### Tab predicts the next edit, not the next word

A classic autocomplete finishes the token you are typing. Cursor Tab proposes an *edit*: it can rewrite several lines around the cursor, and after you accept one change it can offer a jump to the next location that needs a related change. In practice that means one accepted suggestion is often the first of a short sequence:

- You rename a parameter; Tab offers the same rename at the next usage.
- You add a field to one object literal; Tab offers it in the sibling literal below.
- You add a call to a new helper; Tab offers the import it needs.

Treat each Tab press as accepting one step of a sequence, and check each step as you go. A chain of ten accepted edits is ten decisions, not one.

### Put the pattern where Tab can see it

Tab predicts from the code around the cursor and from the edit you made last. The practical rule follows: **make the pattern visible before you ask Tab to continue it.** Before you write a new endpoint, make sure the closest existing one sits in the same file, ideally directly above where the new code goes, or make the first change by hand so Tab has an edit to continue. A suggestion that ignores your conventions usually means the example it needed was not near the cursor.

For deliberate context selection with the agent (`@`-mentions, rules, indexing), see [context management in Cursor](/en/cursor-ide/quick-start/context-management/). Tab has no prompt box for `@`-mentions (Cursor 3.x line, as of 2026-09-26); you steer it with the code you place near the cursor. Rules apply to Agent only, not to Tab completion (Cursor help, checked 2026-10-12).

### Use Tab for more of the same, not for something new

Tab's strength is continuation. When the next edit is a variation of something already on screen, it is the fastest surface Cursor has. When the next edit needs a decision that is not visible anywhere, use a surface that takes an instruction.

| The next edit is… | Use | Why |
| --- | --- | --- |
| The fourth instance of a pattern with three on screen | Tab | The pattern is visible, so the suggestion is mostly right |
| A change to one selected block ("add retry with backoff") | [Inline edit](/en/cursor-ide/build/inline-edit/) | You need to state an instruction the code does not contain |
| A change across files, or one that needs running tests | [Agent](/en/cursor-ide/build/agent-chat/) | It can read, edit and run commands, then report evidence |
| Business logic nobody has written yet | Agent with a spec, or you | Tab can only guess at rules it has never seen |

## Accept, partially accept or reject a Tab suggestion

Every suggestion is a choice to take all of it, part of it or none of it. The subsections below cover the keys, what to check before you accept, and how to chain accepted edits through a repetitive change.

### Take only the part of a suggestion that is right

You do not have to take a suggestion whole:

- **`Tab`** accepts the whole suggestion.
- **Partial accept** takes it word by word. The last reported default is `Cmd+Right Arrow` on macOS (`Ctrl+Right Arrow` on Windows and Linux) as of 2026-09-26; confirm it by searching the Keyboard Shortcuts editor for "partial", and see the [keyboard shortcuts reference](/en/cursor-ide/reference/keyboard-shortcuts/).
- **`Escape`** dismisses the suggestion.

Partial accept pays off when the structure is right and a value is wrong: take the call and the first arguments, then type the argument Tab got wrong.

### Judge each suggestion by the detail most likely to be wrong

A suggestion that copies a pattern is rarely wrong in its shape. It is wrong in the one place the new case differs: the entity name, the status code, the error message, the fixture ID. Look there first, decide, and move on: accept with Tab or dismiss with Escape. Typing over a half-accepted suggestion leaves you with a merge of your intent and Tab's, which is the hardest version to check later.

### Chain Tab through a repetitive change

For a change that repeats down a file (a new field on every fixture, a renamed option in every call), let Tab walk it:

1. Make the first change by hand, exactly as you want every instance to look.
2. Press Tab when the next suggestion appears, or when Tab offers a jump to the next location.
3. Check the one detail that varies per instance before each press.
4. Stop the chain the moment a suggestion is wrong; fix that instance by hand, and Tab continues from your corrected edit.
5. Run the file's tests or the type checker once the chain ends.

## Seed Tab so the first suggestion is right

Three habits make Tab's first suggestion the one you want: examples, an intent comment and descriptive names.

### Write two examples, let Tab write the third

The most dependable Tab workflow: write two instances of a pattern by hand, then put the cursor where the third starts. Two examples show Tab both what stays constant and what varies.

```typescript
// Written by hand:
it("returns 200 for an existing user", async () => {
  const res = await request(app).get("/api/users/1");
  expect(res.status).toBe(200);
  expect(res.body).toHaveProperty("id");
});

it("returns 404 for a missing user", async () => {
  const res = await request(app).get("/api/users/999");
  expect(res.status).toBe(404);
  expect(res.body.error).toBe("User not found");
});

// Type `it(` here. Tab typically proposes the next case in the same shape,
// for example a 400 for a non-numeric ID. Check the status and the message.
```

### Write the intent as a comment before the code

A comment above an empty line gives Tab the one thing the surrounding code cannot: what you intend to do next. The comment works best when it names both the condition and the exact response.

**Copy-paste intent comment: a guard clause Tab can complete**

```typescript
// Reject the signup if the email is already registered.
// Respond 409 with { error: "Email already in use" } and stop.
```

Paste the two lines above the insertion point in your handler, press Enter, and wait for Tab. Line one gives the condition and line two the response shape, so the suggestion should contain the lookup, the early return and the exact message. If it invents a different message or status, dismiss it: the comment is the spec, and the code has to match it.

Delete or keep the comment afterwards on its merits; a comment that restates the next line adds nothing to the file.

### Name variables for what they hold

Tab reads your identifiers. `const data = await fetch(url)` tells it nothing about what comes next; `` const userProfileResponse = await fetch(`/api/users/${userId}/profile`) `` tells it the resource, the ID and that the next lines probably check the response and parse a profile. Descriptive names cost nothing and remove guesswork from every suggestion that follows.

## Where Tab does its best work, and the gate that checks it

Each of the patterns below is one where Tab is usually right, paired with a check that catches it when it is not. The gate matters more than the pattern: it is what lets you accept quickly.

### Switch statements, checked for exhaustiveness

Write the first `case` by hand and Tab proposes the rest from the union type. Add an exhaustiveness check so a missing or misspelled case is a compile error instead of something you have to spot:

```typescript
type Status = "pending" | "active" | "suspended" | "deleted";

function statusLabel(status: Status): string {
  switch (status) {
    case "pending":
      return "Pending review";
    // Tab proposes "active", "suspended" and "deleted" in the same shape.
    default: {
      const unhandled: never = status;
      throw new Error(`Unhandled status: ${unhandled}`);
    }
  }
}
```

If Tab skips a member of the union, `status` is not `never` in the `default` branch and the type checker fails. Adding a new status later fails the build in the same way.

### Configuration objects, checked with `satisfies`

Tab fills configuration objects well once it has seen two or three properties. It also fills them with plausible values that are not yours. Anchor the object to its type so an invented or misspelled key fails:

```typescript
const dbConfig = {
  host: "localhost",
  port: 5432,
  // Tab proposes database, user, pool size and so on from the type and nearby code.
} satisfies DatabaseConfig;
```

`satisfies` rejects a property the type does not declare, and a missing required one, while keeping the literal types. What it cannot catch is a wrong value of the right type, such as a pool size copied from another service. Environment-dependent values belong in environment variables, not in whatever Tab saw last.

### Test cases, then prove the tests can fail

With the implementation open in the editor, start a `describe` block in the test file, write one case by hand and let Tab propose the next ones. Tab-written tests have a specific failure mode: they can assert what the code *does* rather than what it *should* do, so they pass against a bug. Prove they can fail before you trust them.

**Copy-paste Agent prompt: prove Tab-written tests catch a bug**

```text
The tests in src/orders/calculateOrderTotal.test.ts were written mostly with Tab. Check that they can fail.
1. In src/orders/calculateOrderTotal.ts, make three separate temporary mutations, one at a time: remove the discount, apply tax before the discount instead of after, and change `>=` to `>` in the free-shipping threshold.
2. After each mutation, run `npx vitest run src/orders/calculateOrderTotal.test.ts` and record which tests fail.
3. Revert each mutation before the next one. Confirm with `git diff --stat` that calculateOrderTotal.ts is unchanged at the end.
Report a table: mutation, tests that failed. If a mutation fails no test, propose the missing test case but do not add it.
```

A mutation that no test catches is a test you still need to write, and that is a better use of your attention than rereading the tests Tab wrote. For the full test-first loop in Cursor, see [test-driven development in Cursor](/en/cursor-ide/test/testing-tdd/).

## Hand off from Tab to inline edit and Agent

Tab stops being the right tool once a change needs a rule the code does not contain, a check across many accepted lines, or no suggestions at all. The subsections below cover each handoff.

### Scaffold with Tab, then rewrite the core with inline edit

Tab is fast at the parts that repeat: signature, validation shape, error handling, return type. It cannot know a business rule that appears nowhere in the code. Let Tab produce the scaffold, then select the body and state the rule with [inline edit](/en/cursor-ide/build/inline-edit/) (`Cmd+K` on macOS, `Ctrl+K` on Windows and Linux by default; confirm in the [keyboard shortcuts reference](/en/cursor-ide/reference/keyboard-shortcuts/)).

**Copy-paste inline edit prompt: replace a Tab scaffold with the real rule**

```text
Replace the body of this function. A discount code is valid only if: it exists in Redis under `discount:<code>`, its `expiresAt` is in the future, and its `uses` is below `maxUses`. Return { valid: false, reason } with reason "not_found", "expired" or "exhausted" for the three failures. Keep the signature, the existing error type and the early-return style of this file. Do not increment `uses` here.
```

After the inline edit lands, Tab picks up again from the new code: it tends to offer the matching test cases or call-site changes next, and the same gates apply to those.

### Check a Tab-written batch without reading every line

Accepting Tab suggestions for an hour produces a diff in which every line looked plausible at the moment you accepted it. Rereading all of it is the slow path. Use gates for the structure and point your attention, or an agent's, at the details copied patterns get wrong.

1. **Keep the type checker and the test runner in watch mode** while you work (`npx tsc --noEmit --watch` and `npx vitest`, or your stack's equivalents). A wrong Tab suggestion that breaks a type or a test shows up seconds after you accept it, not in CI.
2. **Let lint run on save.** Unused imports, shadowed variables and unreachable code are typical leftovers of a chain that went one step too far.
3. **Before you commit, ask Agent for a targeted check** of the details that pattern-copying gets wrong, using the prompt below. It reads the diff so you read only its findings.
4. **You sign off at the commit.** Tab never commits, and the accept key is not a review. The commit is where you decide, on the evidence from steps 1–3, that the batch is yours.

**Copy-paste Agent prompt: review a Tab-written diff for copied details**

```text
Review my uncommitted changes in src/routes/ and tests/routes/. Most of them were written by accepting Tab suggestions that copied an existing pattern, so the structure is probably right and the details may not be.
For every new or changed function and test, check only the details a copied pattern gets wrong: entity and resource names, HTTP status codes, error messages, field names, route paths and IDs in test fixtures. Compare each against the function's name and the route it serves.
List each mismatch as file:line, what it says, what it should say. Then run `npx tsc --noEmit` and `npx vitest run routes` (or your project's typecheck and test scripts) and paste the summary lines. Do not edit any file.
```

For the wider practice of trusting evidence over line-by-line reading, see [reading evidence instead of code](/en/ladder/evidence-not-diffs/).

### Turn Tab off where it costs more than it saves

Tab suggestions in prose, large data files or a hand-written algorithm you are reasoning through can cost more attention than they save. Cursor Tab is Cursor's own model, not VS Code's IntelliSense, so it is configured in Cursor Settings, not through VS Code's `editor.suggest.*` settings.

In earlier 3.x builds the **Cursor Tab** status-bar item offered snooze and per-extension disable (for file types such as `md` or `json`), and **Cursor Settings** had a Tab section. These controls were not re-checked on 2026-09-26, so search Cursor Settings for "Tab" to find them in your build.

If your build has them, snooze suits a focused hour of non-code work, and a per-extension disable suits a file type that is consistently noisy.

## When Tab suggestions go wrong

| Symptom | Likely cause | Recovery |
| --- | --- | --- |
| The suggestion has the right shape and a wrong value (status code, message, name) | Tab copied the nearest example, including the detail that should have changed | Partial-accept the structure and type the value; run the diff-review prompt above before committing |
| Tab keeps proposing the old pattern after a migration (class components, a deprecated client) | The old pattern is what sits near the cursor | Migrate one or two instances by hand first, in the same file or directly above; Tab continues from recent edits |
| Tab imports a symbol from the wrong package | Several packages export the same name | Dismiss, write the correct import once at the top of the file, then continue; the type checker catches the wrong one if you accepted it |
| Tab-written tests all pass, including against a known bug | The tests assert current behaviour, not intended behaviour | Run the mutation prompt above; add the cases it shows are missing |
| Tab proposes a real-looking URL, key or hostname | It continued a literal it saw elsewhere in the file | Dismiss it; read the value from configuration or an environment variable. Search the diff for literals before you commit |
| The Tab key does nothing, or inserts a different completion | Another completion extension, such as GitHub Copilot, is bound to the same key, or Tab is snoozed | Check whether Tab is snoozed or disabled (search Cursor Settings for "Tab"); disable the other extension's inline suggestions for this workspace |
| A long chain drifted and you are not sure which edits were right | Several accepted steps compounded one early mistake | Stop, run the type checker and tests, and undo back to the last green state instead of patching forward |

## Where to go next with Cursor editing

- Before this page: [Cursor core features](/en/cursor-ide/build/core-features/), for choosing between Tab, inline edit and Agent.
- Next: [Cursor inline edit](/en/cursor-ide/build/inline-edit/), for instructions applied to a selection, which is the natural partner to Tab's continuation.
- Then: [agent chat workflows in Cursor](/en/cursor-ide/build/agent-chat/), for changes that span files and need commands run.
- For the gates themselves: [test-driven development in Cursor](/en/cursor-ide/test/testing-tdd/).
- For where Tab sits in the bigger picture: [levels 1–2 of the autonomy ladder](/en/ladder/level-1-2-assisted-paired/), assisted and paired coding.
