Skip to content

Search is only available in production builds. Try building and previewing the site to test it out locally.

SearchResults container

The SearchResults container displays products for the current search. You can customize it with slots and run separate instances; the container handles loading states, errors, and real-time updates.

import SearchResults from '@dropins/storefront-product-discovery/containers/SearchResults.js';

The SearchResults container provides the following configuration options:

Option Type Req? Description
skeletonCountnumberNo Number of skeleton items to show while loading. Default: 12.
scopestringNo Scope identifier for isolated instances. Default: undefined.
routeProductfunctionNo Function to generate product URLs. Receives a Product object as parameter.
onSearchResultfunctionNo Callback when search results are received. Receives products array.
slotsobjectNo Custom slot renderers for Header, Footer, ProductActions.
// Basic search results container
await provider.render(SearchResults, {
skeletonCount: 12,
routeProduct: (product) => `/product/${product.sku}`
})($container);
// With scope for isolated instance
await provider.render(SearchResults, {
skeletonCount: 6,
scope: 'popover',
routeProduct: (product) => `/product/${product.sku}`
})($container);
// With callback for custom handling
await provider.render(SearchResults, {
skeletonCount: 12,
onSearchResult: (products) => {
console.log('Received', products.length, 'results');
}
})($container);

The SearchResults container supports several customization slots. For detailed information about available slots and their usage, see Product Discovery Slots.

slots: {
Header: (ctx) => {
const header = document.createElement('div');
header.innerHTML = `
<h2>Search Results</h2>
<p>Found ${ctx.products.length} products</p>
`;
ctx.appendChild(header);
},
ProductActions: (ctx) => {
const actions = document.createElement('div');
const addToCartBtn = document.createElement('button');
addToCartBtn.textContent = 'Add to Cart';
addToCartBtn.onclick = () => addToCart(ctx.product);
const quickViewBtn = document.createElement('button');
quickViewBtn.textContent = 'Quick View';
quickViewBtn.onclick = () => openQuickView(ctx.product);
actions.appendChild(addToCartBtn);
actions.appendChild(quickViewBtn);
ctx.appendChild(actions);
}
}
  • Skeleton Loading: Configurable skeleton items while fetching results
  • Loading Indicators: Automatic loading state management
  • Error Handling: Graceful error display with user-friendly messages
  • Empty States: Appropriate messaging when no results are found

The container automatically updates when search events are received:

  1. Search Start: Shows loading state with skeleton items
  2. Search Results: Updates with new product data
  3. Search Complete: Removes loading state
  4. Error Handling: Displays error state if applicable

Customize how products link to their detail pages:

// Basic product routing
routeProduct: (product) => `/product/${product.sku}`
await provider.render(SearchResults, {
skeletonCount: 12,
routeProduct: (product) => `/product/${product.sku}`
})($searchResults);
await render.render(SearchResults, {
skeletonCount: pageSize,
scope: 'popover',
routeProduct: ({ urlKey, sku }) => rootLink(`/products/${urlKey}/${sku}`),
onSearchResult: (results) => {
searchResult.style.display = results.length > 0 ? 'block' : 'none';
},
slots: {
Footer: async (ctx) => {
// View all results button
const viewAllResultsWrapper = document.createElement('div');
const viewAllResultsButton = await UI.render(Button, {
children: labels.Global?.SearchViewAll,
variant: 'secondary',
href: rootLink('/search'),
})(viewAllResultsWrapper);
ctx.appendChild(viewAllResultsWrapper);
ctx.onChange((next) => {
viewAllResultsButton?.setProps((prev) => ({
...prev,
href: `${rootLink('/search')}?q=${encodeURIComponent(next.variables?.phrase || '')}`,
}));
});
},
},
})(searchResult);
await provider.render(SearchResults, {
routeProduct: (product) => rootLink(`/products/${product.urlKey}/${product.sku}`),
slots: {
ProductActions: (ctx) => {
// Wrapper
const wrapper = document.createElement('div');
wrapper.className = 'product-discovery-product-actions';
// Add to Cart Button
const addToCartBtn = document.createElement('button');
addToCartBtn.className = 'product-discovery-product-actions__add-to-cart';
addToCartBtn.innerText = placeholders.Global.AddToCartLabel;
addToCartBtn.addEventListener('click', () => console.log(ctx.product));
// Append element to Slot
ctx.appendChild(addToCartBtn);
},
},
})($productList);
  1. Scope Management: Use unique scope identifiers for multiple instances
  2. Slot Customization: Use slots for merchant-specific customization
  3. Performance: Use lazy loading for off-screen containers
  4. Error Handling: Let the container handle errors automatically
  5. Real-time Updates: Let the container handle updates automatically
  1. Container Not Updating: Check scope configuration and event handling
  2. Slots Not Rendering: Ensure the element is added to the slot using ctx.appendChild(), ctx.replaceWith(), etc.
  3. Styling Conflicts: Use specific CSS classes to avoid conflicts