Habit Tracker with Progress Analytics

Beginner 5 views Jul 24, 2026

Introduction

Building good habits is one of the most powerful ways to improve your life, but tracking them consistently is often the hardest part. In this beginner-friendly tutorial, you'll build a Habit Tracker with Progress Analytics using nothing more than HTML, CSS, and JavaScript. You'll learn how to add habits, mark them complete each day, calculate streaks, and visualize your consistency with a simple analytics chart — all without needing a backend or database, since everything is saved directly in the browser using the LocalStorage API.

Project Overview

This project is a single-page web application. Users can add habits they want to build (like "Drink Water" or "Read 10 Pages"), mark them as done for the day, and see how many days in a row they've kept the habit going (their "streak"). A built-in analytics section uses Chart.js to display a bar chart showing each habit's completion rate over the last 7 days, giving users an instant visual snapshot of their consistency.

Features

  • Add unlimited custom habits
  • Mark a habit as complete or incomplete for today with one click
  • Automatic streak calculation (consecutive days completed)
  • Weekly progress analytics chart powered by Chart.js
  • Delete habits you no longer want to track
  • Data persistence using the browser's LocalStorage — no backend required
  • Fully responsive layout that works on mobile, tablet, and desktop
  • Clean, beginner-friendly, well-commented code

Technologies Used

  • HTML5 – Page structure and semantic markup
  • CSS3 – Styling, Flexbox layout, and responsive design
  • JavaScript (ES6) – App logic, DOM manipulation, and localStorage handling
  • Chart.js (CDN) – Rendering the progress analytics bar chart

Folder Structure

Keeping files organized makes the project easy to understand and maintain. Here's the folder structure we'll use:

PLAINTEXT
habit-tracker/
│
├── index.html          → Main HTML file
├── css/
│   └── style.css       → All styling and responsive rules
└── js/
    └── script.js        → App logic (add, complete, streaks, chart)

Step 1: Create the Project Folder Structure

Start by creating a folder named habit-tracker. Inside it, create a css folder and a js folder as shown above. This keeps our HTML, styling, and logic neatly separated — a good practice even for small projects.

Step 2: Build the HTML Structure

Create a file named index.html and add the code below. This sets up the page skeleton: a header, a form to add new habits, a container where habit cards will appear, and a canvas element for our analytics chart.

HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <!-- Makes the page responsive on mobile devices -->
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Habit Tracker with Progress Analytics</title>

  <!-- Our custom stylesheet -->
  <link rel="stylesheet" href="css/style.css" />
</head>
<body>

  <!-- Page Header -->
  <header class="app-header">
    <h1>🏆 Habit Tracker</h1>
    <p>Build better habits, one day at a time.</p>
  </header>

  <main class="container">

    <!-- Form to add a new habit -->
    <section class="add-habit-section">
      <form id="habitForm">
        <input
          type="text"
          id="habitInput"
          placeholder="Enter a new habit (e.g. Drink Water)"
          required
        />
        <button type="submit">Add Habit</button>
      </form>
    </section>

    <!-- List of habit cards will be injected here by JavaScript -->
    <section class="habit-list-section">
      <h2>Your Habits</h2>
      <ul id="habitList" class="habit-list"></ul>
    </section>

    <!-- Analytics chart section -->
    <section class="analytics-section">
      <h2>Progress Analytics (Last 7 Days)</h2>
      <canvas id="analyticsChart"></canvas>
    </section>

  </main>

  <footer class="app-footer">
    <p>Made with ❤️ using HTML, CSS & JavaScript | MyRiteBook</p>
  </footer>

  <!-- Chart.js library loaded from CDN, used to draw the analytics chart -->
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

  <!-- Our own app logic, loaded last so the DOM is ready -->
  <script src="js/script.js"></script>
</body>
</html>

Step 3: Style the App with CSS (Responsive Design)

Create css/style.css. We'll use CSS variables for easy theming, Flexbox for layout, and a media query so the app looks great on mobile screens too.

CSS
/* ===== CSS Variables for easy theme control ===== */
:root {
  --primary-color: #4f46e5;
  --success-color: #22c55e;
  --danger-color: #ef4444;
  --bg-color: #f4f5f9;
  --card-bg: #ffffff;
  --text-color: #1f2937;
  --radius: 10px;
}

/* ===== Reset default browser spacing ===== */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: "Segoe UI", Arial, sans-serif;
  background-color: var(--bg-color);
  color: var(--text-color);
  line-height: 1.5;
}

/* ===== Header ===== */
.app-header {
  background-color: var(--primary-color);
  color: #fff;
  text-align: center;
  padding: 30px 15px;
}

/* ===== Main container keeps content centered and readable ===== */
.container {
  max-width: 700px;
  margin: 0 auto;
  padding: 20px;
}

/* ===== Add Habit Form ===== */
#habitForm {
  display: flex;
  gap: 10px;
  margin-bottom: 25px;
}

