Build a to-do app in three milestones: add tasks from a box, tick them off with a count of what is left, then make each task a component and clear the done ones.
Every app you use is data, events and a page that keeps up. A to-do list is the classic first app for every framework.
Build a to-do app, one milestone at a time. Everything you need, you have met in this course: refs, v-for, v-model, functions on @click, computed and a component.
One small new thing: v-model on a tick box (<input type="checkbox" v-model="t.done">) joins it to true or false.
Make your prediction first. Help opens once you've locked it in.
This is the app you will build. The picture shows it finished.
<div id="app">
<h1>To do</h1>
<input v-model="newTask" placeholder="Something to do">
<button @click="addTask">Add</button>
<ul>
<todo-item v-for="t in tasks" :task="t"></todo-item>
</ul>
<p>{{ left }} left to do</p>
<button @click="clearDone">Clear done</button>
</div>
<script src="vue.js"></script>
<script>
const { createApp, ref, computed } = Vue;
const app = createApp({
setup() {
const tasks = ref([
{ text: "Feed the cat", done: false },
{ text: "Pack my bag", done: true }
]);
const newTask = ref("");
const left = computed(() => {
let count = 0;
for (const t of tasks.value) {
if (!t.done) {
count++;
}
}
return count;
});
function addTask() {
if (newTask.value !== "") {
tasks.value.push({ text: newTask.value, done: false });
newTask.value = "";
}
}
function clearDone() {
const keep = [];
for (const t of tasks.value) {
if (!t.done) {
keep.push(t);
}
}
tasks.value = keep;
}
return { tasks, newTask, left, addTask, clearDone };
}
});
app.component("todo-item", {
props: ["task"],
template: `<li><label><input type="checkbox" v-model="task.done"> {{ task.text }}</label></li>`
});
app.mount("#app");
</script>

Which piece of Vue makes each part?
A count that goes stale.
Match each part of the app with how it is built.
Clicking Tick the first should show 1 left to do, but it still says 2. There is no error. Click the line that is wrong.
Each milestone starts from a working copy of the one before (Reset to this task's start loads it), so nobody is stuck behind.
Most of the code is there.
A function in setup, returned, and a button that names it with @click.
Inside addTask: if newTask.value is not empty, push { text: newTask.value, done: false } onto tasks.value, then set newTask.value to empty text.

The comments give the outline.
The tick box goes inside the li, and v-model joins it to that task's done.
The count: let count = 0; a for loop over tasks.value; add 1 when !t.done; return count. Wrap it all in computed(() => { … }) and return left.

Goals only.
The component is the li, given a task as its prop. Then the v-for goes on the new tag.
Clear done: make a new empty list, push in every task that is not done, then set tasks.value to it.

Milestone 1: an Add button that adds what is in the box to the list.
This ticks when your page has all of the above.
Milestone 2: a tick box for each task, and a count of the tasks left.
This ticks when your page has all of the above.
Milestone 3: a todo-item component for each task, and a Clear done button.
This ticks when your page has all of the above.
Add one feature of your own. Ideas: All done! 🎉 when nothing is left; a Delete button on each task; a count of done tasks; tasks in two lists, to do and done; press Enter to add (@keyup.enter).
You built an app with Vue. The optional last lesson shows the same app in three frameworks.
Students match the parts of a finished to-do app to the course's pieces, find a count that goes stale, build the app in three milestones with fading support, then add a feature of their own. About 100 minutes.
Open the teacher guide: answers, solutions, slides and a worksheet.
Read the challenge primer first, then build milestone 1 yourself. There is nothing new here apart from one line: v-model on a tick box joins it to true or false. Each milestone recalls a lesson: adding (events and inputs, lesson 3), ticking and counting (computed, lesson 4), the component (lesson 5). Support fades on purpose: milestone 1 gives most of the code, milestone 2 an outline as comments, milestone 3 only the goals. Finishing this lesson marks Challenge complete on the course page.
The count and Clear done use a for loop over the list, which students know from p5.js and the DOM course. Real Vue code would often use filter, which is fine as a stretch. The tick box inside the component changes task.done on the object it was given: it works because the parent and the card share the same object, but in larger apps a component sends an event up instead (emit), which this course leaves out.
Milestone 3 is meant to be hard: turning working code into a component. Ask "what does one card need to be given?" before showing anything.
Stretch: Save the tasks between visits with localStorage (watch the list, then JSON.stringify it); use filter for the count; send a delete event up from the component with emit.
Support: Pair students for milestone 3: one writes the component, one writes Clear done.
The optional lesson builds the same app in Vue, Preact and Svelte. Real projects use a build tool (npm create vue@latest) and single-file .vue components; the ideas stay the same. Assessment idea: students annotate a printout of their app with the lesson each part came from.