Exciting Web Development Journey – To Do List
Hi there! Let’s continue our Web Development Journey we started in the previous post when we learned HTML, CSS, and JavaScript concepts. We had the opportunity to build a proof-of-concept web page to work with Sharing is The Key Website light and dark themes. I will assume you know some of the conventions, tricks, concepts, and tools we taught in the previous post for better productivity.

Scenario
We will make a web app for a to-do list. We’ll be able to use this app to track whatever we want: cool programming tricks that you want to learn, places to go, songs to listen to or learn to play, or something as basic as things to pick up at the stores. It will be possible to add and remove to-do items from the list and save them so they reload for you later.
Learning goals
- Getting to know more HTML elements such as Form and input text fields
- Styling using global variables, Flexbox module, and many other CSS resources
- Creating your own JavaScript functions
- Listening for different user actions on a web page
- Using functions together to write code more professionally.
- Saving user information between visits to your web page
What you will need
- VS Code, the cross-platform code editor. Or one of your preference.
- Docker Desktop latest stable version. You can see how to set it up here.
- Familiarity with at least one programming language. You don’t need to be an expert.
- A computer running one of the following:
- Windows: Windows 10 Home or Professional
- Mac: macOS 10.9 or later
- Linux: Ubuntu, Debian, Red Hat, Fedora, or SUSE
Project structure
Let’s clone the previous post project to save some time and learn more about git CLI.
In your terminal, type the following command and hit Enter.
git clone https://github.com/sitknewnormal/SITK-themes.git "SITK-ToDoList"
This command will pull SITK-themes source code from our Git Hub repository and copy it into the SITK-ToDoList new folder.

Move into the SITK-ToDoList project folder, remove git source control, and open the VS Code by running the following commands.
cd .\SITK-ToDoList\; Remove-Item -LiteralPath ".git" -Force -Recurse; code .

It will open SITK-ToDoList in VS Code. If you remember from the previous post, it will ask you to reopen the project into a Dev Container, but for now, let’s not do this because I want to introduce you to an exciting VS Code extension.

Let’s open the devcontainer.json
file, make some changes and save them:
- In the extensions values, replace
"tht13.html-preview-vscode"
by"ritwickdey.liveserver"
(Live Server). Live Server extension launches a local development server with live reload feature for static and dynamic pages;
- Change the name to “SITK – ToDo List.”

Now that we have everything in place, close VS Code and reopen it, but at this time, choose to reopen the project into the Docker Dev Container when asked to do so.
Let’s code HTML
When the Dev Container is up and running, and the project is ready to be edited, let’s go to the index.html and make some changes.
First, click on Go Live in the status bar to start our Live Server.

It will open the web page in your default browser. Let’s put the VS Code and the browser windows side-by-side to see the reflection of what we are coding in the browser as soon as we save the changes, and let’s close the VS Code Explorer to get more room to code.

Let’s get rid of everything between the opening <body>
and closing </body>
body element except for the theme switcher button element and save it.

Let’s give our app an important header To-do List using <h1>
element at the beginning of the body and save it.

We will need a paragraph <p>
right after the header to inform the user to mark an item as completed by double-clicking on it. Please save it to see it in the browser.

To add items to our To-do List, we will need a Form with an input text element and a button. An HTML Form is used to collect user input. The user input is most often sent to a server for processing, but this is not our case for this post. Therefore copy the code below to a new line right after the paragraph we’ve just added.
<form name="todo-adder">
<input type="text" id="todo-entry-box" />
</form>
<button id="add-btn" class="pretty-button">Add</button>
Notice that we named our Form name="todo-adder"
, we’ve given an identification id="todo-entry-box"
to our input text, we did it to the button id="add-btn"
as well, and we also created a class class="pretty-button"
for future CSS styling. It is essential to keep in mind that naming, giving identification to HTML elements is always a good practice and will help you style them uniquely. Classes are often used to style a group of elements. Furthermore, the naming convention for CSS attributes such as name, id, and class suggests using lowercase and the hyphen for composed names.

