- Stack
-
- React
- TypeScript
- Vite
- Progressive Web App
- Mantine
- Source code
- marekh19/zentodo
- View the app
- zentodo.marek.work
I use this app as a shopping list when going to supermarket - Me
What’s Special About This Todo App?
Most todo apps are just “Hello World” examples. This one has a few interesting technical features that make it worth looking at.
The Cool Parts
1. Custom Zustand Persistence Driver
Instead of the usual localStorage, this app uses IndexedDB through a custom storage
driver:
export const storage: StateStorage = {
getItem: async (id: string): Promise<string | null> => {
const value = await getItem(id)
return value ? JSON.stringify(value) : null
},
setItem: async (id: string, value: string): Promise<void> => {
const parsedValue = JSON.parse(value)
await setItem(id, parsedValue)
},
removeItem: async (id: string): Promise<void> => {
await removeItem(id)
},
}The IndexedDB wrapper is clean and simple:
const dbPromise = openDB(DB_NAME, DB_VERSION, {
upgrade(db) {
db.createObjectStore(STORE_NAME)
},
})
export const setItem = async (key: string, value: unknown) => {
const db = await dbPromise
await db.put(STORE_NAME, value, key)
}2. Smart Todo Store with Timestamps
The Zustand store handles completion timestamps automatically:
setIsCompleted: (id, isCompleted) =>
set((state) => ({
todos: state.todos.map((todo) =>
todo.id === id
? {
...todo,
isCompleted: isCompleted,
completedAt: isCompleted ? new Date() : null,
}
: todo
),
})),3. Elegant Form Handling with Mantine
The add todo form uses Mantine’s form hook with validation:
const form = useForm<FormValues>({
mode: 'uncontrolled',
initialValues: { title: '' },
validate: {
title: (value) =>
value.trim().length < MIN_TASK_TITLE_LENGTH
? 'Todo must have at least 2 letters'
: null,
},
})4. Mobile-First Drawer Pattern
Todo items open in a bottom drawer (perfect for mobile):
<Drawer
opened={isOpen}
onClose={close}
position="bottom"
size="xs"
withCloseButton={false}
>
<TodoDetail todo={todo} />
</Drawer>5. Smart Todo Filtering and Sorting
The useTodos hook provides filtered lists with automatic sorting:
const getFilteredTodos = useMemo(() => {
return (type: TodoListType) => {
const filteredTodos =
type === 'todo'
? todos.filter((todo) => !todo.isCompleted)
: todos.filter((todo) => todo.isCompleted)
return sortByCreatedAt(filteredTodos)
}
}, [todos])PWA Features
The app is fully installable with:
- Service worker for offline functionality
- App icons in multiple sizes
- Firebase messaging setup (ready for future push notifications)
- Custom Vite build configuration for service workers
Screenshots


Conclusion
This isn’t just another todo app tutorial. It’s a PWA with some thoughtful technical implementations, particularly the custom IndexedDB persistence layer for offline storage.