Introduction

Hey dev community! 👋

Have you ever tried to build a habit but struggled to stay consistent? Visual feedback can be a powerful motivator! In this tutorial, I'll walk you through building a beautiful habit tracking application that uses SVG-based circular progress rings to visualize your daily progress.

We'll create a fully functional habit tracker where users can:

  • Add custom habits with personalized goals and colors
  • Track progress with visually satisfying animated progress rings
  • Monitor streak counts for extra motivation
  • Store data locally for persistent progress tracking

Check out the live demo to see what we're building today!

Project Overview

Our application combines several modern web development techniques:

  • SVG animations for the circular progress indicators
  • CSS with glass-morphism effects for a polished UI
  • Object-oriented JavaScript for clean, maintainable code
  • Local Storage API for data persistence

Let's break down how each piece works!

HTML Structure

First, we need to set up our HTML skeleton. This provides the basic structure for our application:

</span>
 lang="en">

     charset="UTF-8">
     name="viewport" content="width=device-width, initial-scale=1.0">
    Habit Tracker Pro
     rel="stylesheet" href="styles.css">
     href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">


     class="container">
        
            Habit Tracker Pro
             class="add-habit-btn" onclick="showAddHabitModal()">+ New Habit
        

         class="habits-grid" id="habitsGrid">

        
         class="modal" id="addHabitModal">
             class="modal-content">
                 class="close" onclick="hideAddHabitModal()">×
                Add New Habit
                 id="habitForm">
                     type="text" id="habitName" placeholder="Habit Name" required>
                     type="number" id="habitGoal" placeholder="Daily Goal" min="1" required>
                     type="color" id="habitColor" value="#4CAF50">
                     type="submit">Add Habit
                
            
        
    
    <span class="na">src="script.js">





    Enter fullscreen mode
    


    Exit fullscreen mode
    




The HTML establishes:
A container with a header and "New Habit" button
A grid to display habit cards
A modal for adding new habits with form fields for name, goal, and color

  
  
  Styling Our App
Next, we'll style our application with CSS to create a modern, dark-themed interface with smooth animations:

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: 'Inter', sans-serif;
}

body {
    background: linear-gradient(145deg, #0f172a, #1e293b);
    min-height: 100vh;
    color: #e2e8f0;
    padding: 30px;
}

.container {
    max-width: 1280px;
    margin: 0 auto;
}

header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding-bottom: 20px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.1);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
}

h1 {
    font-size: 2.25rem;
    font-weight: 700;
    letter-spacing: -0.5px;
    color: #f8fafc;
}

.add-habit-btn {
    padding: 12px 24px;
    background: #10b981;
    border: none;
    border-radius: 50px;
    color: white;
    font-size: 1rem;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.3s ease;
    box-shadow: 0 4px 15px rgba(16, 185, 129, 0.2);
}

.add-habit-btn:hover {
    transform: translateY(-2px);
    background: #059669;
    box-shadow: 0 6px 20px rgba(16, 185, 129, 0.3);
}

.add-habit-btn:active {
    animation: pulse 0.2s ease;
}

.habits-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: 25px;
    padding-top: 30px;
}

.habit-card {
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid rgba(255, 255, 255, 0.05);
    border-radius: 12px;
    padding: 25px;
    backdrop-filter: blur(12px);
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
    animation: fadeIn 0.5s ease;
    transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.habit-card:hover {
    transform: translateY(-5px);
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.2);
}

.habit-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 25px;
}

.habit-name {
    font-size: 1.25rem;
    font-weight: 600;
    color: #f8fafc;
}

.progress-ring {
    position: relative;
    width: 140px;
    height: 140px;
    margin: 0 auto;
}

.circle-bg {
    fill: none;
    stroke: rgba(255, 255, 255, 0.08);
    stroke-width: 12;
}

.circle-progress {
    fill: none;
    stroke-linecap: round;
    stroke-width: 12;
    transform: rotate(-90deg);
    transform-origin: 50% 50%;
    transition: stroke-dashoffset 0.6s ease-in-out;
    animation: ringGrow 0.8s ease-out;
}

.progress-text {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 1.75rem;
    font-weight: 700;
    color: #e2e8f0;
}

.streak {
    text-align: center;
    margin-top: 20px;
    font-size: 1rem;
    font-weight: 400;
    color: #94a3b8;
}

.controls {
    display: flex;
    justify-content: center;
    gap: 15px;
    margin-top: 25px;
}

.btn {
    padding: 10px 20px;
    border: none;
    border-radius: 50px;
    font-size: 1rem;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.3s ease;
}

.btn-increment {
    background: #10b981;
    color: white;
    box-shadow: 0 4px 15px rgba(16, 185, 129, 0.2);
}

.btn-increment:hover {
    background: #059669;
    transform: translateY(-2px);
    box-shadow: 0 6px 20px rgba(16, 185, 129, 0.3);
}