Now let’s break a line to separate the Form from the items, and let’s create an ordered list container so the user can see the item they will add to the list. Please copy the code below to a new line after the Add button.
<br />
<ol id="todo-list">
</ol>
We gave an ID id="todo-list"
to the To-do List for future CSS styling.

Next, we will create three buttons and wrap them in a div element. When it comes to productivity, VS Code is one of the best editors. The Emmet feature can help you with repetitive and boring coding tasks, and no extension is needed. Take a look at the video below to have a taste of VS Code editor’s power.
Let’s give them some IDs and texts accordingly to the chunk of code below.
<div id="control-wrapper">
<button id="clear-completed-btn" class="pretty-button">Clear Completed</button>
<button id="empty-btn" class="pretty-button">Empty List</button>
<button id="save-btn" class="pretty-button">Save List</button>
</div>

Let’s do one more cool thing! To make our To-do List look better, we will wrap the elements from line 12 (Header 1) to 25 (three buttons div) in a div to style it. We will select lines 12 to 25, type the shortcut Ctrl+Shift+P to access the command palette, type Emmet: Wrap with Abbreviation
, when it opens to enter the abbreviation, type div.todo-wrapper
and hit Enter.
The final HTML code should look like this.

As we could notice, we didn’t touch the head section where we still can see the style sheet linked, and we didn’t change the script tag where we still keep our JavaScript also linked.
Styling HTML with CSS
We already have our CSS from the previous post set up, which already supports light and dark themes, and we will keep and extend it. By the way, spending some time styling an application is very important and allows a better user experience.
Let’s do some cleaning before we style the new HTML elements we added.
First, let’s get rid of li
, ul
and .list
selectors since we don’t have them anymore in the HTML file. Plus, let’s add three more colour variables in the root element. Please copy the code below at the end of the root element and save it.
--laser1-light: #e5d9b6;
--light-gray: #3a3a3a;
--completed: #666666

We will use --laser1-light
and --light-gray
to style the to-do wrapper div’s background colour when we switch between light and dark themes, respectively. Place each variable inside light and dark themes selectors.

Let’s change the theme switcher button absolute position to the web page’s right top corner by changing the top
key to 0px
, replacing left
by right
key and set it also to 0px
, in the .btn
selector.

Next, we will style the to-do wrapper div setting the background colour, border-radius, top, left and right margin and padding. Please copy the code below to the end of the CSS file. Notice that we are already using the --todo-wrapper-bg
variable so the background will assume --laser1-light
or --light-gray
for the light and dark themes, respectively.
.todo-wrapper {
margin-top: 60px;
background-color: var(--todo-wrapper-bg);
border-radius: 30px;
margin-left: auto;
margin-right: auto;
padding: 30px;
}
Let’s check our work so far, but first, create four items in the ordered list using Emmet, remember? Inside the <ol>
element, start typing li*4
and hit Enter.

Now let’s style the only paragraph inside the to-do wrapper using the --completed
variable we defined previously to set the colour; let’s set it up to italic and define margins. Please copy the code below to the end of the CSS file.
.todo-wrapper p {
font-style: italic;
color: var(--completed);
margin: 5px 0;
}
The variable --completed
will also be used to style the finished tasks’ font and colour.
Next, we will style the ordered list and its items, setting margin and some padding to the items to allow a little bit of space between them. Please copy the code below to the end of the CSS file.
.todo-wrapper ol {
margin: 20px 0;
}
.todo-wrapper ol li {
margin: 5px 0;
padding: 3px;
}
Now let’s highlight the even items in our list for easy reading. Please copy the code below to the end of the CSS file.
.todo-wrapper ol li:nth-child(even) {
background: #999999;
}
Let’s check our work so far.

Next, we will style the completed list items. First, let’s go to the HTML file and set the two first items as completed using class="completed"
attribute.

Now, we will style those completed list items. Please copy the code below to the end of the CSS file.
.todo-wrapper .completed {
text-decoration: line-through;
color: var(--completed)
}

