Skip to main content

Text Objects

Vim edits text in chunks — a word, a sentence, a paragraph, a quoted string, a block inside parentheses. Each chunk is a text object, and an operator like c (change), d (delete), or y (yank) can act on the whole chunk at once.

Core Idea

Text objects are the nouns of Vim's editing language; operators (c, d, y) are the verbs that act on them. You act on a whole "chunk" of text without ever counting characters or thinking about its exact boundaries.

Start Here: ciw​

Learn one object before the whole system. ciw = change inner word:

PartMeaning
cchange — delete the text, then let you type
iinner — the content only, not the spaces around it
wword — the word the cursor is on

Read the cursor marker

In every example a ^ shows where the cursor sits — it is a picture, not text you type:

The quick brown fox
^
cursor

Here the cursor is on the q of quick.

Before → after

With the cursor on quick, press ciw, type fast, then press Esc:

Before: The quick brown fox
^
After: The fast brown fox

Result: only quick became fast. The, brown, fox, and every space stayed exactly where they were. You never had to move to the start of the word — ciw took the whole word from wherever the cursor was. That position independence is the whole point of text objects.

Test it in text-objects-practice.md (open it with the shell alias np-top):

CommandDrill line to testExpected result
ciwline 61 — The quick brown fox... (cursor in quick)changes only quick to the word you type

Text Object Syntax​

{operator} {i|a} {text-object}

i = "inner" (without surrounding whitespace/delimiters)
a = "around" (includes surrounding whitespace/delimiters)

Every operator + i/a combination below is drilled in text-objects-practice.md (open it with the shell alias np-top), starting with Drill 0 (line 33).

The Two Modifiers: i and a​

ModifierMnemonicWhat it includes
iinnerThe content, without surrounding chars
aaroundThe content AND surrounding chars/whitespace

Example with parentheses (hello world) — put the cursor anywhere inside the object:

(hello world)
^
cursor
ci( → change inner ( → removes "hello world", keeps the parens
ca( → change around ( → removes "(hello world)" including the parens
di( → delete inner ( → removes "hello world", keeps the parens
da( → delete around ( → removes "(hello world)" including the parens

Before → after

Cursor inside (hello world), press ci(, type bye, then press Esc:

Before: (hello world)
^
After: (bye)

Result: only the inner text changed — the parentheses stayed. ci( replaced hello world but kept the () wrapper.

Press ca( instead to drop the parentheses too:

Before: (hello world)
^
After: bye

Result: the whole (hello world) — parentheses and all — became bye.

Test each command on the drill line in practice-folder/nvim/03-text-objects-and-motions/text-objects-practice.md (open it with the shell alias np-top):

CommandDrill 0 line to testExpected result
ci(line 33 — (hello world)replaces hello world, keeps the parens
ca(line 33 — (hello world)replaces (hello world) including the parens
di(line 33 — (hello world)leaves an empty ()
da(line 33 — (hello world)removes the whole pair, line becomes empty

Word and WORD Text Objects​

Text ObjectSelects
iwInner word — the word without surrounding space
awAround word — word plus one surrounding space
iWInner WORD — space-delimited word without space
aWAround WORD — WORD plus surrounding space
For: " hello world "
^^^^^ ← iw selects "hello"
^^^^^^^ ← aw selects "hello " (with trailing space)

For: user-profile.avatar
^^^^ ← iw (just "user")
^^^^^ ← aw (includes "-")
^^^^^^^^^^^^^^^^^^^← iW (entire "user-profile.avatar")
" hello world "
^
cursor

Before → after

Cursor on hello, press ciw, type hi, then press Esc:

Before: " hello world "
^
After: " hi world "

Result: only hello became hi — the quotes and spaces stayed put. daw would also delete the space after the word; diw leaves it.

Test each object in text-objects-practice.md (open it with the shell alias np-top):

CommandDrill line to testExpected result
iw / awline 59 — " hello world "iw grabs hello; aw grabs hello
iw / aw / iWline 60 — user-profile.avatariw=user, aw=user-, iW=whole WORD
ciw / daw / yiWline 61 — The quick brown fox...change / delete / yank the target word

Sentence and Paragraph​

Text ObjectSelects
isInner sentence
asAround sentence (including trailing space)
ipInner paragraph
apAround paragraph (including empty line)
First sentence here. Second sentence follows. Third one closes it out.
^
cursor

Before → after

Cursor in the second sentence, press dis (delete inner sentence):

Before: First sentence here. Second sentence follows. Third one closes it out.
^
After: First sentence here. Third one closes it out.

Result: the whole second sentence is gone (with its trailing space); First sentence here. and Third one closes it out. are untouched.

Test in text-objects-practice.md (open it with the shell alias np-top):

CommandDrill line to testExpected result
is / as / yasline 156 — First sentence here. Second sentence...select / yank one sentence
dip / yaplines 142-147 — the two paragraphsdelete / yank the whole paragraph

Delimiter-Based Text Objects​

These are the most productive text objects for code editing:

Text ObjectSelects inside...
i" a"Double quotes "..."
i' a'Single quotes '...'
i aBackticks ...
i( a(Parentheses (...) — also ib
i[ a[Square brackets [...]
i{ a{Curly braces {...} — also iB
i< a<Angle brackets <...>
it atHTML/XML tags <tag>...</tag>
const name = "Alice Smith"
^
cursor

Before → after

Cursor inside "Alice Smith", press ci", type Bob Jones, then press Esc:

Before: const name = "Alice Smith"
^
After: const name = "Bob Jones"

Result: the quotes stayed; only the text between them changed.

Test each delimiter in text-objects-practice.md (open it with the shell alias np-top):

Text objectDrill line to testExpected result
i" / a" / i' / a' / backtickslines 71-73 — const name, greeting, cmdchange / yank inside the quotes
i( / a(line 33 — (hello world)Drill 0 (see above)
i[ / a[line 88 — const nums = [1, 2, 3, 4];yi[ yanks 1, 2, 3, 4
iB / aB (curly)line 89 — const user = { name: "Bob", age: 30 };viB selects the body
i< / a<line 128 — <placeholder>ci< leaves <>
it / atline 124 — <p>This is inside...</p>dit keeps <p></p>

Practical Code Examples​

// function call: getData(userId, options)
// ^^^^^^^^^^^^^ di( → delete args
// ^^^^^^^^^^^^^^^ da( → delete args + parens

// string: const name = "Alice Smith"
// ^^^^^^^^^^^ ci" → change inner string
// ^^^^^^^^^^^^^ ca" → change including quotes

// array: const nums = [1, 2, 3, 4]
// ^^^^^^^^^^ yi[ → yank inner array
// ^^^^^^^^^^^^ da[ → delete including brackets

// object: const user = { name: "Bob", age: 30 }
// ^^^^^^^^^^^^^^^^^^^ viB → visual inner block

Test each snippet in text-objects-practice.md (open it with the shell alias np-top):

SnippetDrill line to testCommand
getData(userId, options)line 87 — const result = ...di( / da(
const name = "Alice Smith"line 71ci" / ca"
const nums = [1, 2, 3, 4]line 88yi[ / da[
const user = { name: "Bob", age: 30 }line 89viB

Block Text Objects​

Text ObjectSelects
i( / ibInner parentheses
a( / abAround parentheses
i&#123; / iBInner curly braces
a&#123; / aBAround curly braces

B (uppercase) specifically means curly braces — useful for navigating function bodies:

ciB → change entire function body (keep braces)
daB → delete function body including braces
const user = { name: "Bob", age: 30 }
^
cursor

Before → after

Cursor inside the braces, press ciB, type id: 1, then press Esc:

Before: const user = { name: "Bob", age: 30 }
^
After: const user = { id: 1 }

Result: the { } wrapper stayed; only the body between them was replaced.

Test in text-objects-practice.md (open it with the shell alias np-top):

CommandDrill line to testExpected result
ciB / daBline 89 — const user = { name: "Bob", age: 30 };change / delete the brace body
diB / d2iB / >aBline 105 — return getData(value);innermost {} vs outer body vs indent

Text Objects with Operators​

CombinationAction
ciwChange inner word
dawDelete around word
yi"Yank inner double-quoted string
ca(Change around parentheses
viBVisual inner Brace block
ditDelete inner HTML tag content
yapYank around paragraph
dipDelete inner paragraph
citChange inner tag (edit HTML content)
>aBIndent around Brace block

Each combination is drilled in text-objects-practice.md (open it with the shell alias np-top): ciw line 61 · daw line 61 · yi" line 71 · ca( line 33 · viB line 89 · dit line 124 · yap line 142 · dip line 142 · cit line 124 · >aB line 105

Text Objects Don't Require Precise Positioning​

This is the key advantage — you can be anywhere inside the object:

"Hello World"

With cursor on 'l' in "Hello":
ci" → works! Changes "Hello World" to whatever you type

Before → after

Cursor on the l in Hello, press ci", type Goodbye, then press Esc:

Before: "Hello World"
^
After: "Goodbye"

Result: the whole "Hello World" became "Goodbye" — even though the cursor was only on the l, not at the start of the quotes.

Test in text-objects-practice.md (open it with the shell alias np-top):

CommandDrill line to testExpected result
ci"line 168 — "Hello World"cursor on l in Hello still changes the whole string

Nesting — Text Objects Find the Innermost Match​

function outer() {
function inner() {
return getData(value); ← cursor here
}
}

diB → deletes "return getData(value);" (innermost {})
d2iB → deletes inner() function body (next outer {})

Before → after

Cursor on the return line, press diB:

Before:
function outer() {
function inner() {
return getData(value);
}
}

After:
function outer() {
function inner() {
}
}

Result: return getData(value); was removed — the innermost { } block is now empty. d2iB (count 2) removes the whole inner() body instead.

Test in text-objects-practice.md (open it with the shell alias np-top):

CommandDrill line to testExpected result
diB / d2iBline 105 — return getData(value);deletes innermost {}; d2iB the outer function body

Practice​

Practice File

Open the step-by-step drills: practice-folder/nvim/03-text-objects-and-motions/text-objects-practice.md (also available via the shell alias np-top).

DrillWhat it trainsSteps
1iw aw iW aWOn user-profile.avatar → iw (user) vs aw (user-) vs iW (whole WORD)
2Quoted stringsOn "Alice Smith" → ci" (keep quotes) vs ca' (drop quotes)
3Brackets & bracesdi( / da(; yi[ / da[; viB / ciB on the object literal
4Nested blocksCursor on a nested return → diB (innermost) vs d2iB (outer) vs >aB (indent)
5HTML tagsdit (keep tag) / cit (edit content) / vit / dat
6Paragraphsdip, yap, then paste with p
7Sentencesis, as, dis, yas on a three-sentence line
8Rapid-fire + mini.aiPosition-independent ci"; LazyVim cia to change one argument

In LazyVim​

LazyVim Context

Vanilla text objects (iw, ap, etc.) remain fully functional. However, LazyVim supercharges text objects by including mini.ai. mini.ai extends the standard a and i text objects to cover a wider variety of surroundings (like quotes, brackets, and even function arguments or tags) without needing separate plugins for each.

AspectVanilla NeovimLazyVim
Text ObjectsBuilt-in objects (iw, as)Enhanced via mini.ai
Function ArgumentsRequires targets.vim or similarBuilt into mini.ai (ia, aa)
getData(userId, options)
^
cursor

Before → after

Cursor on the options argument, press cia (mini.ai), type flags, then press Esc:

Before: getData(userId, options)
^
After: getData(userId, flags)

Result: only options changed to flags; userId and the commas stayed. (This is a mini.ai extension — vanilla Vim has no argument object.)

Test the mini.ai extras in text-objects-practice.md (open it with the shell alias np-top):

CommandDrill line to testExpected result
cia (mini.ai)line 87 — getData(userId, options)changes only the argument under the cursor

What's Next​