🚀The world's best VBA AI has evolved. ExcelMaster is now an autonomous Agent.Read more →
Back to Blog

VBA Worksheet_SelectionChange in Excel — Run Code When the Cursor Moves (Highlight the Active Row Without Lag)

|

VBA Worksheet_SelectionChange in Excel — Run Code When the Cursor Moves (Highlight the Active Row Without Lag)

TL;DRWorksheet_SelectionChange is an event Excel fires every time the selection moves — a click, an arrow key, pressing Enter. It hands you the new selection as Target, so you can follow the cursor: highlight the active row, show a context panel, update a status cell. The one rule that governs it: it fires constantly, so the handler must be featherlight — anything slow makes the whole sheet feel laggy and broken. And if it changes the selection itself, guard it with Application.EnableEvents or it re-fires like Worksheet_Change does.

' Lives in the sheet's own object (e.g. Sheet1), NOT a Module or ThisWorkbook.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
    ' Highlight the active row — clear only what we set, then re-apply.
    Cells.Interior.ColorIndex = xlNone            ' clear previous highlight
    Target.EntireRow.Interior.Color = RGB(255, 255, 200)
End Sub

Where Worksheet_Change reacts to edits, SelectionChange reacts to movement. It's the event behind every "follow the cursor" effect — the highlighted active row that makes wide tables readable, the side panel that updates as you arrow down a list, the cell reference shown in a status bar. It's also the event most likely to make a workbook feel slow, because Excel calls it on every cursor move — dozens of times a second when you hold an arrow key.

What you'll learn

  • The mental model — a cursor tracker that hands you the new selection as Target
  • The rule that governs everything — it fires constantly, so keep it light
  • The trick everyone wants — highlight the active row, done without wrecking formatting
  • The trap it shares with Change — changing the selection re-fires the event
  • What Target actually is — the new selection, which may be many cells

The mental model: a cursor tracker, not an edit sensor

SelectionChange is a tracker wired to the cursor. You never call it; every time the selected cell or range moves, Excel calls you, passing the new selection as a Range named Target. Nothing about the data changed — only where the user is looking. That's the clean line between the two "react to the user" events: Change = a value changed; SelectionChange = the cursor moved.

Like all worksheet events, the handler lives in the specific sheet's code object (Sheet1 in the Project Explorer), not ThisWorkbook and not a Module, with the fixed signature Private Sub Worksheet_SelectionChange(ByVal Target As Range). The workbook-wide version, firing for every sheet, is Workbook_SheetSelectionChange in ThisWorkbook.

The rule that governs everything: it fires constantly, so keep it light

This is the rule that separates a snappy sheet from a sluggish one. SelectionChange runs on every selection move — each arrow-key press, each click, each Tab and Enter. Hold the down arrow and it can fire many times a second. Whatever the handler does, the user pays that cost on every single move.

So the handler must be O(tiny). The failure mode is unmistakable: put a slow query, a big For loop, or a full-sheet reformat in here and the sheet develops a visible lag — the cursor stutters, arrow keys feel mushy, and users conclude the file is "broken." The rule: do the minimum in SelectionChange; never loop over large ranges, hit disk, or recalc from inside it. If you need heavy work, trigger it from a button, not from moving the cursor.

The trick everyone wants: highlight the active row (done right)

The single most-requested use of this event is highlighting the active cell's row so a wide table is easy to read across. The naive version works but quietly does harm:

' Naive — destroys any existing fills and conditional-format colours on the sheet
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
    Cells.Interior.ColorIndex = xlNone            ' wipes EVERY cell's fill
    Target.EntireRow.Interior.Color = RGB(255, 255, 200)
End Sub

Two real problems hide in it. First, Cells.Interior.ColorIndex = xlNone clears the fill of every cell — including deliberate fills and the results of Conditional Formatting-style banding you set by hand — so your highlight eats the user's formatting. Second, changing .Interior clears Excel's undo stack: after the event runs, the user's Ctrl+Z is gone. On a data-entry sheet that's a genuine usability cost.

The robust approach is to change display, not cell formatting — drive a Conditional Formatting rule from a helper cell you update in the handler:

Private Sub Worksheet_SelectionChange(ByVal Target As Range)
    ' Store the active row number in a helper cell (say Z1);
    ' a Conditional Formatting rule =ROW()=$Z$1 paints the row.
    Application.EnableEvents = False        ' writing Z1 is an edit — don't re-fire events
    Range("Z1").Value = Target.Row
    Application.EnableEvents = True
