Grocery Website Template using HTML, Tailwind CSS, and JavaScript

Faraz

By Faraz -

Learn how to create a simple grocery website template using HTML, TailwindCSS, and JavaScript with step-by-step guide for beginners.


grocery-website-template-using-html-tailwind-css-and-javascript.webp

Table of Contents

  1. Project Introduction
  2. HTML Code
  3. CSS Code
  4. JavaScript Code
  5. Conclusion
  6. Preview

Creating a grocery website is a great project for beginners in web development. With just HTML, TailwindCSS, and a little JavaScript, you can build a modern and responsive template for a grocery store. This guide uses easy-to-understand language so even a beginner can follow along smoothly.

Prerequisites

Before you start, you need to have a few things ready:

  • Basic knowledge of HTML and CSS
  • TailwindCSS CDN or setup
  • A code editor like VS Code
  • A basic understanding of JavaScript
  • A browser to test your site

Source Code

Step 1 (HTML Code):

To get started, we will first need to create a basic HTML file. In this file, we will include the main structure for our grocery website. Here's a detailed breakdown of the various parts of the code:

1. <!DOCTYPE html>

  • Declares the document type as HTML5.

2. <html lang="en">

  • Root element for the page.
  • lang="en" specifies the language is English.

3. <head>...</head>

Sets metadata and loads external resources:

  • <meta charset="UTF-8"> → Sets character encoding to UTF-8.
  • <meta name="viewport" content="width=device-width, initial-scale=1.0"> → Makes the layout responsive on mobile.
  • <title>FreshMart - Your Online Grocery Store</title> → Sets the page title shown in the browser tab.

Loads external styles and scripts:

  • Tailwind CSS: https://cdn.tailwindcss.com → for utility-first CSS styling.
  • Google Fonts: Uses the "Inter" font.
  • Swiper CSS: Used for sliders (carousels).
  • Font Awesome: Loads icons (e.g., cart icon).
  • styles.css: A custom stylesheet for further styling.

4. <body class="bg-gray-50 text-gray-800">

  • The body of the page with a light gray background and dark text.

5. <header id="header">...</header>

Sticky Navigation Bar (Fixed at top)

  • Contains the site name, navigation links, and cart icons for both desktop and mobile.
  • Tailwind classes are used for responsive layout, styling, and animations.

Desktop Nav (md:flex)

  • Links: Home, Products, Categories, About, Contact
  • Cart Button:
    • Contains an SVG shopping cart icon.
    • Cart item count (e.g., 0) in a red badge.
    • Total cart price (e.g., $0.00).

Mobile Nav

  • Shows icons only on smaller screens (md:hidden).
  • A hamburger menu opens a hidden mobile menu using JavaScript.
  • Mobile cart button is also present with count and price display.

6. <div id="mobile-menu" ...>

  • A collapsible menu for mobile devices.
  • Same links as desktop, with hover background color.

7. <main>...</main>

This is where the actual page content begins.

Section: Hero Banner (<section id="home">)

  • Uses a gradient background.
  • Split into two columns (on larger screens):
    1. Text block: Heading, paragraph, and "Shop Now" button.
    2. Image block: An image of groceries.

Section: Popular Products (<section id="popular-products">)

  • Displays a Swiper.js slider of popular items.
  • Each product is in a swiper-slide with:
    • Image
    • Name
    • Price
    • Star rating
    • "Add to Cart" button
  • Product data is embedded using data- attributes:
    data-id="apple-organic" data-name="Organic Apples" data-price="3.99" data-image="organic-apples.png"

