In this tutorial, you are going to learn the basics of Vue.js. While we learn, we are going to build a Todo app that will help us to put in practice what we learn.

A good way to learn a new framework, It’s by doing a Todo app. It’s an excellent way to compare framework features. It’s quick to implement and easy to understand. However, don’t be fooled by the simplicity, we are going to take it to the next level. We are going to explore advanced topics as well such as Vue Routing, Components, directives and many more!

Let’s first setup the dev environment, so we can focus on Vue! 🖖


We are going to start with essential HTML elements and CSS files and no JavaScript. You will learn how to add all the JavaScript functionality using Vue.js.

To get started quickly, clone the following repo and check out the start-here branch:

git clone

After running npm start, your browser should open on port and show the todo app.


Try to interact with it. You cannot create a new Todos, nor can you delete them or edit them. We are going to implement that!

Open your favorite code editor (I recommend Code) on vue-todo-app directory.


Take a look at the package.json dependencies:

"todomvc-app-css": "2.1.2",

We installed Vue and VueRouter dependencies. Also, we have the nice CSS library for Todo apps and live-server to serve and reload the page when we make changes. That’s all we would need for this tutorial.


Open the index.html file. There we have the basic HTML structure for the Todo app that we are going to build upon:

  • Line 9: Loads the CSS from NPM module node_modules/todomvc-app-css/index.css.
  • Line 23: We have the ul and some hard-coded todo lists. We are going to change this in a bit.
  • Line 73: we have multiple script files that load Vue, VueRouter and an empty app.js.

Now, you know the basic structure where we are going to work on. Let’s get started with Vue! 🖖

Getting started with Vue

As you might know…

Vue.js is a reactive JavaScript framework to build UI components.

It’s reactive because the data and the DOM are linked. That means, that when data changes, it automatically updates the DOM. Let’s try that!

Vue Data & v-text

Go to app.js and type the following:

