Todo App using VueJS 3 Composition API and Tailwind CSS

Sayan Sinha
6 min readApr 17, 2022

Searching for “Simple Todo App tutorial using Composition API” ?

You’re in a right place :D

Don’t be afraid by seeing “Tailwind CSS”. Its just a makeup as not to make it look like my ugly face.

Actual App Screenshot Showcase

This is the very easiest tutorial which is neither going to confuse you, nor going to make you go mad.

Highly self explainable code and totally from scratch. From setting up till finishing and wrapping up.

Pre-requisites:

  • You must know how to use Computer :P.
  • Overview of VueJS 3 is enough. You don’t need to be a Guru.
  • No need of Tailwind CSS Experience. If you understand CSS Classes, that would be enough.
  • Any text editor will do except Vim, Nano or MS Notepad.

Actual App Demo

App Demo

Step 1: Create a Folder

Create a folder, inside which we will be creating our project.
For this tutorial, I created a new folder named “Vue Todo” on desktop.

Step 2: Install Vue CLI

Go inside “Vue Todo” folder and open “cmd” in this location.

Install Vue CLI by typing in your command or terminal:

npm install -g @vue/cli

Note: You might see some warning messages. Don’t worry, just ignore them.

Wait for the process to finish.

Step 3: Create a new VueJS Project

After Step 1 completes, create a new VueJS Project by typing the following command and wait for the process to finish:

vue create todo

Step 4: Select Vue 3

Step 5: Run the Project

Get inside the folder

cd todo

Run the project

npm run serve

Open the link in browser : http://localhost:8080/

Leave it running on the browser.

Step 6: Setting up the Project

Delete the folder as marked in red box in the screenshot below.

Step 7: Write this code in “App.vue” file

<template>
<div>
<h1>Todo List</h1>

<form>
<input />
<button>Add Todo</button>
<button>Remove All Todos</button>
</form>
</div>

<div>
<h1>Done Homework</h1>
<h1 class="completed">Riding a Bicycle</h1>
</div>
</template>
<script setup></script><style>
.completed
{
text-decoration: line-through;
}
</style>

Let’s take a peek in a browser :

Todo App Basic UI
Todo App Basic UI

Step 8: Time for JavaScripts

Inside <script setup></script> tag in “App.vue”, add the JavaScript Code below :

import { ref } from "vue";const newTodo = ref("");
const todos = ref([]);
function addTodo() {
if(newTodo.value !== "") {
todos.value.push({ complete: false, text: newTodo.value });
newTodo.value = "";
}
}
function removeAllTodos() {
todos.value.splice(0, todos.value.length);
}
function completedTodo(todo) {
todo.complete = !todo.complete;
}

Step 9: Let’s get it Working

Replace the Markup part of the code inside <template></template> tag in “App.vue”:

<div>
<h1>Todo List</h1>
<form @submit.prevent="addTodo()">
<input v-model="newTodo" />
<button>Add Todo</button>
<button
v-if="todos.length !== 0"
@click="removeAllTodos"
>
Remove All Todos
</button>
</form>
</div>
<div>
<h1 :class="{ completed: todo.complete }"
v-for="(todo, index) in todos"
:key="index"
@click="completedTodo(todo)"
>
{{ todo.text }}
</h1>
</div>

Again, let’s take a peek in a browser :

Todo App Demo
Todo App Demo

Yay! We made it.

Here’s the full code of “App.vue”

<template>
<div>
<h1>Todo List</h1>
<form @submit.prevent="addTodo()">
<input v-model="newTodo" />
<button>Add Todo</button>
<button
v-if="todos.length !== 0"
@click="removeAllTodos"
>
Remove All Todos
</button>
</form>
</div>
<div>
<h1 :class="{ completed: todo.complete }"
v-for="(todo, index) in todos"
:key="index"
@click="completedTodo(todo)"
>
{{ todo.text }}
</h1>
</div>
</template>
<script setup>
import { ref } from "vue";
const newTodo = ref("");
const todos = ref([]);
function addTodo() {
if(newTodo.value !== "") {
todos.value.push({
complete: false, text: newTodo.value
});
newTodo.value = "";
}
}
function removeAllTodos() {
todos.value.splice(0, todos.value.length);
}
function completedTodo(todo) {
todo.complete = !todo.complete;
}
</script>
<style>
.completed
{
text-decoration: line-through;
}
</style>