.btn-reset {
    background: #ef4444;
    color: white;
    box-shadow: 0 4px 15px rgba(239, 68, 68, 0.2);
}

.btn-reset:hover {
    background: #dc2626;
    transform: translateY(-2px);
    box-shadow: 0 6px 20px rgba(239, 68, 68, 0.3);
}

/* Modal Styles */
.modal {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.8);
    align-items: center;
    justify-content: center;
}

.modal-content {
    background: #1e293b;
    padding: 30px;
    border-radius: 12px;
    width: 450px;
    max-width: 90%;
    animation: fadeIn 0.3s ease;
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
}

.close {
    float: right;
    font-size: 1.75rem;
    font-weight: 600;
    color: #94a3b8;
    cursor: pointer;
    transition: color 0.2s;
}

.close:hover {
    color: #e2e8f0;
}

h2 {
    font-size: 1.5rem;
    font-weight: 600;
    margin-bottom: 20px;
    color: #f8fafc;
}

form {
    display: flex;
    flex-direction: column;
    gap: 20px;
}

input {
    padding: 12px;
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 8px;
    font-size: 1rem;
    background: rgba(255, 255, 255, 0.05);
    color: #e2e8f0;
}

input:focus {
    outline: none;
    border-color: #10b981;
    box-shadow: 0 0 5px rgba(16, 185, 129, 0.3);
}

input[type="color"] {
    padding: 5px;
    height: 50px;
    cursor: pointer;
}

button[type="submit"] {
    padding: 12px;
    background: #10b981;
    border: none;
    border-radius: 8px;
    color: white;
    font-size: 1rem;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.3s ease;
}

button[type="submit"]:hover {
    background: #059669;
    transform: translateY(-2px);
}

/* Animations */
@keyframes fadeIn {
    from { opacity: 0; transform: translateY(20px); }
    to { opacity: 1; transform: translateY(0); }
}

@keyframes ringGrow {
    from { stroke-dashoffset: 314; }
}

@keyframes pulse {
    0% { transform: scale(1); }
    50% { transform: scale(0.95); }
    100% { transform: scale(1); }
}



    Enter fullscreen mode
    


    Exit fullscreen mode
    




The CSS creates:
A sleek dark gradient background
Glass-morphism style cards with hover effects
SVG progress circles for visual feedback
Responsive grid layout that works on different screen sizes
Smooth animations throughout the UI

  
  
  Understanding the Progress Rings
The circular progress indicators are the centerpiece of our application. They're built with SVG elements:
A static background circle represents the total goal
An animated foreground circle fills based on current progress
Text in the center displays the numeric progress
The stroke-dasharray and stroke-dashoffset properties control how much of the circle appears filled in, creating a pie chart-like effect as users make progress.
  
  
  JavaScript Implementation
Now for the fun part - bringing our app to life with JavaScript!We'll organize our code with two main classes:

Habit - Blueprint for individual habits

HabitTracker - Manages the collection of habits and UI updates


class Habit {
    constructor(name, goal, color, id) {
        this.name = name;
        this.goal = goal;
        this.progress = 0;
        this.streak = 0;
        this.color = color;
        this.id = id;
        this.lastCompleted = null;
    }
}

class HabitTracker {
    constructor() {
        this.habits = JSON.parse(localStorage.getItem('habits')) || [];
        this.renderHabits();
        document.getElementById('habitForm').addEventListener('submit', (e) => this.addHabit(e));
    }

    addHabit(e) {
        e.preventDefault();
        const name = document.getElementById('habitName').value;
        const goal = parseInt(document.getElementById('habitGoal').value);
        const color = document.getElementById('habitColor').value;
        const id = Date.now();

        const habit = new Habit(name, goal, color, id);
        this.habits.push(habit);
        this.saveHabits();
        this.renderHabits();
        hideAddHabitModal();
        e.target.reset();
    }

    incrementProgress(id) {
        const habit = this.habits.find(h => h.id === id);
        if (habit.progress < habit.goal) {
            habit.progress++;
            this.updateStreak(habit);
            this.saveHabits();
            this.renderHabits();
        }
    }

    resetProgress(id) {
        const habit = this.habits.find(h => h.id === id);
        habit.progress = 0;
        habit.streak = 0;
        habit.lastCompleted = null;
        this.saveHabits();
        this.renderHabits();
    }

    updateStreak(habit) {
        const today = new Date().toDateString();
        if (habit.progress === habit.goal) {
            if (habit.lastCompleted) {
                const lastDate = new Date(habit.lastCompleted);
                const diff = Math.floor((new Date() - lastDate) / (1000 * 60 * 60 * 24));
                if (diff === 1) habit.streak++;
                else if (diff > 1) habit.streak = 1;
            } else {
                habit.streak = 1;
            }
            habit.lastCompleted = today;
        }
    }

