A fully interactive, Notion-inspired habit tracking component built with React, Tailwind CSS, and Lucide icons. Features a responsive table layout, light/dark mode, and a complete set of functional toolbar actions.
| Dark Mode | Light Mode |
|---|---|
| Full table with checkboxes, progress bar, per-column totals | Same layout with light theme toggled via Sun/Moon button |
- Weekly habit grid — 7 rows (Mon–Sun) × N habit columns with toggle checkboxes
- Live progress bar — shows % of all habits completed for the week
- Per-column mini progress bars in the footer row
- Running totals —
X/7count per column,TOTAL X/35overall
| Button | Action |
|---|---|
| Filter | Dropdown to filter rows to only days where a specific habit is checked |
| Sort | Sort rows by name (A→Z / Z→A) or by completion count (most/least) |
| ⚡ Zap | Bulk toggle — checks all habits if any are unchecked, unchecks all if all are checked |
| Search | Reveals an inline search bar to filter rows by day name |
| Settings | Modal to show/hide columns, delete columns, add new columns, or reset all |
| ☀/🌙 Theme | Toggles between dark and light mode |
| New | Opens a modal to add a custom row with any label |
- Check all habits for that day
- Clear all habits for that day
- Duplicate the row
- Move row up / Move row down
- Delete row
- Sort ascending / descending
- Hide column
- Delete column
Opens a modal with a text input for the column name and an icon picker (8 options: Dumbbell, Brain, Moon, Pen, Book, Heart, Activity, Zap).
A check icon appears in the + cell on row hover — clicking it marks all visible habits for that day in one click.
- Reset — clears all checkboxes across the entire week
- Add habit — shortcut to open the Add Column modal
- React —
useState,useRef,useEffect - Tailwind CSS — all styling via utility classes (no separate CSS file)
- Lucide React — icons throughout (
lucide-react@0.263.1) - No other runtime dependencies
# If using npm
npm install lucide-react
# Tailwind CSS must be configured in your project
# https://tailwindcss.com/docs/installationThen drop HabitTracker.jsx into your components folder and import:
import { Component } from "./HabitTracker";
export default function App() {
return <Component />;
}Note: The component uses a named export
Component(not a default export) to match platform conventions.
HabitTracker.jsx
├── useClickOutside() — hook: closes dropdowns when clicking outside
├── DropdownMenu — reusable context/dropdown menu
├── NotionCheckbox — styled checkbox with checked/unchecked states
├── Modal — generic modal wrapper with title + close button
└── Component — main exported component
├── State
│ ├── habits[] — array of day rows with boolean per habit key
│ ├── columns[] — array of column definitions (key, label, icon, visible)
│ ├── sortConfig — { key, dir } or null
│ ├── filterConfig — { key } or null
│ └── UI flags — showSearch, showFilterMenu, modals, rowMenu, etc.
├── Computed
│ ├── visibleCols — columns filtered by visible: true
│ ├── displayedHabits — habits after search + filter + sort applied
│ ├── pct — weekly completion percentage
│ └── totals — per-column and overall checked counts
└── Handlers
├── toggleHabit()
├── handleAddColumn / handleDeleteColumn / handleToggleColumnVis()
├── handleAddRow / handleDeleteRow / handleDuplicateRow / handleClearRow()
├── handleMoveRow() — swaps rows up/down
├── handleCheckAllForDay()
├── handleToggleAll() — bulk check/uncheck
├── handleResetAll()
└── applySort()
Edit the DEFAULT_COLUMNS array at the top of the file:
const DEFAULT_COLUMNS = [
{ key: "workout", label: "Workout", icon: Dumbbell, visible: true },
{ key: "water", label: "Hydration", icon: Zap, visible: true },
// add more...
];Edit the DAYS array:
const DAYS = ["Monday", "Tuesday", ..., "Sunday"];
// or use custom labels:
const DAYS = ["Day 1", "Day 2", ..., "Day 7"];Set useState(false) to start in light mode:
const [dark, setDark] = useState(false);Extend ICON_OPTIONS with any Lucide icon:
import { Flame } from "lucide-react";
const ICON_OPTIONS = [
...existing,
{ label: "Flame", icon: Flame },
];- All checkboxes have
aria-labelattributes - Theme toggle has
aria-label="Toggle theme" - Keyboard support:
Entersubmits both Add Column and Add Row modals - Click-outside closes all dropdowns and context menus
[explore this component on 21st.dev community] (https://21st.dev/aryankumar06/aryan/default)
MIT