#habitInput {
  flex: 1;
  padding: 10px 12px;
  border: 1px solid #ccc;
  border-radius: var(--radius);
  font-size: 15px;
}

#habitForm button {
  background-color: var(--primary-color);
  color: #fff;
  border: none;
  padding: 10px 18px;
  border-radius: var(--radius);
  cursor: pointer;
  font-size: 15px;
}

#habitForm button:hover {
  opacity: 0.9;
}

/* ===== Habit List ===== */
.habit-list {
  list-style: none;
}

.habit-card {
  background: var(--card-bg);
  padding: 14px 16px;
  border-radius: var(--radius);
  margin-bottom: 12px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}

.habit-info h3 {
  font-size: 16px;
}

.habit-info span {
  font-size: 13px;
  color: #6b7280;
}

.habit-actions button {
  border: none;
  padding: 8px 12px;
  border-radius: var(--radius);
  cursor: pointer;
  font-size: 13px;
  margin-left: 6px;
}

.btn-done {
  background-color: var(--success-color);
  color: #fff;
}

.btn-done.completed {
  background-color: #a3a3a3; /* Greyed out once undone/toggled off */
}

.btn-delete {
  background-color: var(--danger-color);
  color: #fff;
}

/* ===== Analytics Section ===== */
.analytics-section {
  background: var(--card-bg);
  padding: 20px;
  border-radius: var(--radius);
  margin-top: 20px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}

/* ===== Footer ===== */
.app-footer {
  text-align: center;
  padding: 20px;
  font-size: 13px;
  color: #6b7280;
}

/* ===== Responsive Design for smaller screens ===== */
@media (max-width: 600px) {
  #habitForm {
    flex-direction: column;
  }

  .habit-card {
    flex-direction: column;
    align-items: flex-start;
  }

  .habit-actions {
    align-self: flex-end;
  }
}

Step 4: Add Functionality with JavaScript

Create js/script.js. This file handles adding habits, marking them complete, calculating streaks, saving data to localStorage, and updating the analytics chart. Every function below is commented to explain exactly what it does.