8. Products Section (#products)

<section id="products" class="py-16 bg-white">
  • This section displays featured products.
  • Uses padding (py-16) and white background.
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
  • A responsive grid layout: 1 column on small screens, 2 on tablets, 4 on desktops.
  • gap-8 adds spacing between grid items.
<div class="product-card ..." data-id="..." data-name="..." data-price="..." data-image="...">
  • Each product is wrapped in a div with custom data- attributes used for JavaScript (e.g., cart functions).
  • Classes apply styles like rounded corners, shadows, hover effects, and vertical layout (flex flex-col).

Inside each card:

  • <img>: Product image.
  • <h3>: Product name.
  • <p>: Product price.
  • Ratings: represented with Unicode stars.
  • Add to Cart button: Styled in green with hover effects.

9. Offers Section (#offers)

<section id="offers" ... style="background-image: url('discount.jpg');">
  • A banner-style promotional section with a background image.
  • Overlay (bg-black/50) darkens the background for better text contrast.
<h2>Get 15% Off Your First Order!</h2>
<p>Use code <strong class="bg-white text-emerald-600">FRESH15</strong>...</p>
<a href="#products">Shop Now & Save</a>
  • Displays a discount code (FRESH15).
  • CTA (Call to Action) button links back to the product section.

10. Testimonials Section (#testimonials)

<section id="testimonials" class="py-16 bg-gray-100">
  • A carousel/slider of customer reviews using Swiper.js (swiper, swiper-wrapper, swiper-slide).

Each testimonial includes:

  • Customer photo (img)
  • Review text (<p>)
  • Customer name
  • Star rating (visual using stars)
<div class="swiper-pagination testimonial-pagination"></div>
  • Placeholder for the Swiper.js pagination dots.

11. Newsletter Section (#newsletter)

<section id="newsletter" class="py-16 bg-white">
  • Users can subscribe to a newsletter.
<form id="newsletter-form">
  <input type="email" ...>
  <button type="submit">Subscribe</button>
</form>
  • Responsive form layout with email field and subscribe button.
  • Shows a success message in <p id="newsletter-message"> after submission.

12. About Section (#about)

<section id="about" class="py-16 bg-gray-50">
  • Provides information about FreshMart.
<div class="md:w-1/2"> <img src="about-us.jpg"> </div>
<div class="md:w-1/2">
  <h2>About FreshMart</h2>
  <p>Founded with a passion...</p>
</div>
  • Flexbox layout: image on the left, text on the right (on desktop).

13. Footer Section (#contact)

<footer id="contact" class="bg-gray-800 text-gray-300 py-12">
  • Footer with 4 columns using a responsive grid.

Sections inside the footer:

  1. Brand Info
    • Logo/title: FreshMart
    • Short description
    • Social media icons (Facebook, Twitter, Instagram)
  2. Quick Links
    • Navigation links like Home, Products, Categories, FAQs
  3. Contact Info
    • Address and other contact details (incomplete in your snippet)

Step 2 (CSS Code):

This CSS code styles a modern, responsive shopping cart popup system, complete with animations, a sticky header, custom scrollbars, Swiper slider buttons, and user-friendly cart features. Below is a deep, section-by-section explanation of what each part does.

Typography and Scroll Behavior

body {
    font-family: 'Inter', sans-serif;
}
  • Sets the main font for the webpage to Inter. If unavailable, it falls back to any sans-serif font.
html {
    scroll-behavior: smooth;
}
  • Enables smooth scrolling when navigating anchor links (e.g., #section-id).

Custom Scrollbar Styling (Only for WebKit browsers like Chrome, Edge, Safari)

::-webkit-scrollbar {
    width: 8px;
}
  • Sets the scrollbar width to a thin 8px.
Edit
::-webkit-scrollbar-track {
    background: #f1f1f1;
}
  • Sets a light background for the scrollbar track.
::-webkit-scrollbar-thumb {
    background: #888;
    border-radius: 4px;
}
  • Scrollbar thumb (the draggable part) is gray with rounded corners.
Edit
::-webkit-scrollbar-thumb:hover {
    background: #555;
}
  • On hover, the scrollbar thumb darkens for better visibility.

Sticky Header

header.sticky {
    background-color: rgba(255, 255, 255, 0.95);
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    transition: background-color 0.3s ease, box-shadow 0.3s ease;
}
  • When the header has the class .sticky, it becomes slightly opaque and casts a soft shadow.
  • Transitions smooth changes in color and shadow when becoming sticky.

Pulse Animation

@keyframes pulse {
    0%, 100% {
        transform: scale(1);
    }
    50% {
        transform: scale(1.1);
    }
}
  • Creates a "pulse" animation that scales the element up slightly at 50% then back.
  • Used for attention-drawing effects like adding an item to the cart.
.cart-pulse {
    animation: pulse 0.5s ease-in-out;
}
  • Applies the pulse animation with a half-second duration.

Swiper Navigation Buttons

.swiper-button-next,
.swiper-button-prev {
    color: #10B981;
    background-color: rgba(255, 255, 255, 0.7);
    border-radius: 50%;
    width: 40px;
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
    transition: background-color 0.3s ease;
}
  • Styled circular Swiper buttons with green text and soft white background.
  • Includes shadow and hover transition.
.swiper-button-next:hover,
.swiper-button-prev:hover {
    background-color: rgba(255, 255, 255, 0.9);
}
  • On hover, the background becomes more opaque.
.swiper-button-next::after,
.swiper-button-prev::after {
    font-size: 18px;
    font-weight: bold;
}
  • Adjusts size and weight of the directional arrows inside buttons.

Cart Price Displays

.cart-price-display, .mobile-cart-price-display {
    font-size: 0.8rem / 0.7rem;
    color: #4B5563;
    margin-left: 4px / 2px;
}
  • Displays prices next to cart items in desktop/mobile with adjusted spacing and size.

Cart Popup Overlay

#cart-popup-overlay {
    position: fixed;
    top: 0; left: 0; right: 0; bottom: 0;
    background-color: rgba(0, 0, 0, 0.5);
    z-index: 999;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.3s ease, visibility 0.3s ease;
}
#cart-popup-overlay.active {
    opacity: 1;
    visibility: visible;
}
  • Covers the entire screen in a dark overlay when cart is active.
  • Smooth transition from hidden to visible when .active is added.

Cart Sidebar Panel

#cart-popup {
    position: fixed;
    top: 0;
    right: -100%;
    width: 90%;
    max-width: 400px;
    height: 100%;
    background-color: white;
    box-shadow: -2px 0 5px rgba(0, 0, 0, 0.1);
    z-index: 1000;
    display: flex;
    flex-direction: column;
    transition: right 0.4s ease-in-out;
}
#cart-popup.active {
    right: 0;
}
  • A hidden cart panel slides in from the right.
  • Using .active, it's shown with a smooth slide.

Cart Components

Header, Body, Footer

.cart-popup-header,
.cart-popup-body,
.cart-popup-footer {
    padding: 1rem;
}
.cart-popup-header {
    border-bottom: 1px solid #e5e7eb;
    display: flex; justify-content: space-between; align-items: center;
}
.cart-popup-body {
    flex-grow: 1; overflow-y: auto;
}
.cart-popup-footer {
    border-top: 1px solid #e5e7eb;
    background-color: #f9fafb;
}
  • Separates cart into header (title & close), body (scrollable items), and footer (totals & buttons).

Cart Item

.cart-item {
    display: flex;
    align-items: center;
    margin-bottom: 1rem;
    padding-bottom: 1rem;
    border-bottom: 1px solid #f3f4f6;
}
.cart-item:last-child {
    margin-bottom: 0;
    padding-bottom: 0;
    border-bottom: none;
}
  • Every cart item is styled with spacing and a light bottom border.

Item Details & Image

.cart-item-image {
    width: 60px;
    height: 60px;
    object-fit: cover;
    border-radius: 0.375rem;
    margin-right: 1rem;
    flex-shrink: 0;
}
.cart-item-details {
    flex-grow: 1;
    margin-right: 1rem;
}
.cart-item-name {
    font-weight: 600;
    color: #1f2937;
    font-size: 0.9rem;
}
.cart-item-price {
    color: #4b5563;
    font-size: 0.85rem;
}
  • Shows product image, name, and price with consistent sizing and clean layout.

Actions (Quantity, Remove)

.quantity-controls button {
    background-color: #e5e7eb;
    color: #374151;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    font-weight: bold;
}
.quantity-controls span {
    margin: 0 0.5rem;
    min-width: 20px;
    text-align: center;
}
.remove-item-btn {
    color: #ef4444;
}
.remove-item-btn:hover {
    color: #dc2626;
}
  • Quantity buttons are small, round, and gray with hover effect.
  • The remove button is red with hover darkening.

Cart Summary

.cart-summary div {
    display: flex;
    justify-content: space-between;
    font-size: 0.9rem;
}
.cart-summary .total {
    font-size: 1rem;
    font-weight: bold;
    border-top: 1px dashed #d1d5db;
}
  • Shows line items and total with aligned labels/values.

Checkout Buttons

.cart-popup-actions button {
    width: 100%;
    padding: 0.75rem 1rem;
    border-radius: 0.5rem;
    font-weight: 600;
}
.checkout-btn {
    background-color: #10b981;
    color: white;
}
.checkout-btn:hover {
    background-color: #059669;
}
.continue-shopping-btn {
    background-color: #e5e7eb;
    color: #374151;
}
.continue-shopping-btn:hover {
    background-color: #d1d5db;
}
  • Checkout is green, continue shopping is gray. Both have full-width buttons with hover effect.

Empty Cart Message

.empty-cart-message {
    text-align: center;
    color: #6b7280;
    padding: 2rem 0;
}
  • If no items, a friendly centered message is shown in muted text.
body {
    font-family: 'Inter', sans-serif;
}

html {
    scroll-behavior: smooth;
}

::-webkit-scrollbar {
    width: 8px;
}

::-webkit-scrollbar-track {
    background: #f1f1f1;
}

::-webkit-scrollbar-thumb {
    background: #888;
    border-radius: 4px;
}

::-webkit-scrollbar-thumb:hover {
    background: #555;
}

header.sticky {
    background-color: rgba(255, 255, 255, 0.95);
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    transition: background-color 0.3s ease, box-shadow 0.3s ease;
}

@keyframes pulse {

    0%,
    100% {
        transform: scale(1);
    }

    50% {
        transform: scale(1.1);
    }
}

.cart-pulse {
    animation: pulse 0.5s ease-in-out;
}

.swiper-button-next,
.swiper-button-prev {
    color: #10B981;
    background-color: rgba(255, 255, 255, 0.7);
    border-radius: 50%;
    width: 40px;
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
    transition: background-color 0.3s ease;
}

.swiper-button-next:hover,
.swiper-button-prev:hover {
    background-color: rgba(255, 255, 255, 0.9);
}

.swiper-button-next::after,
.swiper-button-prev::after {
    font-size: 18px;
    font-weight: bold;
}

.cart-price-display {
    font-size: 0.8rem;
    color: #4B5563;
    margin-left: 4px;
}

.mobile-cart-price-display {
    font-size: 0.7rem;
    color: #4B5563;
    margin-left: 2px;
}

#cart-popup-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: rgba(0, 0, 0, 0.5);
    z-index: 999;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.3s ease, visibility 0.3s ease;
}

#cart-popup-overlay.active {
    opacity: 1;
    visibility: visible;
}

#cart-popup {
    position: fixed;
    top: 0;
    right: -100%;
    width: 90%;
    max-width: 400px;
    height: 100%;
    background-color: white;
    box-shadow: -2px 0 5px rgba(0, 0, 0, 0.1);
    z-index: 1000;
    display: flex;
    flex-direction: column;
    transition: right 0.4s ease-in-out;
}

