# Shadcn Ecommerce Template: Five Screens That Work on Phones

> Build a shadcn ecommerce template from source components: home, search, product page, cart drawer and checkout, and how it compares with hosted starters.

- Author: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Published: Nov 6, 2026
- Category: [Build it: screens and apps](https://wingo-ui.com/blog/category/build)
- Reading time: 11 min
- Canonical: https://wingo-ui.com/blog/shadcn-ecommerce-template

## TL;DR

A shadcn ecommerce template is worth using when it gives you the five screens a store needs (home, search, product page, cart and checkout) as source code that talks to the backend you already have. Hosted starters such as Your Next Store get a store running faster, but their storefront code depends on their commerce API. Wingo UI has no single store template: you assemble the storefront from Pro blocks and components (checkout page, cart drawer, product gallery, marketplace home and search) that turn into bottom sheets, swipe galleries and sticky pay bars on phones.

If you search for a shadcn ecommerce template, you get three kinds of results: hosted store starters that come with a commerce backend, paid front end kits, and directories that list a handful of GitHub repos. What the lists rarely tell you is which of those you can point at your own products API, and what the cart and the product page do when the buyer is on a phone. This guide walks through the five screens a storefront needs, built from shadcn-style source components that you install into your own Next.js app, and compares that route with the hosted starters.

## What screens does a shadcn ecommerce template need?

Five, and they share one product and cart shape:

1. **Home.** A search field, category tiles and a rail of featured products.
2. **Search and category results.** Filters, sort, a grid or list and a result count that follows every change.
3. **Product page.** Photos, price with the sale price, an add to cart control, description and reviews.
4. **Cart.** A drawer you can open from any page, with quantities, removal, a promo code and totals.
5. **Checkout.** Address, delivery, payment, review and a confirmation.

Here is how those map to Wingo UI, the shadcn-style library we build. There is no single shadcn store template in the catalog. You get shadcn ecommerce blocks for whole screens and the 16 components of the commerce category for the parts in between:

| Screen | Wingo UI piece | Kind | What it is shaped for |
| --- | --- | --- | --- |
| Home | [Marketplace Home Page](https://wingo-ui.com/components/marketplace-home-page) | Block | A what and where search, category tiles, a promoted rail, newest items |
| Search | [Marketplace Search Page](https://wingo-ui.com/components/marketplace-search-page) | Block | Facets with counts, price histogram, sort, infinite load |
| Product page | [Product Gallery](https://wingo-ui.com/components/product-gallery), [Price](https://wingo-ui.com/components/price), [Quantity Stepper](https://wingo-ui.com/components/quantity-stepper), [Reviews](https://wingo-ui.com/components/reviews) | Components | A single-brand product page you lay out yourself |
| Product page, many sellers | [Listing Detail Page](https://wingo-ui.com/components/listing-detail-page) | Block | Classifieds: seller card, message and call buttons, map |
| Cart | [Cart Drawer](https://wingo-ui.com/components/cart-drawer) | Component | Side panel on laptops, bottom sheet on phones |
| Checkout | [Checkout Page](https://wingo-ui.com/components/checkout-page) | Block | Cart, four checkout steps, confirmation with an order tracker |

The marketplace blocks started as a classifieds kit, so they speak in listings and sellers. That fits a multi-vendor store directly. For a single brand you keep the home and search blocks, edit the sample categories, and build the product page from components, which is what the next sections do.

## Should you use a template or a hosted commerce starter?

It depends on who owns the backend. A hosted starter gives you products, inventory, checkout and an admin on day one, and the storefront code is written against their API. A front end template gives you screens and leaves the data to you. These are the options that show up for this search, checked on November 6, 2026:

| Option | Price | License | Stack | Backend |
| --- | --- | --- | --- | --- |
| [Your Next Store](https://github.com/yournextstore/yournextstore) | Template free; hosted plans from $25 a month | MIT | Next.js 16.4.5, React 19.3, shadcn on Base UI, Tailwind 4.3 | Needs a `YNS_API_KEY` for its Commerce Kit SDK |
| [Relivator](https://github.com/reliverse/relivator) | Free | MIT | Next.js 15.3.2, React 19.1, shadcn on Radix, Tailwind 4.1 | Drizzle and Postgres, Better Auth, Polar for payments; last pushed October 4, 2025 |
| [Shopix by Shadcn Studio](https://shadcnstudio.com/templates/shopix-ecommerce-template) | $79 | Paid license, see its license page | Next.js 16, shadcn, Tailwind v4, Motion | None included; pitched as a front end for Shopify or Medusa |
| [shadcn.io ecommerce directory](https://www.shadcn.io/template/category/ecommerce) | Free | Per repo | Four open source repos listed | Varies |
| Wingo UI blocks | Pro: $8 a month, $80 a year or $150 once ([pricing](https://wingo-ui.com/pricing)) | [Wingo UI license](https://wingo-ui.com/legal/license) | Next.js App Router, React 19, Radix, Tailwind v4, Motion | None; callbacks for your API |

Your Next Store is the better pick when you want to sell this week and you are fine with their platform: it is actively maintained (pushed November 5, 2026) and the storefront is open source. Its hosted plans on [yns.store](https://yns.store) start with Starter at $25 a month billed yearly or $30 billed monthly, and list 0% transaction fees, so you pay Stripe's own fees only. Relivator is useful to read for a full stack with auth and a database, but it trails the current Next.js release. A front end kit makes sense when you already run Shopify, Medusa, Saleor or your own API and you want the screens to behave well on a phone. That last case is where the rest of this guide sits. Our [roundup of shadcn templates](https://wingo-ui.com/blog/best-shadcn-templates) covers more categories than commerce.

## How do I build the product page from components?

Start with a cart that every page can read. The cart lines use the `CartItem` type the Cart Drawer exports (`id`, `title`, `price`, `quantity`, plus optional `image`, `variant`, `compareAtPrice` and `maxQuantity`), so the product page, the drawer and the checkout all pass the same objects around.

```bash
npx wingo-ui@latest add cart-drawer product-gallery price quantity-stepper
```

```tsx
// app/(shop)/cart-context.tsx
"use client";

import { createContext, useContext, useState, type ReactNode } from "react";
import type { CartItem } from "@/components/ui/cart-drawer";

type Cart = {
  items: CartItem[];
  setItems: (items: CartItem[]) => void;
  setQuantity: (line: CartItem, quantity: number) => void;
  remove: (id: string) => void;
};

const CartContext = createContext<Cart | null>(null);

export function CartProvider({ initial, children }: { initial: CartItem[]; children: ReactNode }) {
  const [items, setItems] = useState(initial);

  const setQuantity = (line: CartItem, quantity: number) =>
    setItems((prev) => {
      if (quantity <= 0) return prev.filter((item) => item.id !== line.id);
      return prev.some((item) => item.id === line.id)
        ? prev.map((item) => (item.id === line.id ? { ...item, quantity } : item))
        : [...prev, { ...line, quantity }];
    });

  const remove = (id: string) => setItems((prev) => prev.filter((item) => item.id !== id));

  return <CartContext value={{ items, setItems, setQuantity, remove }}>{children}</CartContext>;
}

export function useCart() {
  const cart = useContext(CartContext);
  if (!cart) throw new Error("useCart must be used inside CartProvider");
  return cart;
}
```

The product view puts the gallery next to the buy box. The Quantity Stepper with `addToCart` starts as an "Add to cart" button and turns into a stepper once the product is in the cart. Because its `value` comes from the cart, the stepper on the page and the one in the drawer always agree.

```tsx
// app/(shop)/products/[slug]/product-view.tsx
"use client";

import type { CartItem } from "@/components/ui/cart-drawer";
import { Price } from "@/components/ui/price";
import { ProductGallery, type ProductGalleryImage } from "@/components/ui/product-gallery";
import { QuantityStepper } from "@/components/ui/quantity-stepper";
import { useCart } from "../../cart-context";

export type Product = CartItem & { images: ProductGalleryImage[]; description: string; stock: number };

export function ProductView({ product }: { product: Product }) {
  const { items, setQuantity } = useCart();
  const { images, description, stock, ...line } = product;
  const inCart = items.find((item) => item.id === product.id)?.quantity ?? 0;

  return (
    <div className="grid gap-6 md:grid-cols-2 md:gap-10">
      <ProductGallery images={images} aspect="1/1" fit="contain" zoom="lens" bleedOnMobile />
      <div className="flex flex-col gap-4 px-4 md:px-0">
        <h1 className="text-2xl font-semibold text-foreground">{product.title}</h1>
        <Price
          price={product.price}
          compareAtPrice={product.compareAtPrice}
          showDiscount
          size="xl"
          currency="USD"
          locale="en-US"
          caption="Free delivery over $75"
        />
        <QuantityStepper
          addToCart
          value={inCart}
          stock={stock}
          size="lg"
          tone="brand"
          onValueChange={(quantity) => setQuantity(line, quantity)}
        />
        <p className="text-muted-foreground">{description}</p>
      </div>
    </div>
  );
}
```

`fit="contain"` letterboxes product shots on white in light mode, which is what you want for packshots, and `bleedOnMobile` drops the radius below `md` so the photo runs edge to edge. Give each image a `width` and `height` so the full screen viewer takes its shape before the file loads.

> Live demo (Product gallery): With a mouse, rest on the photo to zoom and click it to open the viewer. On a phone width, swipe between photos and watch the counter; there is no hover zoom there. Try it at [Product gallery](https://wingo-ui.com/components/product-gallery) and install it with `npx wingo-ui@latest add product-gallery`.

The `stock` prop caps the stepper and shows a low stock hint at or below its threshold. This view only updates the client cart. To save the line on the server, return the request from `onValueChange`: the stepper shows a spinner until the promise settles and rolls back with a message if it rejects, the same contract the drawer uses below. Add [Reviews](https://wingo-ui.com/components/reviews) under the buy box when you have them; it takes your review data and renders the summary, filters and the list.

## How does the cart drawer behave on a phone?

On a laptop it slides in from the right. Below that, it rises as a bottom sheet with a handle: drag it down, tap outside or press Escape to close it. Each line can be swiped left to remove, and the removed line folds into a row with Undo and a five second countdown. The summary folds to the total with a Details toggle, so the lines keep most of the sheet.

Mount it in the header so it opens from every page. The drawer shows a new quantity at once and waits for your promise; if the server refuses, the line goes back and the error message prints under it.

```tsx
// app/(shop)/header-cart.tsx
"use client";

import { CartDrawer } from "@/components/ui/cart-drawer";
import { useCart } from "./cart-context";

export function HeaderCart() {
  const { items, setQuantity, remove } = useCart();

  return (
    <CartDrawer
      items={items}
      onQuantityChange={async (id, quantity) => {
        const response = await fetch(`/api/cart/${id}`, {
          method: "PATCH",
          body: JSON.stringify({ quantity }),
        });
        if (!response.ok) throw new Error(await response.text());
        const line = items.find((item) => item.id === id);
        if (line) setQuantity(line, quantity);
      }}
      // called once the undo window has passed or the drawer closes
      onRemove={(id) => {
        remove(id);
        void fetch(`/api/cart/${id}`, { method: "DELETE" });
      }}
      shipping={9.99}
      freeShippingThreshold={75}
      taxRate={0.08}
      pricesIncludeTax={false}
      labels={{ tax: (rate) => `Sales tax ${rate}` }}
      currency="USD"
      locale="en-US"
      tone="brand"
      checkoutHref="/checkout"
      note="Free 30-day returns"
    />
  );
}
```

`pricesIncludeTax={false}` adds the 8% sales tax on top as its own row, the US way; leave the default `true` for VAT markets where shelf prices include tax. The tax row is labeled "VAT 8%" by default, which is why `labels.tax` renames it. The default trigger is a bag button with a badge that bounces when the count grows.

> Live demo (Cart drawer): Open the bag, press + on a line and watch the total roll, then remove a line and press Undo. At phone width the cart opens as a bottom sheet you can drag down. Try it at [Cart drawer](https://wingo-ui.com/components/cart-drawer) and install it with `npx wingo-ui@latest add cart-drawer`.

## How does the checkout fit in?

The [Checkout Page](https://wingo-ui.com/components/checkout-page) block takes the same `CartItem[]` lines, so the drawer's Checkout button can lead straight into it. It owns three stages (cart, checkout, confirmation) and four steps inside the checkout (address, delivery, payment, review). Below 1024px the cart stage shows a sticky bar with the total and Continue whenever the summary button has scrolled off screen, and on phones the checkout runs as a full screen flow.

Two defaults matter before you wire it. Every data prop you leave out falls back to the demo data: the sample coupons, two San Francisco addresses, UPS and FedEx rates, test cards and a demo email. And the checkout steps default to Romanian address and tax ID rules (6 digit postal codes, the CUI check), so a US ZIP code fails until you set the country through `UIProvider` defaults. The page passes the shop data in and keeps the cart in the shared context, so the header badge empties after an order too.

```tsx
// app/(shop)/checkout/checkout.tsx
"use client";

import { useRouter } from "next/navigation";
import { CheckoutPage, type CheckoutPageProps } from "@/components/blocks/checkout-page";
import { UIProvider } from "@/lib/ui-config";
import { useCart } from "../cart-context";

// US ZIP codes and phone numbers in the address step
const US_CHECKOUT = { "checkout-steps": { country: "US" } };

// loaded on the server; any prop left out shows the block's demo data
type ShopData = Pick<
  CheckoutPageProps,
  "coupons" | "savedAddresses" | "deliveryMethods" | "lockers" | "paymentMethods" | "customerEmail" | "guest"
>;

export function Checkout(shop: ShopData) {
  const router = useRouter();
  const { items, setItems } = useCart();

  return (
    <UIProvider defaults={US_CHECKOUT}>
      <CheckoutPage
        {...shop}
        items={items}
        onItemsChange={(next) => {
          setItems(next);
          void fetch("/api/cart", { method: "PUT", body: JSON.stringify(next) });
        }}
        currency="USD"
        locale="en-US"
        freeShippingThreshold={75}
        recommended={[]}
        onPlaceOrder={async (value, totals) => {
          const response = await fetch("/api/orders", {
            method: "POST",
            body: JSON.stringify({ value, total: totals.total }),
          });
          // the message shows on the review step
          if (!response.ok) throw new Error(await response.text());
          return (await response.json()) as { orderNumber: string };
        }}
        onTrackOrder={(orderNumber) => router.push(`/account/orders/${orderNumber}`)}
        onContinueShopping={() => router.push("/")}
      />
    </UIProvider>
  );
}
```

`app/(shop)/checkout/page.tsx` stays a server component that loads the shop data and renders `<Checkout {...shop} />`. A rejected `onPlaceOrder` shows its message on the review step and keeps everything the buyer typed. Our [React checkout page tutorial](https://wingo-ui.com/blog/react-checkout-page) covers the coupon check on the server, renaming the company fields for US tax IDs and the confirmation in detail.

> Live demo (Checkout Page): Change a quantity, apply FALL15, then press Continue to delivery and go through the steps. At phone width, scroll past the summary and the sticky bar with the total slides up. Try it at [Checkout Page](https://wingo-ui.com/components/checkout-page) and install it with `npx wingo-ui@latest add checkout-page`.

## Can the marketplace blocks run a single-brand store?

Yes for home and search, with edits. The [Marketplace Home Page](https://wingo-ui.com/components/marketplace-home-page) gives you the hero search, category tiles that link to `/search?cat=…`, a swipeable promoted rail and a grid of new items. Two parts are regional. The location field in the hero lists Romanian towns from `lib/ro-locations`, and the New listings grid filters by the default town, Cluj-Napoca. For a brand store, turn off what you do not need with `showRecent`, `showApp` and `showTrust`, pass your own `categories` and `promoted`, and write `onSearch` to route to your search page. No prop hides the location field, so remove it from the installed file or point its import at your own list. The search block keeps the whole search in the URL and gives you facets with counts; the [React marketplace template tutorial](https://wingo-ui.com/blog/react-marketplace-template) wires it up step by step.

The [Listing Detail Page](https://wingo-ui.com/components/listing-detail-page) is a different story. It is built for classifieds: a seller card, Send message, Show phone, a map with the approximate area and a report dialog. There is no add to cart action, which is why the product page above uses components. If you run a multi-vendor marketplace where buyers contact sellers, it is the right block as is. Every block installs as source, so you can also edit it into a product page if you prefer its layout.

## What does each screen do on a phone?

This is the part that a desktop screenshot of a template does not show you, so here is each screen below 768px or 1024px, as the components ship:

| Screen | What changes on a phone |
| --- | --- |
| Home | The search fields stack, the location opens a full height sheet, popular searches scroll sideways, the promoted rail shows 1.3 cards |
| Search | Filters move into a full height sheet whose button counts the draft before you apply it; sort opens a sheet |
| Product page | The gallery goes full width with a swipe and a 3 / 12 counter, the viewer supports pinch, favorite and share keep 44px targets |
| Cart | A bottom sheet with a drag handle, swipe left to remove with undo, the summary folds to the total |
| Checkout | One column cart, a sticky total bar, a full screen checkout, full width confirmation buttons |

All of these shadcn ecommerce components respect reduced motion: springs turn into fades. For the safe area, tap highlight and keyboard fixes that apply to any storefront, our list of [fixes for a web app that feels native](https://wingo-ui.com/blog/web-app-that-feels-native) goes through them one by one.

## Where do you go from here?

If you want a running store without building a backend, start from Your Next Store. If you already have products in Shopify, Medusa, Stripe or your own database, a Next.js shadcn ecommerce template built from source components is the shorter path to a storefront that holds up on a phone. Try the demos above first. The commerce blocks and components in this post are Pro; a free Wingo UI account installs the free basics they build on, so you can test the CLI in your app before you pay. The [Build category](https://wingo-ui.com/blog/category/build) has the other screen by screen guides, and the [Next.js admin dashboard tutorial](https://wingo-ui.com/blog/nextjs-admin-dashboard-tutorial) covers the back office your store will need next.

## Components in this post

- [Checkout Page](https://wingo-ui.com/components/checkout-page) (Pro): A React checkout page from the cart to Order placed: an editable cart, four checkout steps and the order confirmation. Install: `npx wingo-ui@latest add checkout-page`
- [Marketplace Home Page](https://wingo-ui.com/components/marketplace-home-page) (Pro): A React marketplace home page with a what and where search, category tiles, a promoted rail, new listings near you and an app band. Install: `npx wingo-ui@latest add marketplace-home-page`
- [Listing Detail Page](https://wingo-ui.com/components/listing-detail-page) (Pro): A React listing detail page for a classifieds marketplace: photo gallery, price card, seller, map, contact actions and similar listings. Install: `npx wingo-ui@latest add listing-detail-page`
- [Cart drawer](https://wingo-ui.com/components/cart-drawer) (Pro): A React shopping cart drawer: a side panel on desktop, a bottom sheet on phones, with undo, free delivery progress, a promo code and totals. Install: `npx wingo-ui@latest add cart-drawer`
- [Product gallery](https://wingo-ui.com/components/product-gallery) (Pro): A React product gallery for detail pages: a swipeable photo stage that zooms under the mouse, a thumbnail rail and a full-screen viewer. Install: `npx wingo-ui@latest add product-gallery`

## FAQ

### Is there a free shadcn ecommerce template?

Yes. Your Next Store and Relivator are both MIT licensed on GitHub. As of November 6, 2026, Your Next Store needs an API key from its hosted commerce platform, and Relivator was last pushed on October 4, 2025 and runs on Next.js 15.3, so check both against your stack before you clone one.

### Does shadcn/ui have ecommerce components?

As of November 2026, shadcn/ui ships general primitives such as buttons, dialogs, sheets and forms, plus login, signup and sidebar blocks, but no cart, product gallery or checkout. Those come from third party registries and templates that follow the same copy-the-source model, including Wingo UI's commerce category.

### Can I use a shadcn ecommerce template with Shopify, Medusa or Stripe?

A template that is only a front end can, because you write the data calls yourself. The Wingo UI blocks take the cart lines as props and report changes through callbacks such as onItemsChange and onPlaceOrder, so the backend behind them can be any API. Hosted starters are tied to their own platform.

### What should a store cart do on a phone?

Open as a bottom sheet you can drag down, let people swipe a line away with an undo, and keep the total and the checkout button in view. The Wingo UI Cart Drawer does all three and folds its summary to the total with a Details toggle so the lines keep the room.

### Are the Wingo UI ecommerce blocks free?

No. The checkout page, cart drawer, product gallery and marketplace blocks are Pro ($8 a month, $80 a year or $150 once). The basics they are built on, such as Button, Dialog, Card and Badge, are free with an email only account. Pro comes with a 14-day refund that applies only while no Pro item has been downloaded.

---

Source: https://wingo-ui.com/blog/shadcn-ecommerce-template