    saveHabits() {
        localStorage.setItem('habits', JSON.stringify(this.habits));
    }

    renderHabits() {
        const grid = document.getElementById('habitsGrid');
        grid.innerHTML = '';
        this.habits.forEach(habit => {
            const percentage = (habit.progress / habit.goal) * 100;
            const circumference = 2 * Math.PI * 60; // Increased radius to 60
            const offset = circumference - (percentage / 100) * circumference;

            const card = `
                
                    
                        ${habit.name}
                    
                    
                        
                            
                            ${habit.color}"
                                stroke-dasharray="${circumference}"
                                stroke-dashoffset="${offset}">
                            
                        
                        ${habit.progress}/${habit.goal}
                    
                    Streak: ${habit.streak} days
                    
                        ${habit.id})">+
                        ${habit.id})">Reset
                    
                
            `;
            grid.innerHTML += card;
        });
    }
}

const tracker = new HabitTracker();

function showAddHabitModal() {
    document.getElementById('addHabitModal').style.display = 'flex';
}

function hideAddHabitModal() {
    document.getElementById('addHabitModal').style.display = 'none';
}



    Enter fullscreen mode
    


    Exit fullscreen mode
    




Let's examine some of the key functionality:
  
  
  Managing Streaks
One of the most motivating aspects of habit tracking is maintaining streaks. Our code handles this with smart date comparison:

updateStreak(habit) {
    const today = new Date().toDateString();
    if (habit.progress === habit.goal) {
        if (habit.lastCompleted) {
            const lastDate = new Date(habit.lastCompleted);
            const diff = Math.floor((new Date() - lastDate) / (1000 * 60 * 60 * 24));
            if (diff === 1) habit.streak++;
            else if (diff > 1) habit.streak = 1;
        } else {
            habit.streak = 1;
        }
        habit.lastCompleted = today;
    }
}



    Enter fullscreen mode
    


    Exit fullscreen mode
    




This method:
Checks if the habit goal is completed for today
Calculates days since last completion
Increments streak for consecutive days
Resets streak if there's a gap in completion

  
  
  Calculating Ring Progress
The math behind our progress rings demonstrates how powerful SVG can be for data visualization:

// Inside renderHabits method
const percentage = (habit.progress / habit.goal) * 100;
const circumference = 2 * Math.PI * 60; // Circle with radius 60
const offset = circumference - (percentage / 100) * circumference;



    Enter fullscreen mode
    


    Exit fullscreen mode
    




This calculation:
Determines the completion percentage
Calculates the circle's circumference
Determines how much of the circle to "hide" with stroke-dashoffset
Results in a visually accurate representation of progress

  
  
  Breaking Down Key Concepts

  
  
  SVG Progress Rings
SVG (Scalable Vector Graphics) gives us precise control over the progress visualization. We use two properties:

stroke-dasharray: Defines the pattern of dashes and gaps (set to the full circumference)

stroke-dashoffset: Controls how much of the pattern is hidden (decreases as progress increases)
This creates the effect of a circle filling up as progress increases.
  
  
  Local Storage for Persistence
Our app saves progress automatically using the browser's localStorage API:

saveHabits() {
    localStorage.setItem('habits', JSON.stringify(this.habits));
}



    Enter fullscreen mode
    


    Exit fullscreen mode
    




This ensures users don't lose their progress when they close their browser, making the app much more practical for daily use.
  
  
  Taking It Further
Now that you have a working habit tracker, here are some ways to enhance it:

Weekly View: Add a calendar-style weekly overview

Achievement Badges: Create milestone rewards for consistent habits

Data Visualization: Add charts showing habit completion over time

Social Sharing: Let users share their streaks on social media

PWA Conversion: Turn it into a Progressive Web App for offline use

  
  
  Code Breakdown Summary
To summarize the technical aspects of our application:
Object-Oriented Structure:
We use classes to organize our code, making it more maintainable and extendable.
Event-Driven UI:
All user interactions trigger specific methods that update both the data model and the UI.
Local Storage:
Data persistence means users can track habits over time without losing progress.
SVG Animation:
The circular progress indicators provide intuitive visual feedback.
Responsive Design:
The grid layout adjusts based on screen size for a good experience on any device.

  
  
  Conclusion
Building this habit tracker has shown us how to combine several modern web technologies to create an engaging, practical application. The visual feedback from the progress rings makes tracking habits more satisfying than simple checkboxes or text entries.I hope you've enjoyed this tutorial and found some techniques you can apply to your own projects. The combination of SVG for visual elements, OOP JavaScript for structure, and LocalStorage for persistence creates a solid foundation for many types of web applications.What habits would you track with this app? Let me know in the comments!If you liked this tutorial, follow me for more web development projects and tips. Happy coding! 💻