#cart-popup.active {
    right: 0;
}

.cart-popup-header {
    padding: 1rem;
    border-bottom: 1px solid #e5e7eb;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.cart-popup-body {
    flex-grow: 1;
    overflow-y: auto;
    padding: 1rem;
}

.cart-popup-footer {
    padding: 1rem;
    border-top: 1px solid #e5e7eb;
    background-color: #f9fafb;
}

.cart-item {
    display: flex;
    align-items: center;
    margin-bottom: 1rem;
    padding-bottom: 1rem;
    border-bottom: 1px solid #f3f4f6;
}

.cart-item:last-child {
    margin-bottom: 0;
    padding-bottom: 0;
    border-bottom: none;
}

.cart-item-image {
    width: 60px;
    height: 60px;
    object-fit: cover;
    border-radius: 0.375rem;
    margin-right: 1rem;
    flex-shrink: 0;
}

.cart-item-details {
    flex-grow: 1;
    margin-right: 1rem;
}

.cart-item-name {
    font-weight: 600;
    color: #1f2937;
    margin-bottom: 0.25rem;
    font-size: 0.9rem;
}

.cart-item-price {
    color: #4b5563;
    font-size: 0.85rem;
}

.cart-item-actions {
    display: flex;
    align-items: center;
}

.quantity-controls button {
    background-color: #e5e7eb;
    border: none;
    color: #374151;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    font-weight: bold;
    cursor: pointer;
    transition: background-color 0.2s ease;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.9rem;
    line-height: 1;
}

