Lists and tables Required
Key rows by their ID
Key every rendered list item by the record's stable ID, never by its position in the list.
How to do it
Frameworks reuse elements by key. With position as the key, deleting row 2 moves row 3's checkbox, focus and typed text onto the wrong record. Use the ID the server gave the record. For new records that have none yet, create a client ID once when the record is created.
Example
{orders.map(order => <OrderRow key={order.id} order={order} />)}
<OrderRow v-for="order in orders" :key="order.id" :order="order" />
from playwright.sync_api import expect
def test_typed_text_stays_with_its_row(page):
page.goto("https://staging.example.com/orders/A-1042/lines")
rows = page.get_by_role("row")
rows.nth(3).get_by_label("Note").fill("Gift wrap")
rows.nth(2).get_by_role("button", name="Remove").click()
expect(page.get_by_role("row", name="Blue mug")).to_contain_text("Gift wrap")
How to check
A lint rule bans the index as a key, such as react/no-array-index-key from eslint-plugin-react. Where no such rule exists, a search for :key="index" or key={i} finds nothing. A browser test types into the row for one record, removes the row above it, and the typed text stays with its record.
For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers app interface playbook: https://agentwranglers.se/playbook/app-ui/lists-and-tables/stable-row-keys.