In this tutorial, we will build a simple, yet functional, Todo App using React, Vite, and Tailwind CSS. If you're looking to quickly prototype or create a polished application, these tools offer a great combination of speed, flexibility, and ease of use.
Why React, Vite, and Tailwind?
React is a powerful library for building user interfaces, and its component-based architecture makes it perfect for handling individual UI features.
Vite is a next-generation frontend tool that significantly speeds up the development process by providing instant server starts and lightning-fast HMR (Hot Module Replacement).
Tailwind CSS is a utility-first CSS framework that allows you to build modern, responsive designs with ease and flexibility, without writing custom CSS from scratch.
Setting Up the Project with Vite
First, let's set up our project using Vite.
Open your terminal and run:
npm create vite@latest todo-app cd todo-app npm install
Next, install the necessary dependencies for React and Tailwind CSS:
npm install react react-dom npm install -D tailwindcss postcss autoprefixer npx tailwindcss init
Configure Tailwind by adding the following to your
tailwind.config.js
file:module.exports = { content: [ './index.html', './src/**/*.{js,jsx,ts,tsx}', ], theme: { extend: {}, }, plugins: [], }
Now, include Tailwind CSS in your
index.css
:@tailwind base; @tailwind components; @tailwind utilities;
You're ready to start developing the app! Run
npm run dev
to start the development server.
Building the Todo App
Let’s break down the Todo app step by step.
1. Creating the Main TodoApp Component
The TodoApp
component will manage the core functionality of the app: adding, displaying, and managing todos.
import { useState, useEffect } from "react";
import { v4 as uuidv4 } from "uuid";
import TodoInput from "./TodoInput";
import TodoList from "./TodoList";
function TodoApp() {
const [todo, setTodo] = useState("");
const [todos, setTodos] = useState([]);
const [showFinished, setShowFinished] = useState(true);
useEffect(() => {
const todoString = localStorage.getItem("todos");
if (todoString) {
setTodos(JSON.parse(todoString));
}
}, []);
useEffect(() => {
localStorage.setItem("todos", JSON.stringify(todos));
}, [todos]);
const handleAdd = (newTodo) => {
setTodos([...todos, { id: uuidv4(), todo: newTodo, isCompleted: false }]);
setTodo("");
};
const toggleFinished = () => setShowFinished(!showFinished);
return (
<div className="container mx-auto md:my-5 rounded-md shadow-2xl p-5 bg-blue-200 md:min-h-[90vh] md:w-2/5 w-full">
<div className="flex justify-center bg-blue-900 text-white py-2 rounded-md">
<span className="font-bold text-xl mx-9">Todo App</span>
</div>
<TodoInput todo={todo} setTodo={setTodo} handleAdd={handleAdd} />
<div className="text-center">
<input
type="checkbox"
checked={showFinished}
onChange={toggleFinished}
/> Show Finished
</div>
<TodoList todos={todos} showFinished={showFinished} />
</div>
);
}
export default TodoApp;
2. Todo Input Component
This component handles the input for adding new todos.
function TodoInput({ todo, setTodo, handleAdd }) {
const handleChange = (e) => setTodo(e.target.value);
return (
<div className="addTodo my-5 text-center">
<h2 className="text-xl font-bold">Add a Todo Item</h2>
<input
type="text"
className="w-full mt-5 rounded-md p-2"
value={todo}
onChange={handleChange}
/>
<button
onClick={() => handleAdd(todo)}
className="bg-blue-800 hover:bg-blue-900 text-white p-2 py-1 my-6 rounded-md font-bold mx-6 w-20"
disabled={todo.length === 0}
>
Save
</button>
</div>
);
}
export default TodoInput;
3. Todo List Component
This component displays all the todos and allows marking them as completed or deleting them.
function TodoList({ todos, showFinished }) {
const handleCheckbox = (id) => {
let index = todos.findIndex((item) => item.id === id);
let newTodos = [...todos];
newTodos[index].isCompleted = !newTodos[index].isCompleted;
setTodos(newTodos);
};
const handleDelete = (id) => {
setTodos(todos.filter((item) => item.id !== id));
};
return (
<div className="todos">
{todos.length === 0 && <p className="m-5">Nothing to Do!</p>}
{todos
.filter((item) => showFinished || !item.isCompleted)
.map((item) => (
<div
key={item.id}
className={`todo flex w-full my-3 justify-between items-center ${
item.isCompleted ? "bg-green-200" : "bg-blue-100"
} p-3 border-2 border-blue-300 rounded-md hover:bg-blue-300`}
>
<input
type="checkbox"
checked={item.isCompleted}
onChange={() => handleCheckbox(item.id)}
/>
<div className={item.isCompleted ? "line-through" : ""}>
{item.todo}
</div>
<button
onClick={() => handleDelete(item.id)}
className="bg-blue-800 hover:bg-blue-900 text-white p-2 rounded-md"
>
Delete
</button>
</div>
))}
</div>
);
}
export default TodoList;
4. Adding Tailwind CSS for Styling
Thanks to Tailwind CSS, we can quickly style our components without writing traditional CSS. Throughout the app, we use utility classes like bg-blue-200
, rounded-md
, and text-center
to create a clean and responsive design.
Conclusion
And that’s it! You now have a fully functional Todo app built using React, Vite, and Tailwind CSS. This setup not only makes your development process fast but also helps in maintaining a clean and scalable codebase.
Feel free to experiment and extend this app by adding more features like filtering, search, or even connecting it to a backend service.
https://todoappreactss.netlify.app
I hope this article helps you get started with React, Vite, and Tailwind CSS. Let me know your thoughts or any improvements you make to your Todo app!