.quantity-controls button:hover {
    background-color: #d1d5db;
}

.quantity-controls span {
    margin: 0 0.5rem;
    min-width: 20px;
    text-align: center;
    font-weight: 500;
}

.remove-item-btn {
    background: none;
    border: none;
    color: #ef4444;
    cursor: pointer;
    margin-left: 1rem;
    font-size: 1rem;
    transition: color 0.2s ease;
}

.remove-item-btn:hover {
    color: #dc2626;
}

.cart-summary div {
    display: flex;
    justify-content: space-between;
    margin-bottom: 0.5rem;
    font-size: 0.9rem;
}

.cart-summary div span:first-child {
    color: #4b5563;
}

.cart-summary div span:last-child {
    font-weight: 600;
    color: #1f2937;
}

.cart-summary .total {
    font-size: 1rem;
    font-weight: bold;
    margin-top: 0.75rem;
    padding-top: 0.75rem;
    border-top: 1px dashed #d1d5db;
}

.cart-popup-actions button {
    width: 100%;
    padding: 0.75rem 1rem;
    border-radius: 0.5rem;
    font-weight: 600;
    transition: background-color 0.3s ease;
    cursor: pointer;
    text-align: center;
}

.checkout-btn {
    background-color: #10b981;
    color: white;
    margin-bottom: 0.75rem;
}

