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 aSlideOvercontaining a table with product variations, prices, availability, and quantity selectors.
_130import React, { useEffect } from 'react'_130import Image from 'next/image'_130import {_130 SKUMatrix,_130 SKUMatrixTrigger,_130 SKUMatrixSidebar,_130 useSKUMatrix,_130 UIProvider,_130} from '@faststore/ui'_130_130const 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_130const 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_130function 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_130export 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.
_10import {_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.
_96import { useEffect } from 'react'_96import {_96 SKUMatrix,_96 SKUMatrixSidebar,_96 SKUMatrixTrigger,_96 UIProvider,_96 useSKUMatrix,_96} from '@faststore/ui'_96_96const 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_96function 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_96export 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 token | Default value/Global token linked |
|---|---|
--fs-sku-matrix-sidebar-bkg-color | var(--fs-color-body-bkg) |
--fs-sku-matrix-sidebar-title-size | var(--fs-text-size-6) |
--fs-sku-matrix-sidebar-title-text-weight | var(--fs-text-weight-semibold) |
--fs-sku-matrix-sidebar-table-cell-font-size | var(--fs-text-size-tiny) |
--fs-sku-matrix-sidebar-table-cell-text-weight | var(--fs-text-weight-medium) |
--fs-sku-matrix-sidebar-table-cell-image-width | var(--fs-spacing-7) |
--fs-sku-matrix-sidebar-table-cell-image-border-radius | var(--fs-border-radius) |
--fs-sku-matrix-slide-over-partial-gap | calc(2 * var(--fs-grid-padding)) |
--fs-sku-matrix-slide-over-partial-width-mobile | calc(100vw - var(--fs-sku-matrix-slide-over-partial-gap)) |
Data attributes
Use these selectors to customize styles or attach test logic:
data-fs-sku-matrixdata-testid="fs-sku-matrix"data-fs-sku-matrix-sidebardata-fs-sku-matrix-sidebar-titledata-fs-sku-matrix-sidebar-cell-imagedata-fs-sku-matrix-sidebar-table-pricedata-fs-sku-matrix-sidebar-table-cell-quantity-selectordata-fs-sku-matrix-sidebar-table-actiondata-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
| Name | Type | Description | Default |
|---|---|---|---|
| testId | string | ID to find this component in testing tools (e.g.: cypress, testing library, and jest). | fs-sku-matrix |
| Name | Type | Description | Default |
|---|---|---|---|
| testId | string | ID 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. | |
| inverse | false | true | Defines the use of inverted colors. | |
| disabled | false | true | Specifies that this button should be disabled. | |
| icon | string | number | false | true | {} | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | A React component that will be rendered as an icon. | |
| loading | false | true | Boolean that represents a loading state. | |
| loadingLabel | string | Specifies a label for loading state. | |
| iconPosition | "left" | "right" | Specifies where the icon should be positioned |
| Name | Type | Description | Default |
|---|---|---|---|
| title | string | Title for the SKUMatrixSidebar component. | |
| columns* | VariationProductColumn | Represents 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 | |
| formatter | PriceFormatter | Formatter function that transforms the raw price value and render the result. | |
| loading | false | true | Check if some result is still loading before render the result. | |
| ImageComponent | FunctionComponent<{ 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-labelledby | string | Identifies 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> | ReactPortal | Children or function as a children. | |
| testId | string | ID to find this component in testing tools (e.g.: cypress, testing library, and jest). | |
| onEntered | () => void | Callback function when the modal is opened. | |
| onDismiss | () => void | This function is called whenever the user clicks outside. the modal content | |
| overlayProps | Props | Props forwarded to the `Overlay` component. | |
| disableEscapeKeyDown | false | true | Disable 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.