Time to style all buttons inside to-do wrapper div to look pretty. Remember that we used class="pretty-button"
all buttons except the theme switcher? We are now changing their margin, padding, border-radius, background colour, border, text alignment, and more at once. Please copy the code below to the end of the CSS file.
.todo-wrapper .pretty-button {
margin: 10px 2px;
padding: 5px 20px;
border-radius: 15px;
background-color: var(--bg);
border: none;
color: var(--fontColor);
text-align: center;
display: inline-block;
outline-style: none;
}

If you hover the mouse over the buttons, you will see no indication they are clickable. Let’s fix it by changing the cursor to a pointer and add a little bit of opacity as we hover over them. Please copy the code below to the end of the CSS file.
.todo-wrapper .pretty-button:hover {
cursor: pointer;
opacity: 0.75;
}
If we type in the text input field in the Form, we will notice the text look tight inside the text field. Let’s fix it by adding some padding. Please copy the code below to the end of the CSS file.
.todo-wrapper input {
padding: 6px 6px;
}

Next, we will fix the Add button, placing it on the input text field’s right side, which looks better.

Display using inline-block makes the element generate a block box that’s laid out as if it were an inline box. According to QuirksMode: An inline-block is placed inline (i.e. on the same line as adjacent content), but it behaves as a block.
We will now align the buttons in the control-wrapper div using the display flexible box module, a relatively new and easy way to arrange elements on the page. There is rich content here, so you can dig deep and learn more. For now, let’s say that the setup below finds a way to arrange the buttons at the center of the div and provides even spaces between them and the margins, which fits in most of the scenarios. Please copy the code below to the end of the CSS file.
#control-wrapper {
text-align: center;
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
For the matter of responsiveness, the following code uses media query CSS resource to resize the .to-do wrapper
width and .to-do wrapper input
width. For devices with screen resolutions of at least 1400 pixels, the .to-do wrapper
width will be set to 75%, and the .to-do wrapper input
width will be 600 pixels. For devices with screen resolutions between 750 pixels and 1399 pixels, the .to-do wrapper
width will be set to 50%, and the .to-do wrapper input
width will be 400 pixels. Please copy the code below to the end of the CSS file.
@media only screen and (min-width: 1400px) {
.todo-wrapper {
width: 50%;
}
.todo-wrapper input {
width: 600px;
}
}
@media only screen and (max-width: 1399px) and (min-width: 750px) {
.todo-wrapper {
width: 75%;
}
.todo-wrapper input {
width: 400px;
}
}

We can better see the responsiveness explained before in the following video where the .to-do wrapper
width and .to-do wrapper input
width resizes as soon as they reach the resolution limits defined in the media queries.
It is crucial to think of the behaviour you want your application to have in different devices and resolutions to catch and keep the user’s attention.
Let’s see the viewport metadata behaviour and how the elements arrange themselves in desktop, tablets, smartphones when properly styled using Flexbox, media queries, display inline-block and so on. Go back to the browser and click on the Toggle device toolbar button and play with the options.
Now that we have an awesome To-do List app well structured and styled, we will give it some action with JavaScript.
JavaScript
If you look at the page, you’ll see it has four buttons:
- An Add button for adding new to-do items
- A Clear Completed button for clearing items that you’ve marked as finished
- An Empty List button for completely emptying the to-do list
- A Save List button for saving what’s on the list
Of course, since you haven’t written any code yet, right now, none of them do anything!
Let’s add some code to our JavaScript file.
Add button
It is essential to tell JavaScript which parts of the HTML page are relevant and how user interactions with these parts should react. In this case, you want to tell it about the Add button and tell it to react when the user clicks this button.
Getting the Add button
Start by creating a button variable and telling JavaScript to get the id="add-btn"
button element from the HTML document. Please type or copy the following code to the end of our JavaScript file and save it.
const addBtn = document.getElementById("add-btn");
Listening for the click
Now connect your button to an event handler so that JavaScript listens to a particular type of event and then runs a function when it hears it. The event is a click in our case. Do this with the function addEventListener.
Please type or copy the following code to the end of our JavaScript file and save it.
addBtn.addEventListener("click", addToDoItem);
This listener will wait for the addBtn
click, and when the click is heard, it will respond by running the addToDoItem
function. Of course, because you haven’t written the addToDoItem
function yet, it just won’t work yet!
Creating the function
Later in this post, you will be writing code for your functions to add to-do items, clear the list, save it, etc. But for now, you want to check that you’ve connected your event listeners properly.
Create the addToDoItem
function so that it will pop up an alert message informing the user which button they have clicked. Please type or copy the following code to the end of our JavaScript file and save it.
function addToDoItem() {
alert("Add button clicked!");
}
Let’s check our work so far. Put the VS Code and the browser side by side again, and click on the Add button.

