Indexed v-model in Vue 3
TIL about handling any number of inputs with one reactive array by binding each field to array[index] in Vue 3.
📝 TIL — Indexed v-model
in Vue 3
Today I learned that you can handle any number of inputs with one reactive array by binding each field to
array[index]
. It’s the simplest way to keep dynamic rows in sync—no prop-drilling, no form library, no extra components.
The 4-step recipe
-
Define your dynamic list:
const rows = reactive([{ id: 1 }, /* ... */])
- This is your dynamic list, which could come from props or an API call.
-
Create a ref for input values:
const values = ref<string[]>([])
- This will be the single source of truth for all corresponding inputs.
-
Keep
values
in sync withrows
:watchEffect(() => { values.value = rows.map((_, i) => values.value[i] ?? '') })
- This
watchEffect
ensures that thevalues
array has the same length as therows
array, preserving any existing user input when rows are added or removed.
- This
-
Bind inputs in your template:
<input v-model="values[i]">
- Inside your
v-for
loop, bind each input to its corresponding element in thevalues
array usingvalues[i]
.
- Inside your
That’s it—four key pieces of logic.
🧪 30-second Proof-of-Concept
Play with it
- Add rows → the
values
array grows automatically. - Type in any input → its value appears at the matching index.
- Remove rows → the input and its value disappear together.
Why this pattern rocks
-
Zero boilerplate – one array, one watch, done.
-
Reorder-safe – as long as you keep stable keys (
:key="row.id"
), Vue keeps DOM ↔︎ array indices aligned. -
Easy aggregates – totals, validation, API payloads are all
values.value
away:const total = computed(() => values.value.reduce((s, n) => s + +n, 0))
Use it whenever your UI says “N inputs where N can change.” This is particularly useful when you have a dynamic table with input fields and you need to have validation.