In this tutorial, we’ll walk through the process of creating a simple to-do list application using Zustand for state management, Tailwind CSS for styling, and Next.js for the project setup. By the end of this tutorial, you’ll have a fully functional to-do list app that you can customize to fit your needs.

Prerequisites

Before we get started, make sure you have the following:

Node.js and npm (or yarn) installed on your machineBasic understanding of React.js

Setting Up the Project

First, let’s create a new Next.js project and install the necessary dependencies:

npx create-next-app todo-next-zustand
cd todo-next-zustand
npm install zustand tailwindcss – save

Next, configure Tailwind CSS in your Next.js project by creating a tailwind.config.js file and adding the following configuration:

import type { Config } from “tailwindcss”;

const config: Config = {
content: [
“./pages/**/*.{js,ts,jsx,tsx,mdx}”,
“./components/**/*.{js,ts,jsx,tsx,mdx}”,
“./app/**/*.{js,ts,jsx,tsx,mdx}”,
],
theme: {
extend: {
backgroundImage: {
“gradient-radial”: “radial-gradient(var(–tw-gradient-stops))”,
“gradient-conic”:
“conic-gradient(from 180deg at 50% 50%, var(–tw-gradient-stops))”,
},
},
},
plugins: [],
};
export default config;

Creating the State Management with Zustand

Zustand is a small, fast, and scalable state management library for React. Let’s create a Zustand store to manage our to-do list state:

import { create } from “zustand”;

type Todo = {
id: number;
text: string;
completed: boolean;
};

type TodoStore = {
todos: Todo[];
addTodo: (text: string) => void;
toggleTodo: (id: number) => void;
deleteTodo: (id: number) => void;
};

const useTodoStore = create<TodoStore>((set) => ({
todos: [],
addTodo: (text) =>
set((state) => ({
todos: […state.todos, { id: Date.now(), text, completed: false }],
})),
toggleTodo: (id) =>
set((state) => ({
todos: state.todos.map((todo) =>
todo.id === id ? { …todo, completed: !todo.completed } : todo
),
})),
deleteTodo: (id) =>
set((state) => ({ todos: state.todos.filter((todo) => todo.id !== id) })),
}));

export default useTodoStore;

Building the User Interface with React Components

Now let’s create React components for our to-do list app:

import useTodoStore from “@/util/useTodoStore”;
import Image from “next/image”;
import { useState } from “react”;

export default function Home() {
const [text, setText] = useState(“”);
const todos = useTodoStore((state) => state.todos);
const addTodo = useTodoStore((state) => state.addTodo);
const toggleTodo = useTodoStore((state) => state.toggleTodo);
const deleteTodo = useTodoStore((state) => state.deleteTodo);

const handleAddTodo = () => {
if (text.trim() !== “”) {
addTodo(text);
setText(“”);
}
};

return (
<div className=”min-h-screen bg-gray-100 flex flex-col justify-center py-12 px-6 sm:px-6 lg:px-8″>
<div className=”sm:mx-auto sm:w-full sm:max-w-md”>
<h1 className=”text-3xl font-extrabold text-center text-gray-900 mb-8″>
Todo List made from Zustand
</h1>
<Image
src=”https://images.unsplash.com/photo-1714244322811-f1387dc93909?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxlZGl0b3JpYWwtZmVlZHw2fHx8ZW58MHx8fHx8″
alt=”Beautiful Image”
width={800}
height={400}
className=”mb-8 rounded-lg shadow-md”
/>{” “}
<div className=”mt-6″>
<input
type=”text”
value={text}
onChange={(e) => setText(e.target.value)}
className=”w-full px-4 py-3 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 border-gray-300 bg-white text-gray-900″
placeholder=”Enter a todo”
/>
</div>
<div className=”mt-6″>
<button
onClick={handleAddTodo}
className=”w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-base font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500″
>
Add Todo
</button>
</div>
<ul className=”mt-8 divide-y divide-gray-200″>
{todos.map((todo) => (
<li key={todo.id} className=”py-4″>
<div className=”flex items-center justify-between”>
<p
className={`text-lg cursor-pointer ${
todo.completed
? “line-through text-gray-400”
: “text-gray-900”
}`}
onClick={() => toggleTodo(todo.id)}
>
{todo.text}
</p>
<button
onClick={() => deleteTodo(todo.id)}
className=”text-red-500 hover:text-red-600 focus:outline-none”
>
Delete
</button>
</div>
</li>
))}
</ul>
</div>
</div>
);
}

As you can see in the code snippet above, we are using our Zustand store useTodoStore in our component.

Styling the Application with Tailwind CSS

You can style your component as per your requirement using TailwindCSS.

Complete source code: https://github.com/mansern/nextjs-zustand-todo

Conclusion

Congratulations! You’ve successfully built a simple to-do list application using Zustand, Tailwind CSS, and Next.js. You can further customize and expand this application by adding features like editing todos, filtering todos, or persisting todos to a database.

Learned something new? Comments and feedback always make the writer happy. Happy coding!

Building a To-Do List App with Zustand, Tailwind CSS, and Next.js was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.

​ Level Up Coding – Medium

about Infinite Loop Digital

We support businesses by identifying requirements and helping clients integrate AI seamlessly into their operations.

Gartner
Gartner Digital Workplace Summit Generative Al

GenAI sessions:

  • 4 Use Cases for Generative AI and ChatGPT in the Digital Workplace
  • How the Power of Generative AI Will Transform Knowledge Management
  • The Perils and Promises of Microsoft 365 Copilot
  • How to Be the Generative AI Champion Your CIO and Organization Need
  • How to Shift Organizational Culture Today to Embrace Generative AI Tomorrow
  • Mitigate the Risks of Generative AI by Enhancing Your Information Governance
  • Cultivate Essential Skills for Collaborating With Artificial Intelligence
  • Ask the Expert: Microsoft 365 Copilot
  • Generative AI Across Digital Workplace Markets
10 – 11 June 2024

London, U.K.