Components
Pre-built React components with SDK integration. These handle common storefront patterns like product cards, cart drawers, and checkout modals.
These components are for custom React storefronts outside Shoppex hosting. Hosted Shoppex themes use Liquid templates and the platform commerce runtime.
ProductCard
Display a product with image, price, and add-to-cart functionality.
import { ProductCard } from '@shoppex/ui';
import type { Product } from '@shoppexio/storefront';
// @validate
const product = {
uniqid: 'prod_123',
title: 'Example Product',
slug: 'example-product',
price: '9.99',
currency: 'USD',
images: [],
} satisfies Product;
<ProductCard
product={product}
href={`/product/${product.slug}`}
/>
Props
Show type badge (Subscription, Variants, Group)
Center align title and description
aspectRatio
'square' | '4/3' | '3/4' | '16/9'
default:"'4/3'"
Image aspect ratio
Custom link component (for React Router, Next.js, etc.)
Custom Link Component
For frameworks like React Router or Next.js:
import { Link } from 'react-router-dom';
<ProductCard
product={product}
href={`/product/${product.slug}`}
LinkComponent={({ href, children, className }) => (
<Link to={href} className={className}>{children}</Link>
)}
/>
Custom Rendering
<ProductCard
product={product}
renderImage={(p) => <CustomImage src={p.images[0]} />}
renderBadge={(p) => p.bestseller && <Badge>Bestseller</Badge>}
renderPrice={(p) => <CustomPrice product={p} />}
renderActions={(p) => <QuickViewButton product={p} />}
/>
Smart button that shows quantity controls when product is in cart.
import { AddToCartButton } from '@shoppex/ui';
import type { Product } from '@shoppexio/storefront';
// @validate
const product = {
uniqid: 'prod_123',
title: 'Example Product',
price: '9.99',
currency: 'USD',
images: [],
} satisfies Product;
<AddToCartButton product={product} />
Props
Show +/- controls when in cart
Callback after adding to cart
Behavior
- Not in cart: Shows “Add to Cart” button
- In cart: Shows quantity controls (- qty +)
- Out of stock: Shows disabled “Out of Stock” button
CartDrawer
Slide-over cart sidebar with full cart management.
import { CartDrawer } from '@shoppex/ui';
import { useState } from 'react';
// @validate
function App() {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<button onClick={() => setIsOpen(true)}>Open Cart</button>
<CartDrawer
isOpen={isOpen}
onClose={() => setIsOpen(false)}
checkoutHref="/checkout"
/>
</>
);
}
Props
Whether the drawer is open
Called when drawer should close
checkoutHref
string
default:"'/checkout'"
Checkout page URL
emptyMessage
string
default:"'Your cart is empty'"
Message when cart is empty
checkoutLabel
string
default:"'Checkout'"
Checkout button label
Show “Shipping and taxes calculated at checkout” note
Features
- Escape key closes drawer
- Body scroll lock when open
- Real-time cart sync
- Quantity +/- controls
- Remove item button
- Subtotal calculation
CheckoutModal
Email collection modal for starting checkout.
import { CheckoutModal } from '@shoppex/ui';
import { useState } from 'react';
// @validate
function Example() {
const [showModal, setShowModal] = useState(false);
const appliedCoupon = 'WELCOME10';
return (
<CheckoutModal
isOpen={showModal}
onClose={() => setShowModal(false)}
coupon={appliedCoupon}
/>
);
}
Props
Whether the modal is open
Called when modal should close
Automatically redirect to checkout on success
Callback on successful checkout start
Callback on checkout error
Custom Redirect
<CheckoutModal
isOpen={showModal}
onClose={() => setShowModal(false)}
autoRedirect={false}
onSuccess={({ redirectUrl, invoiceId }) => {
// Track event before redirect
analytics.track('checkout_started', { invoiceId });
window.location.href = redirectUrl;
}}
/>
SearchCommand
Cmd+K style search command palette.
import { SearchCommand, SearchBar } from '@shoppex/ui';
import { useState, useEffect } from 'react';
// @validate
function App() {
const [isOpen, setIsOpen] = useState(false);
// Open with Cmd+K
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault();
setIsOpen(true);
}
};
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
}, []);
return (
<>
<SearchBar onClick={() => setIsOpen(true)} />
<SearchCommand
isOpen={isOpen}
onClose={() => setIsOpen(false)}
getProductHref={(p) => `/product/${p.slug ?? p.uniqid}`}
/>
</>
);
}
Props
Whether the palette is open
Called when palette should close
onSelect
(product: Product) => void
Called when a product is selected
getProductHref
(product: Product) => string
Generate product link URL
Hide out of stock products
PriceDisplay
Formatted price with discount and subscription support.
import { PriceDisplay } from '@shoppex/ui';
import type { Product } from '@shoppexio/storefront';
// @validate
const product = {
uniqid: 'prod_123',
title: 'Example Product',
price: '9.99',
currency: 'USD',
images: [],
} satisfies Product;
<PriceDisplay product={product} />
Props
size
'sm' | 'md' | 'lg'
default:"'md'"
Text size
Show strikethrough original price if discounted
Show “/month” for subscriptions
Example Output
- Regular: $99.99
- Discounted: ~~149.99 ∗∗99.99** -33%
- Subscription: $9.99/month
- Range: 49.99–99.99
QuantityStepper
Standalone quantity input with +/- controls.
import { QuantityStepper } from '@shoppex/ui';
import { useState } from 'react';
// @validate
function QuantityInput() {
const [qty, setQty] = useState(1);
return (
<QuantityStepper
value={qty}
onChange={setQty}
min={1}
max={10}
size="md"
/>
);
}
Props
onChange
(value: number) => void
required
Called when quantity changes
size
'sm' | 'md' | 'lg'
default:"'md'"
Control size
Coupon code input with validation.
import { CouponInput } from '@shoppex/ui';
import { useCheckout } from '@shoppex/ui';
import { useState } from 'react';
// @validate
function CouponSection() {
const [appliedCoupon, setAppliedCoupon] = useState<string>();
const { validateCoupon } = useCheckout();
return (
<CouponInput
appliedCode={appliedCoupon}
onApply={async (code) => {
const result = await validateCoupon(code);
if (result.valid) {
setAppliedCoupon(code);
return true;
}
return false;
}}
onRemove={() => setAppliedCoupon(undefined)}
/>
);
}
Props
onApply
(code: string) => Promise<boolean>
required
Validate and apply coupon. Return true if valid.
Currently applied coupon code
Called when coupon is removed