.checkout-btn:hover {
    background-color: #059669;
}

.continue-shopping-btn {
    background-color: #e5e7eb;
    color: #374151;
}

.continue-shopping-btn:hover {
    background-color: #d1d5db;
}

.empty-cart-message {
    text-align: center;
    color: #6b7280;
    padding: 2rem 0;
} 

Step 3 (JavaScript Code):

Finally, we need to create a function in JavaScript. Let's break it down section-by-section for better understanding:

1. DOMContentLoaded Event

document.addEventListener('DOMContentLoaded', function () {
  • This ensures all DOM elements are fully loaded before running the script. It prevents errors due to elements not being available in the DOM yet.

2. Element Selectors

This section grabs elements from the HTML by their IDs or classes to interact with later:

  • Navigation/Menu: Mobile menu toggle button and the actual mobile menu.
  • Header/Footer: Newsletter form inputs and messages.
  • Cart: Add-to-cart buttons, cart popup, cart data (like items, subtotal, etc.).
const mobileMenuButton = document.getElementById('mobile-menu-button');
const addToCartButtons = document.querySelectorAll('.add-to-cart-btn');
const cartCountElements = [document.getElementById('cart-count'), document.getElementById('mobile-cart-count')];

3. State Variables

let cartItems = [];
const TAX_RATE = 0.05;
  • cartItems: Array storing cart items as objects (id, name, price, quantity, image).
  • TAX_RATE: Simulated tax rate of 5%.

4. Helper Function

function formatPrice(price) {
    return `$${price.toFixed(2)}`;
}
  • Formats numbers to look like currency (e.g., 25 becomes $25.00).

5. Cart Logic Functions

updateHeaderCartDisplay()

  • Updates cart item count and total price in the header.
  • Uses reduce() to sum quantities and prices.

renderCartPopup()

  • Clears the cart popup.
  • If cart is empty: shows "Your cart is empty".
  • Otherwise: creates and appends DOM elements for each item, including image, name, price, quantity controls, and remove button.

updateCartSummary()

  • Calculates subtotal, tax, and total.
  • Updates corresponding UI fields.

addItemToCart(item)

  • Checks if item exists:
    • If yes: increases quantity.
    • If not: adds as new with quantity 1.
  • Calls updateCartState() to refresh everything.

updateItemQuantity(itemId, change)

  • Finds item by id, changes its quantity.
  • Removes item if quantity ≤ 0.

removeItemFromCart(itemId)

  • Removes an item entirely using filter().

updateCartState()

  • Central function to update:
    • Header display.
    • Cart popup.
    • (Optionally) store to localStorage.

6. Cart Popup Visibility

function openCartPopup() {
    cartPopup.classList.add('active');
    document.body.style.overflow = 'hidden';
}
function closeCartPopup() {
    cartPopup.classList.remove('active');
    document.body.style.overflow = '';
}
  • Controls showing/hiding of cart modal.
  • Prevents scrolling while popup is active.

7. Event Listeners

Mobile Menu Toggle

  • Toggles menu visibility.
  • Changes icon based on open/close state.
  • Hides the menu when a link is clicked.

Sticky Header

window.addEventListener('scroll', () => {
    header.classList.toggle('sticky', window.scrollY > 50);
});
  • Makes the header sticky when scrolled more than 50px.

Swiper.js Initialization

  • For product and testimonial carousels.
  • Uses Swiper library with responsive breakpoints.

Newsletter Form Handling

form.addEventListener('submit', (e) => {
    e.preventDefault();
    const email = emailInput.value.trim();
    if (email && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
        messageElement.textContent = 'Thank you for subscribing!';
    }
});
  • Validates email using a regular expression.
  • Displays success message.
  • Clears the input field.
document.addEventListener('DOMContentLoaded', function () {
    // --- Element Selectors ---
    const mobileMenuButton = document.getElementById('mobile-menu-button');
    const mobileMenu = document.getElementById('mobile-menu');
    const header = document.getElementById('header');
    const newsletterForm = document.getElementById('newsletter-form');
    const newsletterEmail = document.getElementById('newsletter-email');
    const newsletterMessage = document.getElementById('newsletter-message');
    const footerNewsletterForm = document.getElementById('footer-newsletter-form');
    const footerNewsletterEmail = document.getElementById('footer-newsletter-email');
    const footerNewsletterMessage = document.getElementById('footer-newsletter-message');
    const addToCartButtons = document.querySelectorAll('.add-to-cart-btn');
    // Cart display elements (Header)
    const cartCountElements = [document.getElementById('cart-count'), document.getElementById('mobile-cart-count')];
    const cartTotalPriceElements = [document.getElementById('cart-total-price'), document.getElementById('mobile-cart-total-price')];
    // Cart Popup Elements
    const cartPopup = document.getElementById('cart-popup');
    const cartPopupOverlay = document.getElementById('cart-popup-overlay');
    const closeCartPopupButton = document.getElementById('close-cart-popup');
    const desktopCartButton = document.getElementById('desktop-cart-button');
    const mobileCartButton = document.getElementById('mobile-cart-button');
    const cartItemsList = document.getElementById('cart-items-list');
    const cartSubtotalElement = document.getElementById('cart-subtotal');
    const cartTaxElement = document.getElementById('cart-tax');
    const cartFinalTotalElement = document.getElementById('cart-final-total');
    const continueShoppingBtn = document.getElementById('continue-shopping-btn');
    const emptyCartMessage = cartItemsList.querySelector('.empty-cart-message');

    // --- State Variables ---
    let cartItems = []; // Array to hold cart item objects: { id, name, price, quantity, image }
    const TAX_RATE = 0.05; // 5% tax rate for simulation

    // --- Helper Functions ---
    function formatPrice(price) {
        return `$${price.toFixed(2)}`;
    }

    // --- Cart Logic Functions ---

    // Update cart display in the header
    function updateHeaderCartDisplay() {
        const totalCount = cartItems.reduce((sum, item) => sum + item.quantity, 0);
        const totalPrice = cartItems.reduce((sum, item) => sum + (item.price * item.quantity), 0);

        cartCountElements.forEach(el => {
            if (el) {
                el.textContent = totalCount;
                // Only pulse if count increased (optional)
                // el.classList.add('cart-pulse');
                // setTimeout(() => { el.classList.remove('cart-pulse'); }, 500);
            }
        });
        cartTotalPriceElements.forEach(el => {
            if (el) el.textContent = formatPrice(totalPrice);
        });
    }

    // Render the items inside the cart popup
    function renderCartPopup() {
        cartItemsList.innerHTML = ''; // Clear current items

        if (cartItems.length === 0) {
            cartItemsList.innerHTML = '<p class="empty-cart-message">Your cart is empty.</p>';
        } else {
            cartItems.forEach(item => {
                const itemElement = document.createElement('div');
                itemElement.classList.add('cart-item');
                // Use item.id in data attributes for easier targeting
                itemElement.innerHTML = `
                    <img src="${item.image}" alt="${item.name}" class="cart-item-image">
                    <div class="cart-item-details">
                        <p class="cart-item-name">${item.name}</p>
                        <p class="cart-item-price">${formatPrice(item.price)}</p>
                    </div>
                    <div class="cart-item-actions">
                        <div class="quantity-controls flex items-center">
                            <button class="quantity-decrease" data-id="${item.id}" aria-label="Decrease quantity">-</button>
                            <span class="quantity">${item.quantity}</span>
                            <button class="quantity-increase" data-id="${item.id}" aria-label="Increase quantity">+</button>
                        </div>
                        <button class="remove-item-btn" data-id="${item.id}" aria-label="Remove item">
                            <i class="fas fa-trash-alt"></i>
                        </button>
                    </div>
                `;
                cartItemsList.appendChild(itemElement);
            });
        }
        updateCartSummary();
    }

    // Calculate and display summary (subtotal, tax, total) in the popup
    function updateCartSummary() {
        const subtotal = cartItems.reduce((sum, item) => sum + (item.price * item.quantity), 0);
        const tax = subtotal * TAX_RATE;
        const finalTotal = subtotal + tax;

        cartSubtotalElement.textContent = formatPrice(subtotal);
        cartTaxElement.textContent = formatPrice(tax);
        cartFinalTotalElement.textContent = formatPrice(finalTotal);
    }

    // Add or update item in the cart array
    function addItemToCart(item) {
        const existingItemIndex = cartItems.findIndex(cartItem => cartItem.id === item.id);
        if (existingItemIndex > -1) {
            // Item exists, increase quantity
            cartItems[existingItemIndex].quantity += 1;
        } else {
            // New item, add to cart with quantity 1
            cartItems.push({ ...item, quantity: 1 });
        }
        updateCartState();
    }

     // Update item quantity in the cart array
    function updateItemQuantity(itemId, change) { // change is +1 or -1
         const itemIndex = cartItems.findIndex(cartItem => cartItem.id === itemId);
         if (itemIndex > -1) {
             cartItems[itemIndex].quantity += change;
             if (cartItems[itemIndex].quantity <= 0) {
                 // Remove item if quantity is 0 or less
                 cartItems.splice(itemIndex, 1);
             }
             updateCartState();
         }
    }

     // Remove item completely from the cart array
    function removeItemFromCart(itemId) {
        cartItems = cartItems.filter(item => item.id !== itemId);
        updateCartState();
    }

    // Central function to update all displays after cart changes
    function updateCartState() {
         updateHeaderCartDisplay();
         renderCartPopup(); // Re-render the popup content
         // Optional: Persist cart to localStorage
         // localStorage.setItem('freshmartCart', JSON.stringify(cartItems));
    }

    // --- Popup Visibility Functions ---
    function openCartPopup() {
        renderCartPopup(); // Ensure content is up-to-date before showing
        cartPopup.classList.add('active');
        cartPopupOverlay.classList.add('active');
        document.body.style.overflow = 'hidden'; // Prevent background scrolling
    }

    function closeCartPopup() {
        cartPopup.classList.remove('active');
        cartPopupOverlay.classList.remove('active');
        document.body.style.overflow = ''; // Restore background scrolling
    }

    // --- Event Listeners Setup ---

    // Mobile Menu Toggle
    if (mobileMenuButton && mobileMenu) {
        mobileMenuButton.addEventListener('click', () => {
            mobileMenu.classList.toggle('hidden');
            mobileMenuButton.innerHTML = mobileMenu.classList.contains('hidden')
                ? `<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16m-7 6h7" /></svg>`
                : `<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg>`;
        });
        mobileMenu.querySelectorAll('a').forEach(link => {
            link.addEventListener('click', () => {
                mobileMenu.classList.add('hidden');
                mobileMenuButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16m-7 6h7" /></svg>`;
            });
        });
    }

    // Sticky Header
    if (header) {
        window.addEventListener('scroll', () => {
            header.classList.toggle('sticky', window.scrollY > 50);
        });
    }

    // Swiper Carousels (Initialize if elements exist)
    if (document.querySelector('.product-swiper')) {
         new Swiper('.product-swiper', {
            slidesPerView: 1.2, spaceBetween: 10, // Adjusted space
            loop: true,
            breakpoints: { 640: { slidesPerView: 2.5, spaceBetween: 15 }, 768: { slidesPerView: 3.5, spaceBetween: 20 }, 1024: { slidesPerView: 4.5, spaceBetween: 20 } },
            pagination: { el: '.product-pagination', clickable: true },
            navigation: { nextEl: '.product-next', prevEl: '.product-prev' },
            autoplay: { delay: 5000, disableOnInteraction: false },
        });
    }
    if (document.querySelector('.testimonial-swiper')) {
         new Swiper('.testimonial-swiper', {
            slidesPerView: 1, spaceBetween: 30, loop: true, centeredSlides: true,
            breakpoints: { 768: { slidesPerView: 1.5 }, 1024: { slidesPerView: 2.5 } },
            pagination: { el: '.testimonial-pagination', clickable: true },
            autoplay: { delay: 6000, disableOnInteraction: true },
        });
    }

    // Newsletter Form Handling
    function handleNewsletterSubmit(form, emailInput, messageElement) {
        if (!form || !emailInput || !messageElement) return;
        form.addEventListener('submit', (e) => {
            e.preventDefault();
            const email = emailInput.value.trim();
            if (email && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
                messageElement.textContent = 'Thank you for subscribing!';
                messageElement.className = 'mt-4 text-sm text-green-600';
                emailInput.value = ''; console.log('Newsletter subscription:', email);
            } else {
                messageElement.textContent = 'Please enter a valid email address.';
                messageElement.className = 'mt-4 text-sm text-red-600';
            }
            setTimeout(() => { messageElement.textContent = ''; }, 5000);
        });
    }
    handleNewsletterSubmit(newsletterForm, newsletterEmail, newsletterMessage);
    handleNewsletterSubmit(footerNewsletterForm, footerNewsletterEmail, footerNewsletterMessage);

    // --- Cart Interaction Event Listeners ---

    // Add to Cart Button Clicks
    addToCartButtons.forEach(button => {
        button.addEventListener('click', (event) => {
            const card = event.target.closest('.product-card');
            if (!card) return;

            const item = {
                id: card.dataset.id,
                name: card.dataset.name,
                price: parseFloat(card.dataset.price),
                image: card.dataset.image
            };

            if (item.id && item.name && item.price > 0 && item.image) {
                addItemToCart(item);
                // Optional: Briefly show popup after adding
                // openCartPopup();
                // setTimeout(closeCartPopup, 1500); // Close after 1.5 seconds
                 // Pulse the header cart icon
                 cartCountElements.forEach(el => el?.classList.add('cart-pulse'));
                 setTimeout(() => cartCountElements.forEach(el => el?.classList.remove('cart-pulse')), 500);
            } else {
                console.warn("Could not add item - missing data attributes on product card:", card);
            }
        });
    });

    // Open Cart Popup Buttons (Header)
    if (desktopCartButton) desktopCartButton.addEventListener('click', openCartPopup);
    if (mobileCartButton) mobileCartButton.addEventListener('click', openCartPopup);

    // Close Cart Popup Buttons/Overlay
    if (closeCartPopupButton) closeCartPopupButton.addEventListener('click', closeCartPopup);
    if (cartPopupOverlay) cartPopupOverlay.addEventListener('click', closeCartPopup);
    if (continueShoppingBtn) continueShoppingBtn.addEventListener('click', closeCartPopup);


    // Handle clicks inside the cart popup (Event Delegation)
    cartItemsList.addEventListener('click', (event) => {
         const target = event.target;
         const itemId = target.dataset.id;

         if (!itemId) return; // Exit if click wasn't on a button with data-id

         if (target.classList.contains('quantity-increase')) {
             updateItemQuantity(itemId, 1);
         } else if (target.classList.contains('quantity-decrease')) {
             updateItemQuantity(itemId, -1);
         } else if (target.closest('.remove-item-btn')) { // Check closest ancestor for icon clicks
             removeItemFromCart(target.closest('.remove-item-btn').dataset.id);
         }
    });

    // --- Initial Load ---
    // Optional: Load cart from localStorage on page load
    // const savedCart = localStorage.getItem('freshmartCart');
    // if (savedCart) {
    //    cartItems = JSON.parse(savedCart);
    //    updateCartState(); // Update display based on loaded cart
    // } else {
         updateHeaderCartDisplay(); // Initialize header display even if cart is empty
    // }


}); // End DOMContentLoaded

Final Output:

grocery-website-template-using-html-tailwind-css-and-javascript.gif

Conclusion:

You’ve just built a simple grocery website template using HTML, TailwindCSS, and JavaScript. This kind of layout is perfect for small shops, farmers’ markets, or for learning UI development. You can add more pages, like a cart, checkout, and product filters, later as your skills grow.

Start with this template and keep improving your project step by step!

That’s a wrap!

I hope you enjoyed this post. Now, with these examples, you can create your own amazing page.

Did you like it? Let me know in the comments below 🔥 and you can support me by buying me a coffee

And don’t forget to sign up to our email newsletter so you can get useful content like this sent right to your inbox!

Thanks!
Faraz 😊

End of the article

Subscribe to my Newsletter

Get the latest posts delivered right to your inbox


Latest Post

Please allow ads on our site🥺