Snippets E-commmerce

Bootstrap 5 E-commerce Product Category Page with Filters

An e-commerce category page for a bakery with a search navbar, a filter sidebar that becomes an offcanvas drawer on mobile, sorting, and a responsive grid of product cards with working add-to-cart.

Bootstrap 5 No jQuery Sep 2020 4.2K views

<div class="pc-page bg-body-tertiary min-vh-100">
  <nav class="navbar bg-body border-bottom">
    <div class="container-xl gap-2 flex-wrap flex-md-nowrap">
      <a class="navbar-brand fw-bold me-2 text-body-emphasis" href="#">The<span class="text-primary">Bakeshop</span></a>
      <form class="flex-grow-1 mx-md-4 order-last order-md-0 w-100" role="search" style="max-width:420px" onsubmit="return false">
        <label for="pc-search" class="visually-hidden">Search products</label>
        <input id="pc-search" type="search" class="form-control rounded-pill bg-body-tertiary border-0 px-3" placeholder="Search bread, pastries...">
      </form>
      <button type="button" class="btn btn-outline-primary rounded-pill ms-auto d-lg-none" data-bs-toggle="offcanvas" data-bs-target="#pc-filters" aria-controls="pc-filters">Filters</button>
      <button type="button" class="btn btn-primary rounded-pill position-relative" aria-label="Cart">
        <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 6h15l-1.5 9h-12z"/><path d="M6 6L5 3H2"/><circle cx="9" cy="20" r="1"/><circle cx="18" cy="20" r="1"/></svg>
        <span class="badge text-bg-light rounded-pill" id="pc-count">0</span>
      </button>
    </div>
  </nav>
  <div class="container-xl py-4">
    <nav aria-label="breadcrumb"><ol class="breadcrumb small mb-3"><li class="breadcrumb-item"><a href="#" class="link-underline-opacity-0">Home</a></li><li class="breadcrumb-item active" aria-current="page">All breads</li></ol></nav>
    <div class="row gx-lg-4 gy-0">
      <aside class="col-lg-3">
        <div class="offcanvas-lg offcanvas-start" tabindex="-1" id="pc-filters" aria-labelledby="pc-filters-title">
          <div class="offcanvas-header">
            <h2 class="offcanvas-title h5" id="pc-filters-title">Filters</h2>
            <button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#pc-filters" aria-label="Close"></button>
          </div>
          <div class="offcanvas-body d-block">
            <div class="wdw-card-sm p-4 w-100">
              <h2 class="h5 fw-bold mb-4">Filters</h2>
          <div class="mb-4">
            <h3 class="h6 fw-bold mb-3">Category</h3>
            <div class="form-check mb-2"><input class="form-check-input pc-cat" type="checkbox" value="breads" id="f-breads"><label class="form-check-label" for="f-breads">Artisan breads</label></div>
            <div class="form-check mb-2"><input class="form-check-input pc-cat" type="checkbox" value="pastries" id="f-pastries"><label class="form-check-label" for="f-pastries">Pastries & baskets</label></div>
          </div>
          <div class="mb-4">
            <h3 class="h6 fw-bold mb-3">Offers</h3>
            <div class="form-check form-switch"><input class="form-check-input" type="checkbox" role="switch" id="f-offer"><label class="form-check-label" for="f-offer">Deals & staff picks</label></div>
          </div>
          <button type="button" class="btn btn-outline-primary btn-sm rounded-pill w-100" id="pc-reset">Clear filters</button>
            </div>
          </div>
        </div>
      </aside>
      <main class="col-lg-9">
        <div class="d-flex align-items-center justify-content-between gap-3 mb-3 flex-wrap">
          <p class="mb-0 small wdw-muted" id="pc-result" role="status" aria-live="polite">Showing 6 results</p>
          <div class="d-flex align-items-center gap-2">
            <label for="pc-sort" class="small wdw-muted text-nowrap">Sort by</label>
            <select id="pc-sort" class="form-select form-select-sm rounded-pill" style="min-width:150px">
              <option value="pop">Popularity</option><option value="low">Price: low to high</option><option value="high">Price: high to low</option>
            </select>
          </div>
        </div>
        <div class="row g-3 g-md-4" id="pc-grid">
        <div class="col-6 col-xl-4 pc-item" data-cat="breads" data-offer="1" data-name="seeded rye loaf" data-price="6.5" data-pop="6">
          <article class="card h-100 border-0 wdw-card-sm overflow-hidden">
            <div class="position-relative">
              <img src="https://images.unsplash.com/photo-1509440159596-0249088772ff?auto=format&fit=crop&w=640&h=480&q=70" class="card-img-top pc-img" alt="Seeded Rye Loaf" loading="lazy">
              <span class="badge text-bg-dark rounded-pill position-absolute top-0 start-0 m-3">Staff pick</span>
            </div>
            <div class="card-body d-flex flex-column p-3 p-md-4">
              <h3 class="h6 fw-bold mb-1">Seeded Rye Loaf</h3>
              <p class="small wdw-muted mb-3 flex-grow-1">Dark rye with sunflower and flax seeds.</p>
              <div class="d-flex align-items-center justify-content-between gap-2 flex-wrap">
                <div><span class="fw-bold fs-5">$6.50</span><span class="small wdw-muted"> / loaf</span></div>
                <button type="button" class="btn btn-sm btn-primary rounded-pill pc-add" aria-label="Add Seeded Rye Loaf to cart">Add</button>
              </div>
            </div>
          </article>
        </div>
        <div class="col-6 col-xl-4 pc-item" data-cat="pastries" data-offer="1" data-name="butter croissant" data-price="3.2" data-pop="5">
          <article class="card h-100 border-0 wdw-card-sm overflow-hidden">
            <div class="position-relative">
              <img src="https://images.unsplash.com/photo-1555507036-ab1f4038808a?auto=format&fit=crop&w=640&h=480&q=70" class="card-img-top pc-img" alt="Butter Croissant" loading="lazy">
              <span class="badge text-bg-danger rounded-pill position-absolute top-0 start-0 m-3">-15%</span>
            </div>
            <div class="card-body d-flex flex-column p-3 p-md-4">
              <h3 class="h6 fw-bold mb-1">Butter Croissant</h3>
              <p class="small wdw-muted mb-3 flex-grow-1">Laminated, flaky and baked every morning.</p>
              <div class="d-flex align-items-center justify-content-between gap-2 flex-wrap">
                <div><span class="fw-bold fs-5">$3.20</span> <s class="small wdw-muted">$3.80</s><span class="small wdw-muted"> / piece</span></div>
                <button type="button" class="btn btn-sm btn-primary rounded-pill pc-add" aria-label="Add Butter Croissant to cart">Add</button>
              </div>
            </div>
          </article>
        </div>
        <div class="col-6 col-xl-4 pc-item" data-cat="breads" data-offer="0" data-name="sliced sourdough" data-price="7.4" data-pop="4">
          <article class="card h-100 border-0 wdw-card-sm overflow-hidden">
            <div class="position-relative">
              <img src="https://images.unsplash.com/photo-1549931319-a545dcf3bc73?auto=format&fit=crop&w=640&h=480&q=70" class="card-img-top pc-img" alt="Sliced Sourdough" loading="lazy">
              
            </div>
            <div class="card-body d-flex flex-column p-3 p-md-4">
              <h3 class="h6 fw-bold mb-1">Sliced Sourdough</h3>
              <p class="small wdw-muted mb-3 flex-grow-1">48-hour ferment, sliced and ready to toast.</p>
              <div class="d-flex align-items-center justify-content-between gap-2 flex-wrap">
                <div><span class="fw-bold fs-5">$7.40</span><span class="small wdw-muted"> / loaf</span></div>
                <button type="button" class="btn btn-sm btn-primary rounded-pill pc-add" aria-label="Add Sliced Sourdough to cart">Add</button>
              </div>
            </div>
          </article>
        </div>
        <div class="col-6 col-xl-4 pc-item" data-cat="breads" data-offer="1" data-name="country boule" data-price="8.0" data-pop="3">
          <article class="card h-100 border-0 wdw-card-sm overflow-hidden">
            <div class="position-relative">
              <img src="https://images.unsplash.com/photo-1586444248902-2f64eddc13df?auto=format&fit=crop&w=640&h=480&q=70" class="card-img-top pc-img" alt="Country Boule" loading="lazy">
              <span class="badge text-bg-dark rounded-pill position-absolute top-0 start-0 m-3">Staff pick</span>
            </div>
            <div class="card-body d-flex flex-column p-3 p-md-4">
              <h3 class="h6 fw-bold mb-1">Country Boule</h3>
              <p class="small wdw-muted mb-3 flex-grow-1">Crackly crust with an open, chewy crumb.</p>
              <div class="d-flex align-items-center justify-content-between gap-2 flex-wrap">
                <div><span class="fw-bold fs-5">$8.00</span><span class="small wdw-muted"> / loaf</span></div>
                <button type="button" class="btn btn-sm btn-primary rounded-pill pc-add" aria-label="Add Country Boule to cart">Add</button>
              </div>
            </div>
          </article>
        </div>
        <div class="col-6 col-xl-4 pc-item" data-cat="pastries" data-offer="0" data-name="baker's basket" data-price="12.9" data-pop="2">
          <article class="card h-100 border-0 wdw-card-sm overflow-hidden">
            <div class="position-relative">
              <img src="https://images.unsplash.com/photo-1608198093002-ad4e005484ec?auto=format&fit=crop&w=640&h=480&q=70" class="card-img-top pc-img" alt="Baker's Basket" loading="lazy">
              
            </div>
            <div class="card-body d-flex flex-column p-3 p-md-4">
              <h3 class="h6 fw-bold mb-1">Baker's Basket</h3>
              <p class="small wdw-muted mb-3 flex-grow-1">An assortment of rolls, baguettes and buns.</p>
              <div class="d-flex align-items-center justify-content-between gap-2 flex-wrap">
                <div><span class="fw-bold fs-5">$12.90</span><span class="small wdw-muted"> / basket</span></div>
                <button type="button" class="btn btn-sm btn-primary rounded-pill pc-add" aria-label="Add Baker's Basket to cart">Add</button>
              </div>
            </div>
          </article>
        </div>
        <div class="col-6 col-xl-4 pc-item" data-cat="breads" data-offer="0" data-name="milk loaf" data-price="5.2" data-pop="1">
          <article class="card h-100 border-0 wdw-card-sm overflow-hidden">
            <div class="position-relative">
              <img src="https://images.unsplash.com/photo-1598373182133-52452f7691ef?auto=format&fit=crop&w=640&h=480&q=70" class="card-img-top pc-img" alt="Milk Loaf" loading="lazy">
              
            </div>
            <div class="card-body d-flex flex-column p-3 p-md-4">
              <h3 class="h6 fw-bold mb-1">Milk Loaf</h3>
              <p class="small wdw-muted mb-3 flex-grow-1">Soft, lightly sweet sandwich bread.</p>
              <div class="d-flex align-items-center justify-content-between gap-2 flex-wrap">
                <div><span class="fw-bold fs-5">$5.20</span><span class="small wdw-muted"> / loaf</span></div>
                <button type="button" class="btn btn-sm btn-primary rounded-pill pc-add" aria-label="Add Milk Loaf to cart">Add</button>
              </div>
            </div>
          </article>
        </div>
        </div>
        <p class="text-center wdw-muted py-5 d-none" id="pc-empty">No products match your filters.</p>
      </main>
    </div>
  </div>
</div>

Built on Bootstrap 5.3.8 with plain JavaScript — no jQuery. Supports light and dark mode via data-bs-theme.