How to Create a Todo App in Vue: Simple Step-by-Step Guide
To create a todo app in Vue, use a
ref to store the list of todos and the new todo input. Use v-for to display todos and v-model to bind input, then add new todos with a method triggered by a button click.Syntax
This is the basic structure for a Vue todo app:
refholds reactive data like the todo list and input.v-modelbinds input fields to data.v-forloops over todos to display them.@clicktriggers methods on button clicks.
vue
<template> <input v-model="newTodo" placeholder="Add todo" /> <button @click="addTodo">Add</button> <ul> <li v-for="todo in todos" :key="todo.id">{{ todo.text }}</li> </ul> </template> <script setup> import { ref } from 'vue' const todos = ref([]) const newTodo = ref('') function addTodo() { if (newTodo.value.trim() === '') return todos.value.push({ id: Date.now(), text: newTodo.value }) newTodo.value = '' } </script>
Output
An input box with an 'Add' button and a list below showing added todo items.
Example
This example shows a complete Vue 3 todo app using the Composition API. You can type a todo, click Add, and see it appear below.
vue
<template>
<main>
<h1>Vue Todo App</h1>
<input v-model="newTodo" placeholder="Enter a todo" aria-label="New todo input" />
<button @click="addTodo" aria-label="Add todo">Add</button>
<ul>
<li v-for="todo in todos" :key="todo.id">{{ todo.text }}</li>
</ul>
</main>
</template>
<script setup>
import { ref } from 'vue'
const todos = ref([])
const newTodo = ref('')
function addTodo() {
if (!newTodo.value.trim()) return
todos.value.push({ id: Date.now(), text: newTodo.value })
newTodo.value = ''
}
</script>
<style scoped>
main {
max-width: 400px;
margin: 2rem auto;
font-family: Arial, sans-serif;
}
input {
padding: 0.5rem;
font-size: 1rem;
width: 70%;
margin-right: 0.5rem;
}
button {
padding: 0.5rem 1rem;
font-size: 1rem;
cursor: pointer;
}
ul {
margin-top: 1rem;
padding-left: 1rem;
}
li {
margin-bottom: 0.5rem;
}
</style>Output
A webpage with a heading 'Vue Todo App', an input box, an Add button, and a list that updates with each new todo entered.
Common Pitfalls
Common mistakes include:
- Not using
reffor reactive data, so UI doesn't update. - Forgetting to clear the input after adding a todo.
- Not checking for empty input before adding.
- Missing
:keyinv-for, which can cause rendering issues.
vue
<!-- Wrong: No reactive refs and no input clearing --> <template> <input v-model="newTodo" /> <button @click="addTodo">Add</button> <ul> <li v-for="todo in todos">{{ todo }}</li> </ul> </template> <script setup> let todos = [] let newTodo = '' function addTodo() { todos.push(newTodo) // input not cleared } </script> <!-- Right: Use ref, clear input, and add key --> <template> <input v-model="newTodo" /> <button @click="addTodo">Add</button> <ul> <li v-for="todo in todos" :key="todo.id">{{ todo.text }}</li> </ul> </template> <script setup> import { ref } from 'vue' const todos = ref([]) const newTodo = ref('') function addTodo() { if (!newTodo.value.trim()) return todos.value.push({ id: Date.now(), text: newTodo.value }) newTodo.value = '' } </script>
Quick Reference
Remember these key Vue features for a todo app:
| Feature | Purpose |
|---|---|
| ref | Creates reactive data variables |
| v-model | Binds input fields to reactive data |
| v-for | Loops over arrays to render lists |
| @click | Handles user click events |
| :key | Provides unique keys for list items |
Key Takeaways
Use Vue's ref to create reactive todo list and input variables.
Bind input with v-model and display todos with v-for and :key.
Always check for empty input and clear it after adding a todo.
Use @click to trigger adding todos on button press.
Include accessibility attributes like aria-label for better usability.