TODO APP using HTML, CSS and JS - Local Storage [Design - HTML and CSS]
Tags: webdev, html, css, javascript
Published: 2021-01-17 19:07:49

Hello developers, I've created a TODO app only using frontend technologies (HTML, CSS and JS). It is a challenge from the website called [Frontend Mentor](https://frontendmentor.io).

If you want to look at my solution, Here is my live site [URL](https://hariramjp777.github.io/frontend-todo-app/) and [Github Repository](https://github.com/hariramjp777/frontend-todo-app/).

Here, In this blog, I'm going to share with you how I did this.

## Design

Here is the design file,

![TODO design](https://dev-to-uploads.s3.amazonaws.com/i/v0ln3dt77c7g4dg0d862.png)

## Boilerplate

First thing we should do is set up our project with HTML Boilerplate.

Here's mine,

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="author" content="Your Name" />
    <title>Frontend Mentor | TODO APP</title>
    <meta
      name="description"
      content="This is a front-end coding challenge - TODO APP"
    />
    <link
      rel="icon"
      type="image/png"
      sizes="32x32"
      href="./assets/images/favicon-32x32.png"
    />
    <link rel="preconnect" href="https://fonts.gstatic.com" />
    <link
      href="https://fonts.googleapis.com/css2?family=Josefin+Sans:wght@400;700&display=swap"
      rel="stylesheet"
    />
    <link rel="stylesheet" href="./css/styles.css" />
  </head>
  <body>
  </body>
</html>
```

## Set up colors and fonts

Next, we set up our colors, fonts that we're going to use using css custom properties.

```css
:root {
  --ff-sans: "Josefin Sans", sans-serif;
  --base-font: 1.6rem;
  --fw-normal: 400;
  --fw-bold: 700;
  --img-bg: url("../assets/images/bg-desktop-dark.jpg");
  --clr-primary: hsl(0, 0%, 98%);
  --clr-white: hsl(0, 0%, 100%);
  --clr-page-bg: hsl(235, 21%, 11%);
  --clr-card-bg: hsl(235, 24%, 19%);
  --clr-blue: hsl(220, 98%, 61%);
  --clr-green: hsl(192, 100%, 67%);
  --clr-pink: hsl(280, 87%, 65%);
  --clr-gb-1: hsl(236, 33%, 92%);
  --clr-gb-2: hsl(234, 39%, 75%);
  --clr-gb-3: hsl(234, 11%, 52%);
  --clr-gb-4: hsl(237, 12%, 36%);
  --clr-gb-5: hsl(233, 14%, 35%);
  --clr-gb-6: hsl(235, 19%, 24%);
  --clr-box-shadow: hsl(0, 0%, 0%, 0.1);
}
```

Custom properties in CSS are like variables. Variable name (Identifier) should be prefixed with `--`

We can use these variables defined here later in our code using `var()` function.

So, `var(--fw-normal)` returns 400.

## Get rid of default css - Using css resets

Every browser has a default style sheet called *User Agent Stylesheet* from which we get some styles for our headings, paragraphs and other elements.

But, It's better to start from scratch. So, Our css resets will be,

```css
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  font-size: 62.5%;
  position: relative;
}

html,
body {
  min-height: 100%;
}

ul {
  list-style: none;
}

img {
  user-select: none;
}
```

In the above code block, 
* We're setting `margin`, `padding` for all elements to be `0`. 
* Our `box-sizing` will be `border-box` which basically lets us get rid of overflow error.
* We're setting our base `font-size` to `62.5%` i.e. `10px` which makes our `rem` calculation easier.

```
1rem = 1 * base-font-size (base-font-size is 16px by default)
     = 1 * 16px 
     = 16px 

We're setting base to 10px. So,

1rem   = 10px
1.5rem = 15px
2.5rem = 25px
4.6rem = 46px

[Calculation is super easy here]
``` 

* Our page's height will be minimum 100%.
* We're disabling bullets for unordered list.
* We're using `user-select: none` to prevent user from selecting images i.e. when user presses `Ctrl + A`

## Background

When we look the above design, first thing we can see clearly is backgrounds.

Yes! we need to add `background-image` and `background-color`.

```css
body {
  font: var(--fw-normal) var(--base-font) var(--ff-sans);
  background: var(--clr-page-bg) var(--img-bg) no-repeat 0% 0% / 100vw 30rem;
  padding-top: 8rem;
  width: min(85%, 54rem);
  margin: auto;
}
```

Here, In this code block,

* ` font `
   * `font` is a shorthand property for ` <font-weight> <font-size> <font-family> `
   * So, our ` font ` will be ` 400 1.6rem "Josefin Sans", sans-serif `.
* ` background `
   * ` background ` is a shorthand property for ` <background-color> <background-image> <background-repeat> <background-position> / <background-size> `.
  * ` background-color ` and ` background-image ` defines color and image.
  * ` background-repeat ` defines whether the background image needs to be repeated or not. In our case, not, so ` no-repeat `.
  * ` background-position ` specifies the position of image. 
     ` 0% 0% ` means top left which is default.
  * ` background-size ` defines the size of our background. 
     * Syntax here as follows: ` <width> <height> `
* ` width `
   * Setting ` width ` using ` min() ` function.
   * ` min() ` function returns minimum value of its arguments.
   * ` min(85%, 54rem) `
      * In mobile devices, ` 85% ` will be body's width, but for desktop devices, ` 54rem ` will be body's width. 
* ` padding `
   * If you see the design file, there is some space at the top. So we're using ` padding-top ` to get that space.
* ` margin: auto ` to center the ` body `.

After we add background to our page, It looks like,

![TODO first step - adding background](https://dev-to-uploads.s3.amazonaws.com/i/jm84t0fmtt3fabtx1rx6.PNG)

## HTML

Next step is writing HTML Content.

We're going to use three semantic elements ` header `, ` main ` and ` footer `.

**` header `**

```html
<header class="card">
  <h1>TODO</h1>
  <button id="theme-switcher">
    <img src="./assets/images/icon-sun.svg" alt="Change color theme" />
  </button>
</header>
```

**` main `**

```html
<main>
  <div class="card add">
    <div class="cb-container">
      <button id="add-btn">+</button>
    </div>
    <div class="txt-container">
      <input
        type="text"
        class="txt-input"
        placeholder="Create a new todo..."
        spellcheck="false"
        autocomplete="off"
      />
    </div>
  </div>
  <ul class="todos"></ul>
  <div class="card stat">
    <p class="corner"><span id="items-left">0</span> items left</p>
    <div class="filter">
      <button id="all" class="on">All</button>
      <button id="active">Active</button>
      <button id="completed">Completed</button>
    </div>
    <div class="corner">
      <button id="clear-completed">Clear Completed</button>
    </div>
  </div>
</main>
```

**` footer `**

```html
<footer>
  <p>Drag and drop to reorder list</p>
</footer>
```

Don't worry about HTML, We're going to discuss each and every line. :thumbsup:

## Some more resets

In the above code blocks, we've used ` input ` and ` button ` elements. We can have some resets for them,

```css
input,
button {
  font: inherit; /* by default input elements won't inherit font 
                    from its parent */
  border: 0;
  background: transparent;
}

input:focus,
button:focus {
  outline: 0;
}

button {
  display: flex;
  user-select: none;
}
```

In the above code blocks, I've used ` display: flex; ` for ` button ` since we're including ` img ` inside ` button ` in markup.

|without ` display: flex `|with ` display: flex `|
| --- | --- |
| ![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/wxzthuc5i0lb9ntwiw87.PNG) | ![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/1t2tlsg41qnvpgyqny31.PNG)  |

Hope you can see the different between two images.

## Approach

If you look at the design file that I've included in the top of this post, you may get lot of ideas to replicate the same in browser.

One thing I got, We're going to assume all as cards. Each card may contain one or more items.

If you take ` header `,

![Todo Header](https://dev-to-uploads.s3.amazonaws.com/i/jezhmzg1bh3wysnjs73f.PNG)

It contains two, one is heading ` h1 ` and on the other side is a ` button `

This is going to be our approach.

## Let's design a card

```css
.card {
  background-color: var(--clr-card-bg);
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1.9rem 2rem;
  gap: 2rem;
}
```

But, some cards will look different, for eg. ` header ` card doesn't contain any background-color and our last ` div.stat ` looks very different.

So,

```css
header.card {
  background: transparent;
  padding: 0;
  align-items: flex-start;
}
```

## Let's continue..

There's a ` h1 ` in ` header `.

```css
header.card h1 {
  color: var(--clr-white);
  letter-spacing: 1.3rem;
  font-weight: 700;
  font-size: calc(var(--base-font) * 2);
}
```

` calc() ` allows us to do arithmetic calculations in css. Here,

```
calc(var(--base-font) * 2)
    = calc(1.6rem * 2)
    = 3.2rem
```

## Add Todo container

It is also a card. But It has some margins at the top and bottom and border-radius. So, let's add that.

```css
.add {
  margin: 4rem 0 2.5rem 0;
  border-radius: 0.5rem;
}
```

And for plus button **` #add-btn `**,

```css
/* add-btn */
.add .cb-container #add-btn {
  color: var(--clr-gb-2);
  font-size: var(--base-font);
  transition: color 0.3s ease;
  width: 100%;
  height: 100%;
  align-items: center;
  justify-content: center;
}

/* add some transition for background */
.add .cb-container {
  transition: background 0.3s ease;
}

/* define some states */
.add .cb-container:hover {
  background: var(--clr-blue);
}

.add .cb-container:active {
  transform: scale(0.95);
}

.add .cb-container:hover #add-btn {
  color: var(--clr-white);
}
```

And the text input container should stretch to the end. ` flex: 1 ` will do that.

```css
.add .txt-container {
  flex: 1;
}
```

and the actual input field,

```css
.add .txt-container .txt-input {
  width: 100%;
  padding: 0.7rem 0;
  color: var(--clr-gb-1);
}
```

We can also style the placeholder text using ` ::placeholder `,
Here we go,

```css
.add .txt-container .txt-input::placeholder {
  color: var(--clr-gb-5);
  font-weight: var(--fw-normal);
}
```

## Checkbox

```
MARKUP

.cb-container  [Container for checkbox]
  .cb-input    [Actual checkbox] 
  .check       [A span to indicate the value of checkbox]
```

**` .cb-container `**

```css
.card .cb-container {
  width: 2.5rem;
  height: 2.5rem;
  border: 0.1rem solid var(--clr-gb-5);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}
```

**` .cb-input `**

```css
.card .cb-container .cb-input {
  transform: scale(1.8);
  opacity: 0;
}
```

Here, we're using ` transform: scale() ` ie. ` scale() ` just zooms the field.

|without ` scale() `|with ` scale() `|
| --- | --- |
| ![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/1gpukcqfg393jd0ng0uj.PNG) | ![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/y1c1r07tdey9frr8nhl7.PNG) |

Since we're hiding our input using ` opacity: 0 `, User can't see the input, but can see the container. i.e. Input has to fill the entire container. That's the point of using ` scale() `.

And our ` span ` element i.e. ` .check `

```css
.card .cb-container .check {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  pointer-events: none;
  border-radius: inherit;
}
```

We're using ` pointer-events: none; ` here. Since, It's positioned absolute, It hides its parent which is ` .cb-container ` thereby not letting the user to check the checkbox.

To fix that, we can use ` pointer-events: none; ` which means that the current element i.e. ` .check ` won't react to any kind of mouse events. If user clicks there, checkbox will be clicked.

We can find whether the checkbox is checked or not using ` :checked `

```css
.card .cb-container .cb-input:checked + .check {
  background: url("../assets/images/icon-check.svg"),
    linear-gradient(45deg, var(--clr-green), var(--clr-pink));
  background-repeat: no-repeat;
  background-position: center;
}
```

Here, the selector defines,

` .check ` coming after ` .cb-input ` which is checked.

We're just adding a background image and color to indicate that this checkbox is true (checked).


## Todos container

Todos container ` .todos ` is a collection of ` .card `.

![TODO container](https://dev-to-uploads.s3.amazonaws.com/i/i22w3py8o288zxjgqsyt.PNG)

```
MARKUP

.todos            [todo container]
  .card           [a card]
    .cb-container + ------------ +
      .cb-input   |  [CHECKBOX]  |
      .check      + ------------ +
    .item         [Actual text i.e. todo]
    .clear        [clear button only visible when user hovers over 
                   the card]
```  

We need to add ` border-radius ` only for first card. We can add that using ` :first-child `.

```css
.todos .card:first-child {
  border-radius: 0.5rem 0.5rem 0 0;
}
```

If you look at the above image, you can see there's a line after each card. We can add that easily using, 

```css
.todos > * + * {
  border-top: 0.2rem solid var(--clr-gb-6);
}
```

In this block, Each card will be selected and ` border-top ` will be added to the card next to the selected card.

And for the actual text, **` .item `**

```css
.item {
  flex: 1; /* item needs to be stretched */
  color: var(--clr-gb-2);
}

/* Hover state */
.item:hover {
  color: var(--clr-gb-1);
}
```

And the **` .clear `** button,

```css
.clear {
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.5s ease;
}
```

` .clear ` button is visually hidden. It'll only be visible when user hovers over the card.

*Hover state*

```css
/* .clear when .card inside .todos is being hovered */
.todos .card:hover .clear {
  opacity: 1;
}
```

## Stat containers **` .stat `**

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/qckl1yhnuo4ovka1ffx8.PNG)

```
MARKUP

.stat             [stat container]
  #items-left     [text - items-left]
  .filter         [filter-container to filter todos, we use in js]
    #all
    #active
    #completed
  .corner         [corner contains button for Clear Completed]
    button      
```

```css
.stat {
  border-radius: 0 0 0.5rem 0.5rem;
  border-top: 0.2rem solid var(--clr-gb-6);
  font-size: calc(var(--base-font) - 0.3rem);
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
}

/* Add color property */
.stat * {
  color: var(--clr-gb-4);
}
```

We're using grid layout here, since It is easy to make ` .stat ` container responsive in smaller devices.

And for the filter buttons **` .filter `**

```css
.stat .filter {
  display: flex;
  justify-content: space-between;
  font-weight: var(--fw-bold);
}

.stat .filter *:hover {
  color: var(--clr-primary);
}
```

And finally if you see the corner ` Clear Completed `, It's aligned to the right side.

```css
.stat .corner:last-child {
  justify-self: end;
}

/* Hover state for button */
.stat .corner button:hover {
  color: var(--clr-primary);
}
```

## Footer

There is only one paragraph in the ` footer `.

```css
footer {
  margin: 4rem 0;
  text-align: center;
  color: var(--clr-gb-5);
}
```

## Responsive css

We need to change grid style of ` .stat ` in smaller devices, introducing two grid rows.

```css
@media (max-width: 599px) {
  .stat {
    grid-template-columns: 1fr 1fr;
    grid-template-rows: 1fr 1fr;
    gap: 5rem 2rem;
  }
  .stat .filter {
    grid-row: 2/3;
    grid-column: 1/3;
    justify-content: space-around;
  }
}
```

Thank you!, That's it for this post! Next is adding interactivity to our page using JavaScript. A post on adding interactivity to our app is [here](https://dev.to/hariramjp777/todo-app-using-html-css-and-js-local-storage-interactivity-javascript-3f3a).

Feel free to check my [Github Repository](https://github.com/hariramjp777/frontend-todo-app/)

If you have any questions, please leave them in the comments.