Menu
Guides
Storefront Development

Storefront Development

SKUMatrix

Displays a table of product variations with quantity selectors for bulk purchases.

6 min read
The SKUMatrix component displays a table of product variations with quantity selectors, enabling customers to add multiple SKUs to the cart at once. This component is ideal for B2B scenarios or bulk purchasing.
The final component is a compound of the following:
  • SKUMatrix: Wraps the SKU Matrix structure that provides context and state management.
  • SKUMatrixTrigger: Renders a button that opens the SKU Matrix sidebar.
  • SKUMatrixSidebar: Renders a SlideOver containing a table with product variations, prices, availability, and quantity selectors.

_130
import React, { useEffect } from 'react'
_130
import Image from 'next/image'
_130
import {
_130
SKUMatrix,
_130
SKUMatrixTrigger,
_130
SKUMatrixSidebar,
_130
useSKUMatrix,
_130
UIProvider,
_130
} from '@faststore/ui'
_130
_130
const skus = [
_130
{
_130
id: 'monitor-black-24',
_130
name: 'Philips Monitor - Black - 24"',
_130
image: {
_130
url: 'https://storeframework.vtexassets.com/arquivos/ids/190897/Photo.jpg',
_130
alternateName: 'Black 24-inch monitor',
_130
},
_130
inventory: 8,
_130
availability: 'inStock',
_130
price: 249,
_130
listPrice: 299,
_130
priceWithTaxes: 249,
_130
listPriceWithTaxes: 299,
_130
specifications: {
_130
color: 'Black',
_130
size: '24"',
_130
safetyrating: 'A',
_130
},
_130
selectedCount: 0,
_130
offers: {
_130
highPrice: 299,
_130
lowPrice: 249,
_130
lowPriceWithTaxes: 249,
_130
offerCount: 1,
_130
priceCurrency: 'USD',
_130
offers: [
_130
{
_130
listPrice: 299,
_130
listPriceWithTaxes: 299,
_130
sellingPrice: 249,
_130
priceCurrency: 'USD',
_130
price: 249,
_130
priceWithTaxes: 249,
_130
priceValidUntil: '2030-12-31T23:59:59Z',
_130
itemCondition: 'https://schema.org/NewCondition',
_130
availability: 'inStock',
_130
quantity: 8,
_130
},
_130
],
_130
},
_130
},
_130
]
_130
_130
const ImageComponent = ({
_130
src,
_130
alt,
_130
width = 64,
_130
height = 64,
_130
}: {
_130
src: string
_130
alt: string
_130
width?: number
_130
height?: number
_130
}) => (
_130
<Image
_130
src={src}
_130
alt={alt}
_130
width={width}
_130
height={height}
_130
style={{ objectFit: 'contain' }}
_130
/>
_130
)
_130
_130
function SKUMatrixContent() {
_130
const { setAllVariantProducts } = useSKUMatrix()
_130
_130
useEffect(() => {
_130
setAllVariantProducts(skus)
_130
}, [setAllVariantProducts])
_130
_130
return (
_130
<>
_130
<SKUMatrixTrigger variant="secondary">Plan restock</SKUMatrixTrigger>
_130
_130
<SKUMatrixSidebar
_130
title="Philips Monitor"
_130
direction="rightSide"
_130
size="partial"
_130
loading={false}
_130
columns={{
_130
name: 'Variation',
_130
additionalColumns: [
_130
{ label: 'Color', value: 'color' },
_130
{ label: 'Screen sizes', value: 'size' },
_130
{ label: 'Safety rating', value: 'safetyrating' },
_130
],
_130
availability: {
_130
label: 'Availability',
_130
stockDisplaySettings: 'showStockQuantity',
_130
},
_130
price: 'Price',
_130
quantitySelector: 'Quantity',
_130
}}
_130
buyProps={{
_130
'data-testid': 'bulk-add',
_130
'data-sku': 'safety-boot',
_130
'data-seller': 'seller-1',
_130
onClick: () => {
_130
/* call cart mutation */
_130
},
_130
}}
_130
overlayProps={{ 'aria-label': 'SKU matrix overlay' }}
_130
ImageComponent={ImageComponent}
_130
>
_130
{null}
_130
</SKUMatrixSidebar>
_130
</>
_130
)
_130
}
_130
_130
export default function ProductPageSkuMatrix() {
_130
return (
_130
<UIProvider>
_130
<SKUMatrix data-testid="sku-matrix">
_130
<SKUMatrixContent />
_130
</SKUMatrix>
_130
</UIProvider>
_130
)
_130
}


