Skip to content

Keys

Recall reconciliation: when rebuilding, Flutter matches each new widget to the existing element by position and type. Usually that is exactly right. But in a list of same-typed stateful widgets, position-matching breaks when items move.

Imagine two TodoItem widgets (each stateful, each holding, say, a checkbox state). Swap their order in the list. Flutter sees “position 0 is still a TodoItem, position 1 is still a TodoItem” — types and positions match — so it keeps each element (and its state) exactly where it was. The data moved but the state did not. Result: you reorder the list and the checkboxes stay put on the wrong rows.

flowchart LR
  subgraph before["Before swap"]
    a0["item A (checked)"] --- a1["item B"]
  end
  subgraph after["After swap, NO keys"]
    b0["item B
but still shows checked"] --- b1["item A
now unchecked"]
  end
  before -->|state stuck to position| after
Without keys, state is matched by position and stays behind

A key tells Flutter “this element’s identity is X, not just its position.” Now, when matching new widgets to elements, Flutter matches by key — so state follows the widget even when it moves.

// Give each item a stable key derived from its data:
return ListView(
children: [
for (final todo in todos)
TodoItem(key: ValueKey(todo.id), todo: todo),
],
);

With ValueKey(todo.id), swapping the list reorders the elements to match — each TodoItem’s state travels with its id. The rule of thumb: you need keys when you have a list of stateful widgets whose order or membership can change. Stateless lists rarely need them.

KeyIdentity based onUse when
ValueKey(v)a value (id, string)Most list items — a stable data value identifies each
ObjectKey(o)object identityThe identifying thing is an object, not a simple value
UniqueKey()always uniqueYou want to force a new element (discard old state) every build
GlobalKey()app-wide uniqueYou need to access a widget state or its context from elsewhere (use sparingly)

GlobalKey is the heavy one: it uniquely identifies a widget across the entire app and lets you reach its State (e.g. formKey.currentState!.validate()). It is powerful but expensive and easy to misuse — prefer local keys unless you truly need cross-tree access.

When do you most need keys?
What goes wrong when you reorder stateful list items without keys?
Which key would you use to give each list item a stable identity from its data id?
What is special (and costly) about a GlobalKey?