JAVASCRIPT
// ================================================================
    //  HABIT TRACKER — Complete Beginner Project
    //  Data is stored in localStorage using the key "habitTrackerData"
    //  Each habit: { id, name, completedDates: [dateStrings] }
    // ================================================================

    (function() {
      'use strict';

      // ----- CONSTANTS & STATE -----
      const STORAGE_KEY = 'habitTrackerData';
      let habits = JSON.parse(localStorage.getItem(STORAGE_KEY)) || [];
      let analyticsChart = null; // Chart.js instance

      // DOM refs
      const habitForm = document.getElementById('habitForm');
      const habitInput = document.getElementById('habitInput');
      const habitListEl = document.getElementById('habitList');
      const chartCanvas = document.getElementById('analyticsChart');

      // ----- HELPERS -----
      function saveHabits() {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(habits));
      }

      function getTodayString() {
        return new Date().toISOString().split('T')[0]; // 'YYYY-MM-DD'
      }

      // ----- CORE CRUD -----
      function addHabit(name) {
        const newHabit = {
          id: Date.now(),
          name: name.trim(),
          completedDates: []
        };
        habits.push(newHabit);
        saveHabits();
        renderAll();
      }

      function toggleToday(id) {
        const habit = habits.find(h => h.id === id);
        if (!habit) return;
        const today = getTodayString();
        const idx = habit.completedDates.indexOf(today);
        if (idx === -1) {
          habit.completedDates.push(today);
        } else {
          habit.completedDates.splice(idx, 1);
        }
        saveHabits();
        renderAll();
      }

      function deleteHabit(id) {
        habits = habits.filter(h => h.id !== id);
        saveHabits();
        renderAll();
      }

      // ----- STREAK & RATE CALCULATIONS -----
      function calculateStreak(habit) {
        let streak = 0;
        let date = new Date();
        // Loop backwards from today
        while (true) {
          const dStr = date.toISOString().split('T')[0];
          if (habit.completedDates.includes(dStr)) {
            streak++;
            date.setDate(date.getDate() - 1);
          } else {
            break;
          }
        }
        return streak;
      }

      function calculateWeeklyRate(habit) {
        let count = 0;
        for (let i = 0; i < 7; i++) {
          const d = new Date();
          d.setDate(d.getDate() - i);
          const dStr = d.toISOString().split('T')[0];
          if (habit.completedDates.includes(dStr)) count++;
        }
        return Math.round((count / 7) * 100);
      }

      // ----- RENDER FUNCTIONS -----
      function renderHabits() {
        habitListEl.innerHTML = '';
        if (habits.length === 0) {
          habitListEl.innerHTML = '<li style="color:#6b7280; padding:8px 0;">✨ No habits yet. Add one above!</li>';
          return;
        }
        const today = getTodayString();
        habits.forEach(habit => {
          const isDone = habit.completedDates.includes(today);
          const streak = calculateStreak(habit);
          const li = document.createElement('li');
          li.className = 'habit-card';
          li.innerHTML = `
            <div class="habit-info">
              <h3>${escapeHtml(habit.name)}</h3>
              <span>🔥 ${streak} day${streak !== 1 ? 's' : ''}</span>
            </div>
            <div class="habit-actions">
              <button class="btn-done ${isDone ? 'completed' : ''}" data-id="${habit.id}">
                ${isDone ? 'Undo' : 'Mark Done'}
              </button>
              <button class="btn-delete" data-id="${habit.id}">Delete</button>
            </div>
          `;
          habitListEl.appendChild(li);
        });
      }

      function renderChart() {
        const labels = habits.map(h => h.name);
        const data = habits.map(h => calculateWeeklyRate(h));

        // Destroy old chart if exists
        if (analyticsChart) {
          analyticsChart.destroy();
          analyticsChart = null;
        }

        // If no habits, show empty chart with message
        if (habits.length === 0) {
          // Draw an empty chart with a placeholder dataset
          analyticsChart = new Chart(chartCanvas, {
            type: 'bar',
            data: {
              labels: ['No habits yet'],
              datasets: [{ label: 'Completion Rate (%)', data: [0], backgroundColor: '#d1d5db' }]
            },
            options: {
              responsive: true,
              plugins: {
                legend: { display: false }
              },
              scales: {
                y: { beginAtZero: true, max: 100 }
              }
            }
          });
          return;
        }

        analyticsChart = new Chart(chartCanvas, {
          type: 'bar',
          data: {
            labels: labels,
            datasets: [{
              label: '7-Day Completion Rate (%)',
              data: data,
              backgroundColor: '#4f46e5',
              borderRadius: 6
            }]
          },
          options: {
            responsive: true,
            maintainAspectRatio: true,
            plugins: {
              legend: { display: true, position: 'top' }
            },
            scales: {
              y: {
                beginAtZero: true,
                max: 100,
                ticks: { callback: v => v + '%' }
              }
            }
          }
        });
      }

      function renderAll() {
        renderHabits();
        renderChart();
      }

      // ----- UTILITY: escape HTML to prevent XSS -----
      function escapeHtml(text) {
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
      }

      // ----- EVENT LISTENERS -----
      // Add habit form
      habitForm.addEventListener('submit', e => {
        e.preventDefault();
        const name = habitInput.value.trim();
        if (name !== '') {
          addHabit(name);
          habitInput.value = '';
          habitInput.focus();
        }
      });

      // Delegated clicks for habit actions (Mark Done / Delete)
      habitListEl.addEventListener('click', e => {
        const target = e.target.closest('button');
        if (!target) return;
        const id = Number(target.dataset.id);
        if (isNaN(id)) return;

        if (target.classList.contains('btn-done')) {
          toggleToday(id);
        } else if (target.classList.contains('btn-delete')) {
          if (confirm('Delete this habit and all its history?')) {
            deleteHabit(id);
          }
        }
      });

      // ----- INITIAL RENDER -----
      renderAll();

    })();

Step 5: How the Analytics Chart Works

The calculateWeeklyRate() function checks the last 7 calendar days and counts how many of them appear in a habit's completedDates array, converting that into a percentage. The renderChart() function feeds these percentages into Chart.js, which draws a bar for each habit. Every time a habit is added, completed, or deleted, we call renderChart() again — Chart.js is destroyed and recreated so it always reflects the latest data.

Step 6: Test Your Habit Tracker

Open index.html in your browser. Try the following:

  • Add a few habits like "Exercise" or "Read a Book"
  • Click "Mark Done" and watch the streak counter and chart update instantly
  • Refresh the page — your data should still be there, thanks to localStorage
  • Resize your browser window or open it on your phone to see the responsive layout in action

Possible Enhancements

Once you're comfortable with the basics, try extending the project with these ideas:

  • Add a monthly calendar view showing which days each habit was completed
  • Allow users to set a target streak goal and get a congratulations message
  • Add habit categories (Health, Study, Fitness) with color-coded cards
  • Sync data with a backend (PHP/MySQL) instead of localStorage for multi-device access
  • Add dark mode using CSS variables

Conclusion

In this project, you built a fully functional Habit Tracker with Progress Analytics using only HTML, CSS, and JavaScript. You learned how to manipulate the DOM dynamically, store and retrieve data with localStorage, calculate streaks with date logic, and visualize data using Chart.js. This project is a great addition to your beginner portfolio and a solid foundation for learning more advanced state management concepts later on.

Technologies Used

HTML5 CSS3 JavaScript Chart.js LocalStorage API

Live Output

See the project in action below. This is the live preview of the code you just learned.

Output Preview