Usage

Import the component

Import the compound from @faststore/ui.

_10
import {
_10
SKUMatrix,
_10
SKUMatrixTrigger,
_10
SKUMatrixSidebar,
_10
useSKUMatrix,
_10
} from "@faststore/ui";

Import styles

Add the sidebar styling tokens and layout rules to your theme stylesheet:

_10
@import "@faststore/ui/src/components/organisms/SKUMatrix/styles.scss";

Follow the Importing FastStore UI component styles instructions. Additionally, the quantity selector, badges, and table components rely on their respective partials.

Examples

Loading State

While fetching product variants, display skeleton loaders to improve perceived performance by setting loading={true} on SKUMatrixSidebar.
Example
Code

Full Width Sidebar

Display the matrix in a full-width slide-over for complex product catalogs with many columns by setting size="full". This example also sets stockDisplaySettings to showAvailability, which renders an availability badge instead of the inventory quantity.

_96
import { useEffect } from 'react'
_96
import {
_96
SKUMatrix,
_96
SKUMatrixSidebar,
_96
SKUMatrixTrigger,
_96
UIProvider,
_96
useSKUMatrix,
_96
} from '@faststore/ui'
_96
_96
const skus = [{
_96
id: 'monitor-black-24',
_96
name: 'Philips Monitor - Black - 24"',
_96
image: {
_96
url: 'https://storeframework.vtexassets.com/arquivos/ids/190897/Photo.jpg',
_96
alternateName: 'Black 24-inch monitor',
_96
},
_96
inventory: 8,
_96
availability: 'inStock',
_96
price: 249,
_96
listPrice: 299,
_96
priceWithTaxes: 249,
_96
listPriceWithTaxes: 299,
_96
specifications: { color: 'Black', size: '24"', material: 'Plastic' },
_96
selectedCount: 0,
_96
offers: {
_96
highPrice: 299,
_96
lowPrice: 249,
_96
lowPriceWithTaxes: 249,
_96
offerCount: 1,
_96
priceCurrency: 'USD',
_96
offers: [{
_96
listPrice: 299,
_96
listPriceWithTaxes: 299,
_96
sellingPrice: 249,
_96
priceCurrency: 'USD',
_96
price: 249,
_96
priceWithTaxes: 249,
_96
priceValidUntil: '2030-12-31T23:59:59Z',
_96
itemCondition: 'https://schema.org/NewCondition',
_96
availability: 'inStock',
_96
quantity: 8,
_96
}],
_96
},
_96
}]
_96
_96
function FullWidthMatrix() {
_96
const { setAllVariantProducts } = useSKUMatrix()
_96
_96
useEffect(() => {
_96
setAllVariantProducts(skus)
_96
}, [setAllVariantProducts])
_96
_96
return (
_96
<>
_96
<SKUMatrixTrigger variant="secondary">View all options</SKUMatrixTrigger>
_96
<SKUMatrixSidebar
_96
title="Select your products"
_96
size="full"
_96
direction="rightSide"
_96
columns={{
_96
name: 'Product',
_96
additionalColumns: [
_96
{ label: 'Color', value: 'color' },
_96
{ label: 'Size', value: 'size' },
_96
{ label: 'Material', value: 'material' },
_96
],
_96
availability: {
_96
label: 'Availability',
_96
stockDisplaySettings: 'showAvailability',
_96
},
_96
price: 'Unit Price',
_96
quantitySelector: 'Qty',
_96
}}
_96
buyProps={{
_96
'data-testid': 'bulk-add',
_96
'data-sku': 'full-width',
_96
'data-seller': 'seller-1',
_96
onClick: () => {},
_96
}}
_96
ImageComponent={({ src, alt }) => <img src={src} alt={alt} />}
_96
>
_96
{null}
_96
</SKUMatrixSidebar>
_96
</>
_96
)
_96
}
_96
_96
export default function FullWidthExample() {
_96
return (
_96
<UIProvider>
_96
<SKUMatrix>
_96
<FullWidthMatrix />
_96
</SKUMatrix>
_96
</UIProvider>
_96
)
_96
}


Left Side Direction

Open the sidebar from the left side of the screen by setting direction="leftSide".
Example
Code

Design tokens