End Sub

Now Conditional Formatting does the painting, no real fills are touched, and the undo stack survives. The handler stays tiny — it writes one number. That's the pattern to reach for when "highlight the active row" needs to coexist with real formatting.

The trap it shares with Change: re-selecting re-fires the event

SelectionChange has the same self-triggering hazard as Worksheet_Change, just via a different action. If your handler moves the selectionTarget.Offset(1, 0).Select, or Range("A1") .Select — that is itself a selection change, which fires SelectionChange again. Do it unconditionally and you get recursion or a cursor that fights the user.

The same fix applies: bracket any selection change (or cell write, like the helper cell above) with Application.EnableEvents = False … = True, and re-enable in an error handler so a crash can't leave events globally off:

Private Sub Worksheet_SelectionChange(ByVal Target As Range)
    On Error GoTo Done
    Application.EnableEvents = False
    Range("Z1").Value = Target.Row     ' or a guarded .Select, if you must move the cursor
Done:
    Application.EnableEvents = True
End Sub

If you ever see events stop working after experimenting here, an errored handler left EnableEvents off — the same story told in VBA On Error. Run Application.EnableEvents = True in the Immediate window to recover.

What Target actually is: the new selection, maybe many cells

Target is whatever is now selected — and that isn't always one cell. Click a column header and Target is the entire column; drag a block and Target is that block; Ctrl-click and it's a multi-area range. Code that assumes a single cell — Target.Value, Target.Row treated as "the one row" — can misbehave on a big selection.

Two habits keep it safe: read Target.Cells(1, 1) when you truly want the top-left active cell, and short-circuit when a huge selection makes the work pointless: If Target.Cells.Count > 1000 Then Exit Sub. That both protects performance (back to the "keep it light" rule) and avoids doing something silly when the user selects a whole column. SelectionChange completes the trio: it reacts to the cursor, Worksheet_Change reacts to edits, and Workbook_Open reacts to the file opening.

How ExcelMaster helps

A cursor-following macro is a balance act: react on every move, but do almost nothing; highlight the row, but don't eat the user's formatting or their undo; guard your own selection changes so the event doesn't fight itself. It's easy to write one that works and still makes the sheet feel broken.

ExcelMaster lets you describe the effect instead. Say "highlight the row of whatever cell I'm on, without disturbing my existing colours," and it writes a lightweight Worksheet_SelectionChange in the right sheet object — driving Conditional Formatting from a helper cell, guarded with EnableEvents, and kept small enough not to lag. You keep full control of the code; you skip the part where a well-meaning highlight wipes a day's formatting.

Frequently asked questions

What is Worksheet_SelectionChange in VBA?

It's a worksheet event Excel fires every time the selection moves — a click, an arrow key, Tab, or Enter. Excel passes the new selection to your handler as a Range called Target. Unlike Worksheet_Change, it fires on movement, not on edits, so it's used to follow the cursor (highlight the active row, update a status panel).

How do I highlight the active row in Excel with VBA?

The robust way is to store the active row number in a helper cell from a Worksheet_SelectionChange handler and let a Conditional Formatting rule (=ROW()=$Z$1) paint the row. That avoids the naive Cells.Interior.ColorIndex = xlNone approach, which wipes existing fills and clears the undo stack every time the cursor moves.

Why is my sheet slow or laggy after adding SelectionChange?

Because the event fires on every cursor move — many times a second when you hold an arrow key — and your handler is doing too much. Keep it featherlight: no loops over large ranges, no recalculation, no disk access. Move heavy work to a button instead, and exit early on large selections with If Target.Cells.Count > 1000 Then Exit Sub.

What is the difference between SelectionChange and Change?

Worksheet_SelectionChange fires when the cursor moves to a new cell or range; Worksheet_Change fires when a cell's content changes (typed, pasted, deleted, or written by VBA). Moving around a sheet fires only SelectionChange; editing a value fires only Change.

Does SelectionChange fire when I move the selection with VBA?

Yes. Range("A1").Select from code counts as a selection change and fires the event, which can cause recursion if it happens inside the handler. Wrap any selection change in Application.EnableEvents = False … = True, and always re-enable events in an error handler.

Tested in

Tested in: Excel 365 (Windows 11), VBA 7.1 — last verified 2026-08-02.

Related guides: VBA Worksheet_Change · VBA Workbook_Open · VBA On Error · VBA Range · VBA For Loop