Replace the product card
Use your own product card in every list, slider and grid the engine renders.
Framework: TanStack Start
Registration below lives in app/router.tsx, which is specific to TanStack Start. See what depends on the framework.
Use this when you want one product card design everywhere a product is shown.
Goal: the engine's listing pages and home blocks all render MyProductCard.
The engine's ProductCard checks a component registry for the key product:card and renders the override if one exists. Listing pages and the engine's product home blocks all render that same ProductCard, so one override covers them. Pages you write should import ProductCard from @salla.sa/twilight-theme-engine/product (not from a deep path) so they get it too.
Write the card
It receives the engine's ProductCardProps (product, layout, className, ...).
import type { ProductCardProps } from '@salla.sa/twilight-theme-engine/product';
import { useMoney } from '@salla.sa/twilight-theme-engine/hooks/useMoney';
export function MyProductCard({ product, className }: ProductCardProps) {
const { format } = useMoney();
const onSale = product.is_on_sale && product.sale_price;
return (
<div className={`s-product-card-entry ${className ?? ''}`}>
<a href={product.url}>
<img src={product.image?.url} alt={product.image?.alt ?? product.name} loading="lazy" />
<h3>{product.name}</h3>
</a>
<span>{format(onSale ? product.sale_price : product.price)}</span>
</div>
);
}Register, then override
Both calls are needed (see the first trap). Do it at module scope.
import { registry } from '@salla.sa/twilight-theme-engine';
import { MyProductCard } from './components/product/MyProductCard';
registry.register('product:card', MyProductCard);
registry.override('product:card', MyProductCard);Check it worked
Open a category page and the home page: every product uses your markup.
Traps
overridealone does nothing. The engine only switches when the registry has an original entry, andoverriderecords one only if the key already exists. Without a priorregister, the built-in card keeps rendering.- Do not render the engine card inside your override. It resolves to your component again and loops forever. A wrapper around the engine card works only for pages that import it directly, as the reference theme's
ProductCarddoes for its own listing pages. - Register before the first render. The engine reads the override once per card, so late registration is ignored by cards already mounted.
- Cover what the card does. The built-in card handles the wishlist, add-to-cart (
salla-add-product-button), out-of-stock and badges. Drop them and you drop the feature. - Format raw prices with useMoney so currency and digits follow the locale.
Replace the whole product list page
The product lists work the same way one level up. Every list the engine serves (category, search, tag, brand and products) renders whatever is registered as product:list-page, and shows product:list-skeleton while it loads. Register your own page once instead of replacing each route:
import { lazy } from 'react';
import { registry } from '@salla.sa/twilight-theme-engine';
import { MyListSkeleton } from './components/product/MyListSkeleton';
// Lazy, so the page loads with a product list instead of with every page.
const MyListPage = lazy(() =>
import('./components/product/MyListPage').then((m) => ({ default: m.MyListPage }))
);
registry.register('product:list-page', MyListPage);
registry.register('product:list-skeleton', MyListSkeleton);- The page receives the loader data,
ProductListLoaderDatafrom@salla.sa/twilight-theme-engine/routes/product-listing:page,source,query,products,paginationand, for categories and search,filters. Branch onsource.typewhere the lists should differ. - The skeleton receives
{ hideFilters }, which istrueon every list but categories and search. - A plain
registeris enough for these two keys; there is nooverridestep. Without them, the engine's ownProductListPageand page skeleton render. - Keep what the engine page does if you need it: the
product:list.*hook slots, infinite scroll frompagination.next, the sort menu and the filters. The reference theme'sapp/components/product/ProductListPage.tsxis a complete example.
Next
- ProductCard: the props and layouts you can reuse.
- Add a home block and Fork a built-in route when you need more than the card.