const todoApp = new Vue({

The el is the element where Vue is going to be mounted. If you notice in the index.html that’s the section part. The data object is reactive. It keeps track of changes and re-render the DOM if needed. Go to the index page and change <h1>todos</h1> for <h1>{{ title }}</h1>. The rest remains the same:

<section class="todoapp">
<input class="new-todo" placeholder="What needs to be done?" autofocus>

If you have npm start running you will see that the title changed!

You can also go to the console and change it todoApp.title = "Bucket List" and see that it updates the DOM.


Note: besides the curly braces you can also use v-text:


Let’s do something useful and put an initial todo list:

const todoApp = new Vue({
{ text: 'Learn JavaScript ES6+ goodies', isDone: true },
{ text: 'Learn Vue', isDone: false },
{ text: 'Build something awesome', isDone: false },

Now that we have the list we need to replace the <li> elements with each of the elements in the data.todos array.

Let’s do the CRUD (Create-Read-Update-Delete) of a Todo application.

review diff

READ: List rendering with v-for

As you can see everything starting with v- is defined by the Vue library.

We can iterate through elements using v-for as follows:

<li v-for="todo in todos">
<input class="toggle" type="checkbox">
<button class="destroy"></button>
<input class="edit" value="Rule the web">

You can remove the other <li> tag that was just a placeholder.

review diff

CREATE Todo and event directives

We are going to implement the create functionality. We have a textbox, and when we press enter, we would like to add whatever we typed to the list.

In Vue, we can listen to an event using v-on:EVENT_NAME. E.g.:

  • v-on:click
  • v-on:dbclick
  • v-on:keyup
  • v-on:keyup.enter

Protip: since v-on: is used a lot, there’s a shortcut @. E.g. Instead of v-on:keyup.enter it can be @keyup.enter.

Let’s use the keyup.enter to create a todo:

<input class="new-todo" placeholder="What needs to be done?"

On enter we are calling createTodo method, but it’s not defined yet. Let’s define it on app.js as follows:

const textbox =;
this.todos.push({ text: textbox.value, isDone: false });

review diff

Applying classes dynamically & Vue v-bind

If you click the checkbox (or checkcirlcle) we would like the class completed to be applied to the element. We can accomplish this by using the v-bind directive.

v-bind can be applied to any HTML attribute such as class, title and so forth. Since v-bind is used a lot we can have a shortcut :, so instead of v-bind:class it becomes :class.

<li v-for="todo in todos" :class="{ completed: todo.isDone }">

Now if a Todo list is completed, it will become cross out. However, if we click on the checkbox, it doesn’t update the isDone property. Let’s fix that next.

review diff

Keep DOM and data in sync with Vue v-model

The todos have a property called isDone if it’s true we want the checkbox to be marked. That’s data -> DOM. We also want if we change the DOM (click the checkbox) we want to update the data (DOM -> data). This bi-directional communication is easy to do using v-model, it will keep it in sync for you!

<input class="toggle" type="checkbox" v-model="todo.isDone">

If you test the app now, you can see when you click the checkbox; also the text gets cross out. Yay!

You can also go to the console and verify that if you change the data directly, it will immediately update the HTML. Type the following in the browser console where you todo app is running:

todoApp.todos[2].isDone = true

You should see the update. Cool!

We want to double click on any list and that it automatically becomes a checkbox. We have some CSS magic to do that, the only thing we need to do is to apply the editing class.

<li v-for="todo in todos" :class="{ completed: todo.isDone }">
<input class="toggle" type="checkbox" v-model="todo.isDone">
<button class="destroy"></button>
<input class="edit" value="Rule the web">

Similar to what we did with the completed class, we need to add a condition when we start editing.

Starting with the label, we want to start editing when we double-click on it. Vue provides v-on:dblclick or shorthand @dblclick:

<label @dblclick="startEditing(todo)">{{todo.text}}</label>

In the app.js we can define start editing as follows:

const todoApp = new Vue({
{ text: 'Learn JavaScript ES6+ goodies', isDone: true },
{ text: 'Learn Vue', isDone: false },
{ text: 'Build something awesome', isDone: false },
const textbox =;
this.todos.push({ text: textbox.value, isDone: false });

We created a new variable editing in data. We just set whatever todo we are currently editing. We want only to edit one at a time, so this works perfectly. When you double-click the label, the startEditing function is called and set the editing variable to the current todo element.

Next, we need to apply the editing class:

<li v-for="todo in todos" :class="{ completed: todo.isDone, editing: todo === editing }">

When data.editing matches the todo , then we apply the CSS class. Try it out!

If you try it out, you will notice you can enter on edit mode, but there’s no way to exit from it (yet). Let’s fix that.


First, we want the input textbox to have the value of the todo.text when we enter to the editing mode. We can accomplish this using :value="todo.text". Remember that colon : is a shorthand for v-bind.

Before, we implemented the startEditing function. Now, we need to complete the edit functionality with these two more methods:

  • finishEditing: applies changes to the todo.text. This is triggered by pressing enter or clicking elsewhere (blur).
  • cancelEditing: discard the changes and leave todos list untouched. This happens when you press the esc key.

Let’s go to the app.js and define these two functions.

if (!this.editing) { return; }
const textbox =;
this.editing.text = textbox.value;

Cancel is pretty straightforward. It just set editing to null.

finishEditing will take the input current’s value ( and copy over the todo element that is currently being edited. That’s it!

review diff

DELETE todo list on @click event

Finally, the last step to complete the CRUD operations is deleting. We are going to listen for click events on the destroy icon:

<button class="destroy" @click="destroyTodo(todo)"></button>

also, destroyTodo implementation is as follows:

const index = this.todos.indexOf(todo);
this.todos.splice(index, 1);

review diff

Trimming inputs

It’s always a good idea to trim user inputs, so any accidental whitespace doesn’t get in the way with textbox.value.trim().

review diff

Items left count with computed properties

Right now the item left count is always 0. We want the number of remaining tasks. We could do something like this:

<strong>{{ todos.filter(t => !t.isDone).length }}</strong> item(s) left</span>

That’s a little ugly to stick out all that logic into the template. That’s why Vue has the computed section!

return this.todos.filter(t => !t.isDone);

Now the template is cleaner:

<strong>{{ activeTodos.length }}</strong> item(s) left</span>

You might ask, why use a computed property when we can create a method instead?

Computed vs. Methods. Computed properties are cached and updated when their dependencies changes. The computed property would return immediately without having to evaluate the function if no changes happened. On the other hand, Methods will always run the function.

Try completing other tasks and verify that the count gets updated.


review diff

We want to show clear completed button only if there are any completed task. We can accomplish this with the v-show directive:

<button class="clear-completed" @click="clearCompleted" v-show="completedTodos.length">Clear completed</button>

The v-show will hide the element if the expression evaluates to false or 0.

One way to clearing out completed tasks is by assigning the activeTodos property to the todos:

this.todos = this.activeTodos;

Also, we have to add the computed property completedTodos that we use in the v-show

return this.todos.filter(t => t.isDone);

review diff

Vue Conditional Rendering: v-show vs v-if

v-show and v-if looks very similar, but they work differently. v-if removes the element from the DOM and disable events, while v-show hides it with the CSS display: none;. So, v-if is more expensive than v-show.

If you foresee the element being toggling visibility very often then you should use v-show. If not, then use v-if.

We can hide the footer and central section if there’s no todo list.

<section class="main" v-if="todos.length">... </section>
<footer class="footer" v-if="todos.length">...</footer>

review diff

Local Storage

On every refresh, our list gets reset. This is useful for dev but not for users. Let’s persist our Todos in the local storage.

Local storage vs. Session storage. Session data goes away when you close the window or expire after a specific time. Local storage doesn’t have an expiration time.

The way localStorage works is straightforward. It is global variable and has only 4 methods:

  • localStorage.setItem(key, value): key/value storage. key and value are coerced into a string.
  • localStorage.getItem(key): get the item by key.
  • localStorage.removeItem(key): remove item matching the key.
  • localStorage.clear(): clear all items for the current hostname.

We are going to use getItem and setItem. First we need to define a storage key:

const LOCAL_STORAGE_KEY = 'todo-app-vue';

Then we replace data.todos to get items (if any) from the local storage:

todos: JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY)) || [
{ text: 'Learn JavaScript ES6+ goodies', isDone: true },
{ text: 'Learn Vue', isDone: false },
{ text: 'Build something awesome', isDone: false },

We have to use JSON.parse because everything gets stored as a string and we need to convert it to an object.

getItem will retrieve the saved todos from the localstorage. However, we are saying it yet. Let’s see how we can do that.

Vue Watchers

For saving, we are going to use the Vue watchers.

Vue watchers vs. Computed properties. Computed properties are usually used to “compute” and cache the value of 2 or more properties. Watchers are more low level than computed properties. Watchers allow you to “watch” for changes on a single property. This is useful for performing expensive operations like saving to DB, API calls and so on.

localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(newValue));

This expression watches for changes in our todos data. Deep means that it recursively watches for changes in the values inside arrays and objects. If there’s a change, we save them to the local storage.

review diff

Once you change some todos, you will see they are stored in the local storage. You can access them using the browser’s dev tools:

local storage

The last part to implement is the routing! However, for that, we need to explain some more concepts and will do that in the next post.

In the next tutorial, we are going to switch gears a little bit and go deeper into Vue Components, Routing, and Local Storage. Stay tuned!

We learned a lot! Here is a summary:

Name Description Examples
v-bind Bind to HTML attribute <span v-bind:title="tooltip"></span>
: Shortcut for v-bind <span :title="tooltip"></span> <li v-bind:class="{completed: todo.isDone }"></li>
v-text Inject text into the element <h1 v-text="title"></h1>
v-html Inject raw HTML into the element <blog-post v-html="content"></blog-post>
Name Description Examples
v-for Iterate over elements <li v-for="todo in todos"></li>
Name Description Examples
v-on:click Invoke callback on click <button class="destroy" v-on:click="destroyTodo(todo)"></button>
@ @ is shorcut for v-on: <input class="edit" @keyup.esc="cancelEditing" @keyup.enter="finishEditing" @blur="finishEditing">
v-on:dblclick Invoke callback on double-click <label @dblclick="startEditing(todo)"></label>
@keyup.enter Invoke callback on keyup enter <input @keyup.enter="createTodo">
@keyup.esc Invoke callback on keyup esc <input @keyup.esc="cancelEditing">
Conditional Rendering
Name Description Examples
v-show Show or hide the element if the expression evaluates to truthy <button v-show="completedTodos.length">Clear completed</button>
v-if Remove or add the element if the expression evaluates to truthy <footer v-if="todos.length">...</footer>
Automatic Data<->DOM Sync
Name Description Examples
v-model Keep data and DOM in sync automatially <input class="toggle" type="checkbox" v-model="todo.isDone">
Vue instance
const todoApp = new Vue({
const textbox =;
this.todos.push({ text: textbox.value.trim(), isDone: false });
return this.todos.filter(t => !t.isDone);
handler(newValue, oldValue) {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(newValue));