Write code for the remaining buttons.
Try it on your own, following the Add button steps to make sure you got the idea.
Connect the Clear Completed button, which has the Id clear-completed-btn
, to an alerting function called emptyList
.
Connect the Empty List button, which has the Id empty-btn
, to an alerting function called clearCompletedToDoItems
.
Connect the Save List button, which has the Id save-btn
, to an alerting function called saveList
.
Let’s see if you got it right! Check the code below and make some adjustments if needed or copy the following code to the end of our JavaScript file and save it.
let clearCompletedBtn = document.getElementById("clear-completed-btn");
clearCompletedBtn.addEventListener("click", clearCompletedToDoItems);
function clearCompletedToDoItems() {
alert("Clear completed button clicked!");
}
let emptyBtn = document.getElementById("empty-btn");
emptyBtn.addEventListener("click", emptyList);
function emptyList() {
alert("Empty button clicked!");
}
let saveBtn = document.getElementById("save-btn");
saveBtn.addEventListener("click", saveList);
function saveList() {
alert("Save List button clicked!");
}
Don’t forget to save it! So far, the code should look like this.

Click on all buttons to make sure they are working.
Add to-do items
Time to make the first of those buttons work correctly! This step will show you how to add a to-do item to the list.
First, go to the HTML file and delete all four list items <li>
inside the ordered list <ol>
and save it.

We are going to use a tiny bit of HTML in this step. Therefore you need to write some JavaScript to add <li>
elements for each new to-do item. The user should enter text and then click the Add button to see it appear on the list as a numbered item.
First, just like you did with the buttons, create variables to select the text box and the ordered list. They already have the Ids todo-entry-box
and todo-list
. Please type or copy the following code to the end of our JavaScript file and save it.
let toDoEntryBox = document.getElementById("todo-entry-box");
let toDoList = document.getElementById("todo-list");
Now the text box and the ordered list can be easily reached from within your JavaScript.
Let’s create a function called newToDoItem
to add an item to the list. This function will need to have two inputs:
- The text of the item
- The item completed or not
Of course, no new to-do item will ever be complete, but we are preparing ahead of time here: we will use the same function again when we load a saved list with some finished items. Please type or copy the following code to the end of our JavaScript file and save it.
function newToDoItem(itemText, completed) {
let toDoItem = document.createElement("li");
let toDoText = document.createTextNode(itemText);
toDoItem.appendChild(toDoText);
if (completed) {
toDoItem.classList.add("completed");
}
toDoList.appendChild(toDoItem);
toDoItem.addEventListener("dblclick", toggleToDoItemState);
}
This newToDoItem
does a few things.
let toDoItem = document.createElement("li");
Creates an li
element to use as your new list item.
let toDoText = document.createTextNode(itemText);
Creates a text node, a special container for a text that we want to put inside an HTML element using JavaScript, and fills it with the contents of the itemText
variable that is passed into the function.
toDoItem.appendChild(toDoText);
The function appendChild
takes the element, or text node, that you pass to it (in this case toDoText
), and puts it inside toDoItem
. If there are already elements inside that one, the one we are adding now will be last.
if (completed) { toDoItem.classList.add("completed"); }
Checks if the value for the completed variable that was passed to newToDoItem
is true. If it is true, it will add the class completed to the li
element, changing how it looks on the page. In our CSS file, there are special styling rules text-decoration: line-through;
for li
elements.
toDoList.appendChild(toDoItem);
The function appendChild
puts toDoItem
, the <li>
element inside of toDoList
, the <ol>
element.
toDoItem.addEventListener("dblclick", toggleToDoItemState);
Attaches an event listener for double-click to the toDoItem
And tells it to call a function named toggleToDoItemState
in response.
Next, connect to the function to the Add button. Change your existing addToDoItem
function to get the text from the box and pass it to the function newToDoItem
we’ve just created.
function addToDoItem() {
let itemText = toDoEntryBox.value;
newToDoItem(itemText, false);
}
Since a new to-do item is never finished, you can always pass false
to the completed parameter of the newToDoItem
function.
Let’s test it to see if it works!

