Build a habit tracker with Lovable: step-by-step guide to set goals, track progress, customize templates and boost productivity

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
You can build a simple habit tracker entirely inside Lovable by using Chat Mode to create and edit files, Preview to run and test the app, and the Lovable Cloud Secrets UI if you want a Supabase backend. No terminal is required for the basic app — only for advanced build/deploy workflows (see GitHub export note). Below I give step-by-step Lovable prompts you can paste into the Lovable chat to implement a localStorage-based tracker and an optional Supabase-backed upgrade.
A small React habit-tracker web app with a habits list, create/delete/toggle completion, and persistence. Two options:
Use Chat Mode edits to create/modify files (no terminal). Use Preview to run and iterate. Use Lovable Cloud Secrets UI to store SUPABASE_URL and SUPABASE_ANON\_KEY if you choose Supabase. If you need server functions or CI you’ll export/sync to GitHub (outside Lovable) — I’ll call that out where relevant.
<p><b>Goal:</b> Create a minimal React app and habit UI stored in localStorage.</p>
<p><b>Files to create/modify:</b> create these files with exact paths:</p>
<ul>
<li>package.json</li>
<li>src/main.tsx</li>
<li>src/App.tsx</li>
<li>src/pages/HabitsPage.tsx</li>
<li>src/components/HabitItem.tsx</li>
<li>src/styles.css</li>
</ul>
<p><b>Acceptance criteria (done when):</b> In Lovable Preview you see a "Habit Tracker" page, can add a habit, toggle done, and the list persists after reload.</p>
<p><b>Prompt to paste:</b></p>
\`\`\`
// Create package.json (basic metadata; Lovable may already provide a template)
// // If your project already has package.json, skip creating and just add dependencies in the file.
{
"name": "lovable-habit-tracker",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"start": "vite preview"
},
"dependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"vite": "^5.0.0",
"@vitejs/plugin-react": "^3.0.0",
"typescript": "^5.0.0"
}
}
// src/main.tsx
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
import "./styles.css";
createRoot(document.getElementById("root")!).render(
// src/App.tsx
import React from "react";
import HabitsPage from "./pages/HabitsPage";
export default function App(){
return
// src/pages/HabitsPage.tsx
import React, { useEffect, useState } from "react";
import HabitItem from "../components/HabitItem";
type Habit = { id: string; title: string; done: boolean; };
const STORAGE_KEY = "lovable.habits.v1";
export default function HabitsPage(){
const [habits, setHabits] = useState<Habit[]>([]);
const [text, setText] = useState("");
useEffect(()=>{
const raw = localStorage.getItem(STORAGE_KEY);
if(raw) setHabits(JSON.parse(raw));
},[]);
useEffect(()=>{ localStorage.setItem(STORAGE_KEY, JSON.stringify(habits)); },[habits]);
function add(){ if(!text.trim()) return; setHabits(h=>[{id:Date.now().toString(),title:text.trim(),done:false},...h]); setText(""); }
function toggle(id:string){ setHabits(h=>h.map(x=>x.id===id?{...x,done:!x.done}:x)); }
function remove(id:string){ setHabits(h=>h.filter(x=>x.id!==id)); }
return (
No habits yet.
: habits.map(h=> <HabitItem key={h.id} habit={h} onToggle={()=>toggle(h.id)} onRemove={()=>remove(h.id)}/>)}
// src/components/HabitItem.tsx
import React from "react";
export default function HabitItem({habit,onToggle,onRemove}:{habit:any,onToggle:()=>void,onRemove:()=>void}){
return (
<div className={"habit "+(habit.done?"done":"")}>
// src/styles.css
body{font-family:system-ui;padding:16px}
.app{max-width:640px;margin:0 auto}
.input-row{display:flex;gap:8px;margin-bottom:12px}
.habit{display:flex;justify-content:space-between;align-items:center;padding:8px;border:1px solid #eee;border-radius:6px;margin-bottom:6px}
.habit.done span{text-decoration:line-through;color:#888}
</li>
<li>
<b>Prompt 2 — Add tests for persistence and small polish (edit files)</b>
<p><b>Goal:</b> Ensure localStorage saving is correct and add sample seed data for first-run.</p>
<p><b>Files to modify:</b> src/pages/HabitsPage.tsx</p>
<p><b>Acceptance criteria (done when):</b> On first Preview run you see two sample habits; adding/toggling persists.</p>
<p><b>Prompt to paste:</b></p>
```
// Edit src/pages/HabitsPage.tsx near useEffect initial load to seed sample data if empty
// Replace the first useEffect with:
useEffect(()=>{
const raw = localStorage.getItem(STORAGE_KEY);
if(raw){ setHabits(JSON.parse(raw)); return; }
// seed on first run
const seed = [
{id:Date.now().toString(), title: "Wake up by 7am", done:false},
{id:(Date.now()+1).toString(), title: "Drink water", done:false}
];
setHabits(seed);
},[]);
<p><b>Goal:</b> Swap localStorage for Supabase CRUD. This requires creating a Supabase project and a table "habits" with columns (id text PK, title text, done boolean).</p>
<p><b>Files to create/modify:</b> create src/lib/supabase.ts and modify src/pages/HabitsPage.tsx to call Supabase.</p>
<p><b>Secrets/Integration steps:</b></p>
<ul>
<li>In Supabase console (outside Lovable) create a project and run table creation SQL or use GUI to create table "habits" with columns: id TEXT primary key, title TEXT, done BOOLEAN, inserted\_at TIMESTAMPTZ default now()</li>
<li>In Lovable Cloud, open <b>Secrets</b> UI and add keys: SUPABASE_URL and SUPABASE_ANON\_KEY with values from your Supabase project.</li>
</ul>
<p><b>Acceptance criteria (done when):</b> After adding Secrets, Preview loads habits from Supabase and changes persist to Supabase.</p>
<p><b>Prompt to paste:</b></p>
\`\`\`
// src/lib/supabase.ts
import { createClient } from "@supabase/supabase-js";
// Lovable Secrets will inject values via process.env at runtime
const SUPABASE_URL = process.env.SUPABASE_URL!;
const SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY!;
export const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
// Modify src/pages/HabitsPage.tsx to use supabase when process.env.SUPABASE_URL exists
// Pseudocode insertion points:
// // If SUPABASE_URL present, use Supabase queries for load/add/toggle/remove
// // Use supabase.from('habits').select('*') etc.
// // Keep localStorage fallback when secrets not set.
```
<p><b>Note:</b> Creating the Supabase table is outside Lovable (use Supabase UI). No CLI required.</p>
This prompt helps an AI assistant understand your setup and guide to build the feature
This prompt helps an AI assistant understand your setup and guide to build the feature
This prompt helps an AI assistant understand your setup and guide to build the feature

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
Build a simple, reliable habit tracker by using AI code generation for UI scaffolding and repetitive backend code, but keep core app logic, data model, secrets, and integrations under explicit control. In Lovable, iterate in Chat Mode, store runtime secrets with the Secrets UI, use Preview to test, and export/sync to GitHub only when you need terminal-level control. Prioritize a small, clear data model, secure server-side writes, idempotent APIs (to avoid duplicate completions), and good UX around missed / late completions.
Start small and explicit. Keep habits and entries separate so AI can scaffold UI without guessing business rules.
Use AI to generate components, routes, and tests, but review and lock the business logic.
Configure Supabase via Lovable Secrets, call it from server-side routes, never expose service-role keys to client.
// server/api/addEntry.js
import { createClient } from '@supabase/supabase-js'
// createClient reads secrets set in Lovable Cloud Secrets UI
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY)
export default async function handler(req, res) {
// guard method and payload
if (req.method !== 'POST') return res.status(405).end()
const { user_id, habit_id, completed_at } = req.body
// idempotent insert: ignore duplicates for same habit+day
const { data, error } = await supabase
.from('habit_entries')
.upsert(
{ user_id, habit_id, completed_at },
{ onConflict: ['user_id', 'habit_id', 'completed_at::date'] }
)
if (error) return res.status(500).json({ error: error.message })
res.json(data)
}
Because there's no terminal, use Lovable features to iterate safely.
Test idempotency, timezones, duplicate clicks, and offline behavior.
Export when you need DB migrations, advanced CI, or to run local scripts. Before exporting, ensure Secrets are set in both Lovable and your CI.
From startups to enterprises and everything in between, see for yourself our incredible impact.
Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We’ll discuss your project and provide a custom quote at no cost.