In this tutorial, we’ll build a full-featured Recipe App using React, Tailwind CSS, and Vite. This project is perfect for React beginners who want to learn how to manage state, use components, handle modals, and filter dynamic data.
🚀 What You’ll Build
React app powered by Vite
Styled with Tailwind CSS
Data fetched from a local recipe "database"
Features:
Search bar
Filter by categories
View recipe details in a modal
Responsive layout
🧰 Tech Stack
React 19
Tailwind CSS 4.1+
Vite
React Icons (optional)
🛠️ Step 1: Set Up with Vite
npm create vite@latest tasty-recipes
cd tasty-recipes
npm install
npm run dev
Choose React with JavaScript (no TypeScript).
🎨 Step 2: Install Tailwind CSS
Follow the Tailwind + Vite installation guide:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Update your tailwind.config.js
:
content: ["./index.html", "./src/**/*.{js,jsx}"],
Update src/index.css
:
@tailwind base;
@tailwind components;
@tailwind utilities;
🧼 Step 3: Clean Up Vite Template
Delete unnecessary files:
src/assets
folderApp.css
Boilerplate JSX in
App.jsx
🧩 Step 4: Create Components
Header.jsx
const Header = () => (
<header className="bg-gradient-to-r from-amber-500 to-orange-500 text-white shadow-md fixed top-0 left-0 w-full z-10">
<div className="container mx-auto px-4 py-4 flex justify-between items-center">
<a href="#home" className="text-2xl font-bold tracking-wide">Tasty Recipes</a>
<nav>
<ul className="flex space-x-6">
{["Home", "Recipes", "About", "Contact"].map((item, i) => (
<li key={i} className="hover:text-orange-200 cursor-pointer transition-colors duration-200">{item}</li>
))}
</ul>
</nav>
</div>
</header>
);
🧾 Step 5: Add a Local Recipe Database
Create src/db.js
:
const recipes = [
{
title: "Spaghetti Carbonara",
category: "Pasta",
image: "https://www.themealdb.com/images/media/meals/llcbn01574260722.jpg",
ingredients: ["Spaghetti", "Eggs", "Parmesan", "Pancetta", "Pepper"],
description: "A creamy, savory classic Roman pasta dish.",
instructions: "Boil pasta, cook pancetta, mix eggs & cheese, combine everything."
},
// Add more recipes...
];
export default recipes;
🧠 Step 6: Main App State Logic
In App.jsx
:
import { useState, useEffect } from 'react';
import recipes from './db';
import Header from './Header';
function App() {
const [filteredRecipes, setFilteredRecipes] = useState([]);
const [selectedRecipe, setSelectedRecipe] = useState(null);
useEffect(() => {
setFilteredRecipes(recipes);
}, []);
const handleRecipeClick = (recipe) => setSelectedRecipe(recipe);
const closeModal = () => setSelectedRecipe(null);
return (
<>
<Header />
<main className="pt-24 px-4">
<section id="recipes" className="grid gap-8 grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
{filteredRecipes.length > 0 ? (
filteredRecipes.map((recipe, i) => (
<RecipeCard key={i} recipe={recipe} onClick={() => handleRecipeClick(recipe)} />
))
) : (
<p>No recipes found.</p>
)}
</section>
{selectedRecipe && <RecipeModal recipe={selectedRecipe} onClose={closeModal} />}
</main>
</>
);
}
🍽️ Step 7: RecipeCard Component
const RecipeCard = ({ recipe, onClick }) => (
<div onClick={onClick} className="bg-white rounded-lg shadow-lg cursor-pointer overflow-hidden transition-transform hover:shadow-xl">
<img src={recipe.image} alt={recipe.title} className="w-full h-48 object-cover" />
<div className="p-4">
<h3 className="text-xl font-semibold text-gray-800 mb-2">{recipe.title}</h3>
<p className="text-gray-600">{recipe.description}</p>
</div>
</div>
);
🔍 Step 8: RecipeModal Component
const RecipeModal = ({ recipe, onClose }) => (
<div onClick={onClose} className="fixed inset-0 bg-slate-400 bg-opacity-75 flex items-center justify-center z-20">
<div onClick={e => e.stopPropagation()} className="bg-white rounded-lg shadow-lg p-6 max-w-xl w-full relative">
<button onClick={onClose} className="absolute top-4 right-4 text-gray-500 text-xl">×</button>
<h2 className="text-2xl font-bold mb-4">{recipe.title}</h2>
<img src={recipe.image} alt={recipe.title} className="w-full h-48 object-cover rounded mb-4" />
<p className="mb-4 text-gray-700">{recipe.description}</p>
<h3 className="text-xl font-semibold mb-2">Ingredients:</h3>
<ul className="list-disc list-inside space-y-1">
{recipe.ingredients.map((item, i) => (
<li key={i}>{item}</li>
))}
</ul>
</div>
</div>
);
📁 Folder Structure
src/
├── App.jsx
├── Header.jsx
├── RecipeCard.jsx
├── RecipeModal.jsx
├── db.js
├── index.css
├── main.jsx
Share this post