Skip to main content

CartItems

Description​

A headless render props component that provides cart items data and operations. It reads from CartContext and exposes items, loading states, item count, and a remove handler. It contributes no item markup of its own, but it does render a div.cart-items wrapper plus the cartItemsBefore and cartItemsAfter Areas.

Role in Theming​

CartItems is one of EverShop's headless components — it owns the data access and operations while leaving all UI decisions to its parent:

  • CartItems renders almost nothing. Around whatever its children function returns it emits a div.cart-items wrapper and two Areas — cartItemsBefore and cartItemsAfter — which are themselves extension points you can drop components into.
  • Theme developers do not override CartItems. Instead, they override the parent components that consume it (DefaultCartItemList, DefaultMiniCartDropdown).
  • The data layer stays stable across themes. Item data extraction, tax display logic, loading states, and remove operations are encapsulated in CartItems.

Theme Override Points​

CartItems is consumed by these components, which are the actual override targets for theme developers:

Parent ComponentRouteOverride Path in Theme
DefaultCartItemListcartthemes/<name>/src/components/frontStore/cart/DefaultCartItemList.tsx
DefaultMiniCartDropdownall (via MiniCart)themes/<name>/src/components/frontStore/cart/DefaultMiniCartDropdown.tsx

Import​

import { CartItems } from '@components/frontStore/cart/CartItems';

Usage​

import { CartItems } from '@components/frontStore/cart/CartItems';

function Cart() {
return (
<CartItems>
{({ items, loading, isEmpty, totalItems, onRemoveItem }) => (
<div>
{isEmpty ? (
<p>Your cart is empty</p>
) : (
<ul>
{items.map(item => (
<li key={item.cartItemId}>
{item.productName} - {item.qty}
<button onClick={() => onRemoveItem(item.cartItemId)}>
Remove
</button>
</li>
))}
</ul>
)}
</div>
)}
</CartItems>
);
}

Props​

NameTypeRequiredDescription
childrenRenderFunctionYesRender function receiving cart data

Render Function Props​

The render function receives an object with:

NameTypeDescription
itemsCartItem[]Array of cart items
showPriceIncludingTaxbooleanWhether to show prices with tax
loadingbooleanTrue when cart is loading
isEmptybooleanTrue when cart has no items
totalItemsnumberTotal quantity of items
onRemoveItem(itemId: string) => Promise<void>Function to remove an item

CartItem Interface​

Each item in the items array has:

interface CartItem {
cartItemId: string;
productId: string;
productSku: string;
productName: string;
productUrl: string;
thumbnail?: string;
qty: number;
productPrice: { value: number; text: string };
finalPrice: { value: number; text: string };
finalPriceInclTax: { value: number; text: string };
lineTotal: { value: number; text: string };
lineTotalInclTax: { value: number; text: string };
subTotal: { value: number; text: string };
variantOptions?: Array<{
attributeCode: string;
attributeName: string;
optionText: string;
}>;
}

Price Fields​

Different price fields available:

  • productPrice: Base product price
  • productPriceInclTax: Base price with tax
  • finalPrice: Price after discounts (excl. tax)
  • finalPriceInclTax: Price after discounts (incl. tax)
  • lineTotal: Item total (qty x price, excl. tax)
  • lineTotalInclTax: Item total (qty x price, incl. tax)
  • subTotal: Subtotal for the item

Use showPriceIncludingTax to determine which price to display.

Examples​

Basic Cart List​

import { CartItems } from '@components/frontStore/cart/CartItems';

function ShoppingCart() {
return (
<CartItems>
{({ items, loading, isEmpty, onRemoveItem }) => {
if (loading) {
return <div>Loading cart...</div>;
}

if (isEmpty) {
return <p>Your cart is empty</p>;
}

return (
<div>
{items.map(item => (
<div key={item.cartItemId}>
<h3>{item.productName}</h3>
<p>Price: {item.productPrice.text}</p>
<p>Quantity: {item.qty}</p>
<button onClick={() => onRemoveItem(item.cartItemId)}>
Remove
</button>
</div>
))}
</div>
);
}}
</CartItems>
);
}

With Tax-Aware Pricing​

import { CartItems } from '@components/frontStore/cart/CartItems';

function CartTable() {
return (
<CartItems>
{({ items, showPriceIncludingTax, onRemoveItem }) => (
<table>
<thead>
<tr>
<th>Product</th>
<th>Price</th>
<th>Quantity</th>
<th>Total</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{items.map(item => {
const price = showPriceIncludingTax
? item.finalPriceInclTax
: item.finalPrice;
const total = showPriceIncludingTax
? item.lineTotalInclTax
: item.lineTotal;

return (
<tr key={item.cartItemId}>
<td>{item.productName}</td>
<td>{price.text}</td>
<td>{item.qty}</td>
<td>{total.text}</td>
<td>
<button onClick={() => onRemoveItem(item.cartItemId)}>
Remove
</button>
</td>
</tr>
);
})}
</tbody>
</table>
)}
</CartItems>
);
}

Theme Override Example​

A theme developer overrides DefaultCartItemList to provide a custom cart item layout. Create this file in your theme:

themes/my-theme/src/components/frontStore/cart/DefaultCartItemList.tsx

import { CartItems } from '@components/frontStore/cart/CartItems';
import { ItemQuantity } from '@components/frontStore/cart/ItemQuantity';

function DefaultCartItemList() {
return (
<CartItems>
{({ items, loading, isEmpty, showPriceIncludingTax, onRemoveItem }) => {
if (isEmpty) {
return (
<div className="my-theme-empty-cart">
<p>Nothing here yet</p>
<a href="/products">Start Shopping</a>
</div>
);
}

return (
<div className="my-theme-cart-items">
{items.map(item => {
const price = showPriceIncludingTax
? item.finalPriceInclTax
: item.finalPrice;

return (
<div key={item.cartItemId} className="my-theme-cart-item">
{item.thumbnail && (
<img src={item.thumbnail} alt={item.productName} />
)}
<div>
<h3>{item.productName}</h3>
<p>{price.text}</p>
{item.variantOptions?.map(opt => (
<span key={opt.attributeCode}>
{opt.attributeName}: {opt.optionText}
</span>
))}
</div>
<ItemQuantity
cartItemId={item.cartItemId}
initialValue={item.qty}
>
{({ quantity, increase, decrease, loading: qtyLoading }) => (
<div>
<button onClick={decrease} disabled={qtyLoading}>-</button>
<span>{quantity}</span>
<button onClick={increase} disabled={qtyLoading}>+</button>
</div>
)}
</ItemQuantity>
<button onClick={() => onRemoveItem(item.cartItemId)}>
Remove
</button>
</div>
);
})}
</div>
);
}}
</CartItems>
);
}

export default DefaultCartItemList;

Features​

  • Headless: Renders no UI — full control via render props
  • Loading State: Automatic loading management
  • Empty State: isEmpty flag for easy empty cart handling
  • Item Count: Total items count
  • Remove Handler: Built-in remove functionality
  • Tax Configuration: Respects tax display settings
  • Type Safe: Full TypeScript support


Support us


EverShop is an open-source project that relies on community support. If you find our project useful, please consider sponsoring us.