Looks Ugly ? Huh!
Let’s give this beauty a touch of Tailwind CSS.

Step 10: Install Tailwind CSS

Open a new command window inside your project folder and then execute this 2 commands one by one:

> npm install -D tailwindcss postcss autoprefixer
> npx tailwindcss init -p

Step 11: Configure “tailwind.config.js” file

Replace the contents of “tailwind.config.js” with the code below:

module.exports = {
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}

Step 12: Create stylesheet

Create a new file “styles.css” under “assets” folder and add the code below:

@tailwind base;
@tailwind components;
@tailwind utilities;

Step 13: Include the “styles.css” to your Vue Project

Replace the contents of “main.js” with the code below:

import { createApp } from 'vue'
import App from './App.vue'
import './assets/styles.css'
createApp(App).mount('#app')

Step 15: Finally, we’re in last step to styling :D

Again, replace the Markup part of the code inside <template></template> tag in “App.vue”:

<div class="container mx-auto px-10 md:px-0">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div></div>
<div>
<div class="mt-10">
<h1 class="text-5xl font-semibold text-gray-600">Todo List</h1>
<div class="mt-6">
<form @submit.prevent="addTodo()">
<div class="grid grid-cols-1 gap-4">
<div>
<input class="border-2 outline-none py-2 px-2 shadow-md font-medium w-full rounded border-blue-300 hover:border-blue-600 focus:border-blue-600 focus:shadow-blue-200" v-model="newTodo" />
</div>
<div>
<div class="grid grid-cols-2 gap-2">
<div>
<button class="text-white py-2 px-4 shadow-md w-full rounded bg-red-400 hover:bg-red-600 font-semibold" v-if="todos.length !== 0" @click="removeAllTodos">Remove All Todos</button>
</div>
<div>
<button class="text-white py-2 px-4 shadow-md w-full rounded bg-blue-400 hover:bg-blue-600 font-semibold">Add Todo</button>
</div>
</div>
</div>
</div>
</form>
</div>
<div class="mt-8 text-center">
<div class="grid grid-cols-1 gap-3">
<div>
<div class="h-full" v-if="todos.length === 0">
<p class="text-gray-400">It appears you didn't added any To-Do.</p>
</div>
</div>
<div class="rounded shadow-md p-3 h-full hover:shadow-gray-400 text-gray-600 text-lg font-semibold" :class="{ completed: todo.complete }" v-for="(todo, index) in todos" :key="index" @click="completedTodo(todo)">
<span class="text-gray-600 text-lg font-semibold">{{ todo.text }}</span>
</div>
</div>
</div>
</div>
</div>
<div></div>
</div>
</div>

Again, let’s take a peek in a browser :

Final Todo App

Yes! Yes! Yes! We did it.
You made an App in VueJS 3 using Composition API.

A Little Extra for Extra Features

We did made a Todo App, but let’s use some Local-Storage feature to make the data persistent even after you close the browser.

To make it happen, just Replace the code inside the <script setup></script> in “App.vue” with the code below:

import { ref } from "vue";const newTodo = ref("");let storedTodos;localStorage.getItem("todos") ? storedTodos = JSON.parse(localStorage.getItem("todos")) : (storedTodos = []);const todos = ref(storedTodos);function addTodo() {
if(newTodo.value !== "") {
todos.value.push({ complete: false, text: newTodo.value });
newTodo.value = "";
}
}
function removeAllTodos() {
todos.value.splice(0, todos.value.length);
updateStorage();
}
function completedTodo(todo) {
todo.complete = !todo.complete;
updateStorage();
}
function updateStorage() {
localStorage.setItem('todos', JSON.stringify(todos.value));
}

Hope you like the tutorial :D

Here’s the Github Repository link: https://github.com/sayansinha5/VueJS-3-TodoList

--

--