Building a Full-Stack Habit Tracker with Claude Code - Part 1: From Idea to MVP
Introduction: Why Another Habit Tracker?
I started this a demo project to learn how can claude code help as an AI dev partner , I used this demo project to understand and learn different ways to effectively use Claude code and its feature
Here are some important topics that I learned while making this project
Claude.md file
context window
Tools and permission
Planning and Thinking
Slash Command
Custom Slash command
MCP Server
Sub Agents
Follow along to see how I used this learning while creating this Habit Tracker app in React.
Here is what the app does:
- ๐ Fast - No login, no setup, just start tracking
- ๐จ Beautiful - A UI I'd actually want to use daily
- ๐ Insightful - Show me trends, not just checkboxes
- ๐ Private - My data stays in my browser
I built this entire app with Claude Code as my AI pair programmer. And I'm going to show you exactly how.
But first lets start with basics
What is Claude Code?
Before we dive in, let me explain what made this project possible. Claude Code is Anthropic's official CLI tool that brings Claude AI directly into your development workflow. Think of it as an AI pair programmer that can:
- Read and understand your entire codebase
- Write, edit, and refactor code
- Run terminal commands
- Debug issues
- Suggest architecture improvements
It's not just a chatbot - it's a full development partner.
The Game Plan
Here's what I wanted to build:
Core Features (MVP):
- โ Add, edit, and delete habits
- โ Track habits by date
- โ Four status types: Completed, To Do, Not Completed, Skipped
- โ Data persistence with localStorage
- โ Dark/light theme toggle
- โ Basic analytics and trends
Tech Stack:
- React (latest version)
- React Router for navigation
- Pure CSS (no frameworks - keeping it simple)
- localStorage for data persistence (no backend needed!)
Let's build it! ๐ ๏ธ
Phase 1: Project Setup (The Boring-But-Essential Part)
I started by asking Claude Code to initialize a React project:
npx create-react-app habit-tracker
cd habit-tracker
npm install react-router-dom
Claude suggested this project structure, which turned out to be perfect:
src/
โโโ components/ # Reusable UI components
โโโ pages/ # Route components (Home, Trends, About)
โโโ hooks/ # Custom React hooks
โโโ styles/ # CSS modules
โโโ utils/ # Helper functions
๐ก Learning #1: Having a clear structure from day one saves hours of refactoring later.
Phase 2: Building the Data Model
The first real code we wrote together was the data model. I described what I wanted, and Claude helped me design a simple but extensible structure:
// Each habit entry looks like this:
{
id: "1712345678901", // Timestamp-based unique ID
habit: "Morning workout", // What you're tracking
date: "2026-04-08", // ISO date format
status: "Completed" // Completed | To Do | Not Completed | Skipped
}
Clean, simple, and stored as a JSON array in localStorage. No over-engineering!
Phase 3: State Management Strategy
Here's where Claude Code really shined. Instead of reaching for Redux or Zustand, it suggested a simple but powerful approach:
// App.js - The main component
function App() {
// Load habits from localStorage on mount
const [habits, setHabits] = useState(() => {
const saved = localStorage.getItem('habit-tracker-habits');
return saved ? JSON.parse(saved) : [];
});
// Auto-save to localStorage whenever habits change
useEffect(() => {
localStorage.setItem('habit-tracker-habits', JSON.stringify(habits));
}, [habits]);
// ... rest of the app
}
๐ก Learning #2: You don't always need a state management library. React hooks + localStorage can handle a lot!
Phase 4: The HabitForm Component
This was our first real UI component. I asked Claude to create a form that felt intuitive, and here's what we built together:
// src/components/HabitForm.js
import { useState } from 'react';
import TextInput from './TextInput';
import DateSelector from './DateSelector';
import StatusSelector from './StatusSelector';
function HabitForm({ onAddHabit }) {
const [habit, setHabit] = useState('');
const [date, setDate] = useState(new Date().toISOString().split('T')[0]);
const [status, setStatus] = useState('To Do');
const handleSubmit = (e) => {
e.preventDefault();
if (!habit.trim()) return; // Basic validation
onAddHabit({
id: Date.now().toString(),
habit: habit.trim(),
date,
status
});
// Clear form
setHabit('');
setStatus('To Do');
};
return (
<form className="habit-form" onSubmit={handleSubmit}>
<h2>โ Add New Habit</h2>
<TextInput
label="Habit"
value={habit}
onChange={(e) => setHabit(e.target.value)}
placeholder="Enter habit (e.g., Morning workout, Read 30 mins)"
/>
<div className="form-row">
<DateSelector
value={date}
onChange={(e) => setDate(e.target.value)}
/>
<StatusSelector
value={status}
onChange={(e) => setStatus(e.target.value)}
/>
</div>
<button type="submit" className="btn-primary">
โ Add Habit
</button>
</form>
);
}
Key Design Decisions:
Composition over monolith: Breaking the form into smaller components (TextInput, DateSelector, StatusSelector) made it easier to maintain and reuse.
Emojis as icons: Instead of icon libraries, we used emojis. Lightweight and universally supported!
Immediate feedback: The form clears immediately after submission, giving users a sense of accomplishment.
๐ก Learning #3: Small, focused components are easier for AI (and humans!) to understand and modify.
Phase 5: Displaying Habits - The HabitList Component
Now came the fun part - displaying habits in a beautiful, organized way. I wanted habits grouped by date, with special labels for "Today" and "Yesterday".
Claude Code helped me build this smart grouping logic:
// src/components/HabitList.js
function HabitList({ habits, onEdit, onDelete }) {
// Group habits by date
const groupedHabits = habits.reduce((groups, habit) => {
const date = habit.date;
if (!groups[date]) {
groups[date] = [];
}
groups[date].push(habit);
return groups;
}, {});
// Sort dates in descending order (newest first)
const sortedDates = Object.keys(groupedHabits).sort((a, b) =>
new Date(b) - new Date(a)
);
// Format date labels
const formatDateLabel = (dateString) => {
const date = new Date(dateString);
const today = new Date();
const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1);
if (dateString === today.toISOString().split('T')[0]) {
return 'Today';
} else if (dateString === yesterday.toISOString().split('T')[0]) {
return 'Yesterday';
} else {
return date.toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
});
}
};
if (habits.length === 0) {
return (
<div className="empty-state">
<span className="empty-icon">๐</span>
<h2>No habits tracked yet</h2>
<p>Add your first habit to get started!</p>
</div>
);
}
return (
<div className="habit-list">
<h2>Your Habits</h2>
{sortedDates.map(date => (
<div key={date} className="date-group">
<div className="date-header">
<h3>{formatDateLabel(date)}</h3>
<span className="habit-count">
{groupedHabits[date].length} {groupedHabits[date].length === 1 ? 'habit' : 'habits'}
</span>
</div>
<div className="habits-grid">
{groupedHabits[date].map(habit => (
<HabitCard
key={habit.id}
habit={habit}
onEdit={onEdit}
onDelete={onDelete}
/>
))}
</div>
</div>
))}
</div>
);
}
Why this works:
-
Smart date grouping: Uses
reduce()to organize habits efficiently - User-friendly labels: "Today" and "Yesterday" are more intuitive than dates
- Empty state: Always handle the zero-state gracefully
- Responsive grid: CSS Grid makes the layout adapt to any screen size
Phase 6: The Theme System (Making It Pretty)
I'm a sucker for dark mode. I asked Claude to implement a theme system that would:
- Toggle between light and dark
- Remember user preference
- Apply smoothly without flashing
Here's the elegant solution we built:
// src/hooks/useTheme.js
import { useState, useEffect } from 'react';
function useTheme() {
const [theme, setTheme] = useState(() => {
const saved = localStorage.getItem('habit-tracker-theme');
return saved || 'light';
});
useEffect(() => {
// Apply theme to root element
document.documentElement.setAttribute('data-theme', theme);
// Save to localStorage
localStorage.setItem('habit-tracker-theme', theme);
}, [theme]);
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return { theme, toggleTheme };
}
export default useTheme;
And the CSS magic:
/* src/index.css */
:root[data-theme="light"] {
--bg-primary: #ffffff;
--bg-secondary: #f5f5f5;
--text-primary: #333333;
--text-secondary: #666666;
--border-color: #e0e0e0;
--shadow: rgba(0, 0, 0, 0.1);
--button-bg: #4a90e2;
--button-hover: #357abd;
}
:root[data-theme="dark"] {
--bg-primary: #1a1a1a;
--bg-secondary: #2d2d2d;
--text-primary: #e0e0e0;
--text-secondary: #a0a0a0;
--border-color: #404040;
--shadow: rgba(0, 0, 0, 0.5);
--button-bg: #4a90e2;
--button-hover: #357abd;
}
/* Now all components just use CSS variables */
.habit-card {
background: var(--bg-primary);
color: var(--text-primary);
border: 1px solid var(--border-color);
box-shadow: 0 2px 4px var(--shadow);
}
๐ก Learning #4: CSS custom properties + the data-theme attribute = the cleanest theme system.
Phase 7: Analytics & Trends
This is where the app went from "useful" to "actually cool". I wanted to show users:
- Current streaks
- Completion rates
- Trends over time
Claude helped me build a utility file for all the calculations:
// src/utils/habitAnalytics.js
// Calculate current streak for a habit
export const calculateCurrentStreak = (habitEntries) => {
if (!habitEntries.length) return 0;
// Sort by date descending
const sorted = [...habitEntries].sort((a, b) =>
new Date(b.date) - new Date(a.date)
);
let streak = 0;
const today = new Date().toISOString().split('T')[0];
let checkDate = new Date(today);
for (const entry of sorted) {
const entryDate = entry.date;
const expectedDate = checkDate.toISOString().split('T')[0];
if (entryDate !== expectedDate) {
break; // Streak broken
}
if (entry.status === 'Completed') {
streak++;
checkDate.setDate(checkDate.getDate() - 1);
} else {
break; // Not completed = streak broken
}
}
return streak;
};
// Calculate completion rate for a habit
export const calculateCompletionRate = (habitEntries) => {
if (!habitEntries.length) return 0;
const completed = habitEntries.filter(h => h.status === 'Completed').length;
return Math.round((completed / habitEntries.length) * 100);
};
// Group habits by name (case-insensitive)
export const groupHabitsByName = (habits) => {
return habits.reduce((groups, habit) => {
const name = habit.habit.toLowerCase();
if (!groups[name]) {
groups[name] = [];
}
groups[name].push(habit);
return groups;
}, {});
};
These pure functions made testing easy and the code maintainable.
Phase 8: Routing with React Router
Finally, I wanted to organize the app into multiple pages:
- Home: Main tracker interface
- Trends: Analytics dashboard
- About: Info about the app
// src/App.js
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Navigation from './components/Navigation';
import Home from './pages/Home';
import Trends from './pages/Trends';
import About from './pages/About';
function App() {
// ... state management code ...
return (
<Router>
<div className="App">
<header className="app-header">
<h1>Habit Tracker</h1>
<Navigation />
<ThemeToggle />
</header>
<main>
<Routes>
<Route path="/" element={
<Home
habits={habits}
onAddHabit={handleAddHabit}
onUpdateHabit={handleUpdateHabit}
onDeleteHabit={handleDeleteHabit}
/>
} />
<Route path="/trends" element={<Trends habits={habits} />} />
<Route path="/about" element={<About />} />
</Routes>
</main>
</div>
</Router>
);
}
๐ก Learning #5: Keep routing simple. Pass data as props instead of reaching for context too early.
Working with Claude Code: What I Learned
After spending hours pair programming with Claude Code, here are my biggest takeaways:
1. Be Specific About What You Want
โ Bad: "Add a form"
โ
Good: "Create a form component that accepts habit name, date, and status, with validation and clear button"
2. Let Claude Suggest Architecture
I often asked: "What's the best way to structure this?" Claude's suggestions were consistently better than my first instincts.
3. Iterate in Small Steps
Instead of "build the entire app", I worked feature by feature. This made reviewing code easier and kept momentum high.
4. Ask for Explanations
When Claude wrote code I didn't understand, I asked "Why did you do it this way?" The explanations taught me new patterns.
5. Review Everything
AI is powerful, but not infallible. I read every line of code Claude wrote and tested thoroughly.
The MVP After One Day
After about 6-8 hours of focused development with Claude Code, I had:
โ
A fully functional habit tracker
โ
Clean, maintainable code
โ
Dark/light theme support
โ
Data persistence with localStorage
โ
Responsive design
โ
Basic analytics
Lines of code written: ~1,200
Number of manual file operations: Almost zero
Time saved vs coding alone: Estimated 60-70%
What's Next?
In Part 2, I'll cover:
- ๐จ Adding an 8-category system with emoji icons
- ๐ Building comprehensive analytics with streak tracking
- ๐ Creating beautiful summary cards with progress bars
- ๐ Deploying to Vercel with proper configuration
- ๐งช Testing with Playwright MCP (170+ test cases!)
- ๐ Generating comprehensive documentation
Try It Yourself
Want to build along? Here's the starter template we used:
npx create-react-app habit-tracker
cd habit-tracker
npm install react-router-dom
npm start
Then open Claude Code and start with: "Help me build a habit tracking app with React. Let's start with the data model."
Conclusion
Building with Claude Code felt like having a senior developer sitting next to me, ready to:
- Write boilerplate instantly
- Suggest better patterns
- Explain complex concepts
- Catch bugs before I ran the code
Is it perfect? No. I still reviewed every line and made adjustments.
Is it faster? Absolutely. What would have taken me a weekend took an evening.
Did I learn? More than I expected. Claude's explanations taught me new React patterns and best practices.
In Part 2, we'll take this MVP and turn it into a production-ready app with advanced features, comprehensive testing, and real deployment. Stay tuned!
Resources:
Questions? Drop them in the comments below! I'll be responding to all of them.
Found this helpful? Give it a โค๏ธ and follow me for Part 2!
Part 2 coming next week: "Advanced Features, Testing, and Deployment"
Top comments (0)