Completing items
When you can’t mark items as done, there’s not much point to a to-do list! It’s time to add that feature.
For a double-click on a to-do item, we have already set up the listener. All we need to do now is write a function that, when that double-click occurs, will toggle the item between complete and not complete.
Note that to mark items as complete, we are using the completed class. Not having that class implies that they aren’t complete. All our function needs to do is add or remove the class from the item’s class list, either add it if it is not yet on the list or delete it.
The this
keyword
The trick is to know which item the class can turn on. You’ll need to use a new JavaScript keyword to describe the object that was clicked: this
.
It’s a little bit complicated how exactly the this
keyword works, but all we need to know here is that when used with a function called by an event listener, it means ‘the element to which the listener was bound.’ But you can use this
to find the particular list item <li>
we clicked on.
Add the toggleToDoItemState
function to the end of our JavaScript like so:
function toggleToDoItemState() {
this.classList.contains("completed") ? this.classList.remove("completed") : this.classList.add("completed");
}
Let’s test it to see if it works!

Remove items
If we have marked the items as complete, we will want to delete all of the completed items. Also, we may want to clear off everything on it if we come back to your list after a long time or if we just want to focus on something entirely different. To do this, we just need to update two functions that have already been connected to the buttons: clearCompletedToDoItems
and emptyList
.
Clearing completed items
Just as you can select all the elements in an HTML document, you can select the elements within any other element. The children of that element are called the elements inside another element. Likewise, just as you can select the Id elements, you can also select them by class.
Let’s update the existing clearCompletedToDoItems
function with code to select the toDoList
children who have the completed class to clear the completed items. Then loop over the selected items to remove one by one.
function clearCompletedToDoItems() {
let completedItems = toDoList.getElementsByClassName("completed");
while (completedItems.length > 0) {
completedItems.item(0).remove();
}
}
You will see that the code will always delete the item at position 0 of the list, the first item on the list. To do this, you need to use 0 since JavaScript starts counting at 0 and not 1. You delete this item to replace the first item every time the loop runs, so the list gets shorter and shorter. In this way, the loop will finally delete them all, no matter how many incomplete items are listed.
Let’s test it to see if it works!