Local tokenDefault value/Global token linked
--fs-sku-matrix-sidebar-bkg-color
var(--fs-color-body-bkg)
--fs-sku-matrix-sidebar-title-sizevar(--fs-text-size-6)
--fs-sku-matrix-sidebar-title-text-weightvar(--fs-text-weight-semibold)
--fs-sku-matrix-sidebar-table-cell-font-sizevar(--fs-text-size-tiny)
--fs-sku-matrix-sidebar-table-cell-text-weightvar(--fs-text-weight-medium)
--fs-sku-matrix-sidebar-table-cell-image-widthvar(--fs-spacing-7)
--fs-sku-matrix-sidebar-table-cell-image-border-radiusvar(--fs-border-radius)
--fs-sku-matrix-slide-over-partial-gapcalc(2 * var(--fs-grid-padding))
--fs-sku-matrix-slide-over-partial-width-mobilecalc(100vw - var(--fs-sku-matrix-slide-over-partial-gap))

Data attributes

Use these selectors to customize styles or attach test logic:
  • data-fs-sku-matrix
  • data-testid="fs-sku-matrix"
  • data-fs-sku-matrix-sidebar
  • data-fs-sku-matrix-sidebar-title
  • data-fs-sku-matrix-sidebar-cell-image
  • data-fs-sku-matrix-sidebar-table-price
  • data-fs-sku-matrix-sidebar-table-cell-quantity-selector
  • data-fs-sku-matrix-sidebar-table-action
  • data-fs-sku-matrix-sidebar-footer
  • SlideOver attributes propagated to the sidebar: data-fs-slide-over, data-fs-slide-over-direction, data-fs-slide-over-size, data-fs-slide-over-state

Props

NameTypeDescriptionDefault
testIdstringID to find this component in testing tools (e.g.: cypress, testing library, and jest).fs-sku-matrix
NameTypeDescriptionDefault
testIdstringID to find this component in testing tools (e.g.: cypress, testing library, and jest).
variant"primary" | "secondary" | "tertiary"Specifies the component color variant.secondary
size"small" | "regular"Specifies the size variant.
inversefalse | trueDefines the use of inverted colors.
disabledfalse | trueSpecifies that this button should be disabled.
iconstring | number | false | true | {} | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortalA React component that will be rendered as an icon.
loadingfalse | trueBoolean that represents a loading state.
loadingLabelstringSpecifies a label for loading state.
iconPosition"left" | "right"Specifies where the icon should be positioned
NameTypeDescriptionDefault
titlestringTitle for the SKUMatrixSidebar component.
columns*VariationProductColumnRepresents the variations products to building the table.
buyProps*{ 'data-testid': string; 'data-sku': string; 'data-seller': string; onClick(e: React.MouseEvent<HTMLButtonElement>): void; }Properties related to the 'add to cart' button
formatterPriceFormatterFormatter function that transforms the raw price value and render the result.
loadingfalse | trueCheck if some result is still loading before render the result.
ImageComponentFunctionComponent<{ src: string; alt: string; width?: number; height?: number; }>Function that returns a React component that will be used to render images.({ src, alt, ...otherProps }) => <img src={src} alt={alt} {...otherProps} />
aria-labelledbystringIdentifies the element (or elements) that labels the current element. @see aria-labelledby https://www.w3.org/TR/wai-aria-1.1/#aria-labelledby
children*string | number | false | true | {} | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortalChildren or function as a children.
testIdstringID to find this component in testing tools (e.g.: cypress, testing library, and jest).
onEntered() => voidCallback function when the modal is opened.
onDismiss() => voidThis function is called whenever the user clicks outside. the modal content
overlayPropsPropsProps forwarded to the `Overlay` component.
disableEscapeKeyDownfalse | trueDisable being closed using the Escape key.
direction"leftSide" | "rightSide"Represents the side that the SlideOver comes from.rightSide
size"full" | "partial"Represents the size of the SlideOver.partial

Best practices

✅ Dos

  • Use concise variation labels (example: "Black - 42") to ensure the table remains scannable on partial-width slide-overs.
  • Keep price and availability in sync with your store OMS; display a toast message when quantity adjustments are coerced.
  • Localize both column headers and toast messages for multinational catalogs.

❌ Don'ts

  • Don't expose the matrix when a product is single-SKU; it adds unnecessary friction.
  • Don't rely on color alone for stock communication; pair badge colors with descriptive text.
  • Don't allow quantities beyond available stock; the component exposes validation hooks for this reason.
  • Don't hardcode dimensions; merchants should be able to adjust columns per category via Headless CMS.