Clearing everything
Let’s focus on the existing function emptyList
. To delete all from the list, do the same thing as above, but pick all the children from the toDoList
.
function emptyList() {
let toDoItems = toDoList.children;
while (toDoItems.length > 0) {
toDoItems.item(0).remove();
}
}
It is always recommended to check your work as soon as you advance on it.
Save the list
You can save the to-do list to a local storage location on the user’s computer to make it even more useful. Then, as long as they open it in the same browser next time, it will remember their to-do list.
There are two important steps for this task: saving the list and loading it again when the page is reloaded if the list is there.
This gets a little tricky: HTML can’t be saved in the local storage, so we need to take the HTML code and convert it to pure JavaScript. To do that, you will need an array.
An array is a variable of a special kind, which is a list of variables. You can build one with square brackets and use the push method to add objects to it. You can remind yourself what a specific array item is, using alert and the item’s position in the array. Remember that JavaScript starts counting at 0. Take a look at the code below to understand it better.
const myArray = [];
myArray.push("array item 1");
myArray.push("array item 2");
alert(myArray[0]);
//This will alert "array item 1"
Next, we need to loop over the list toDoList
and add each item to the array. Remember that we need to store the task and whether or not it has been completed. The best way to do this is to use JavaScript objects.
JavaScript objects
A JavaScript object is a set of properties and values. You create one like this:
const toDoInfo = {
"task": "Learn HTML",
"completed": false
};
You just need to save all the to-do items to local storage once you have converted them into objects. Local Storage can only store strings, but fortunately, JavaScript turns arrays into strings for you when you use the stringify function.
Saving in a Local Storage – Step 1
Enough said, let’s put it all together!
Let’s update the existing saveList
function to:
- Make an array
- Use a for loop to put every item in
toDoList
into the array as an object - Stringify the array and store it in local storage with the key
toDos
Here is the code for the saveList
function.
function saveList() {
const toDos = [];
for (let i = 0; i < toDoList.children.length; i++) {
let toDo = toDoList.children.item(i);
let toDoInfo = {
"task": toDo.innerText,
"completed": toDo.classList.contains("completed")
};
toDos.push(toDoInfo);
}
localStorage.setItem("toDos", JSON.stringify(toDos));
}
Load the saved list – Step 2
We need to reverse everything we have done to save it, to load the list. But first, we need to check if anything needs to be loaded. You do this by checking whether there is no null value on the key you used to store the list. ‘Null’ is yet another ’empty’ word or ‘nothing’ word.
Let’s create a loadList
function to:
- Check if the toDos key exists in local storage
- If it does, load it into a variable as an array
- Loop over the array, and use
newToDoItem
to create new to-do items for everything in it
Add the code below to the end of our JavaScript file.
function loadList() {
if (localStorage.getItem("toDos") != null) {
let toDos = JSON.parse(localStorage.getItem("toDos"));
for (let i = 0; i < toDos.length; i++) {
let toDo = toDos[i];
newToDoItem(toDo.task, toDo.completed);
}
}
}
Let’s call the loadList
function after we have created it. Add the code below to the end of our JavaScript file and save it.
loadList();
To close it with a golden key, let’s save it to the local storage every time we add a new item, mark an item as completed, clear completed items, and empty the list. All we need to do is adding the code below at the very end of addToDoItem
, clearCompletedToDoItems
, toggleToDoItemState
and emptyList
functions.
saveList();




Let’s test it to see if it works!
Congratulations, we have done a great job! I know it is a lot to take in, but you will get used to it with time and practice.
Let’s use Git to commit our work. We taught how to do it in our previous post; check it out here.
If you don’t know what Git is, take a look here and find out. The SITK-ToDo List source code can be found on our GitHub account.
We’ve got more of the basics of front-end web development. Certainly, there is much more to learn about HTML, CSS and JavaScript, and there are some free and excellent references for this: w3schools.com and MDN Web Docs.
If you need any help or something is going wrong, let me know. It will be my pleasure to help you. If you want to extend some point, we have discussed, contact me so we can cover in the next posts.
We also have pro bono projects just in case you are interested in learning more about them.
Be aware of why we started this project by clicking here.
Learn more about other posts here.
Contact us for any suggestions. And follow us on Facebook, Instagram and Twitter.
If you are a good reader like myself, I recommend the following readings:
- Docker Quick Start Guide: Learn Docker like a boss, and finally own your applications
- Docker for Developers: Develop and run your application with Docker containers using DevOps tools for continuous delivery
- Responsive Web Design with HTML5 and CSS: Develop future-proof responsive websites using the latest HTML5 and CSS techniques, 3rd Edition
- JavaScript: The Definitive Guide: Master the World’s Most-Used Programming Language 7th Edition
See you in the next post!
One thought on “Exciting Web Development Journey – To Do List”