@haus-storefront-react/product-variant-selector
v0.0.50
Published
A headless, flexible product variant selector component for e-commerce storefronts. Supports multiple UI patterns (pills, dropdowns, radio buttons), option availability tracking, and variant selection. Built with TypeScript, accessibility-first design, an
Readme
Product Variant Selector Component
A headless, flexible product variant selector component for e-commerce storefronts. Supports multiple UI patterns (pills, dropdowns, radio buttons), option availability tracking, and variant selection. Built with TypeScript, accessibility-first design, and platform-agnostic architecture.
Installation
npm install @haus-storefront-react/product-variant-selector
# or
yarn add @haus-storefront-react/product-variant-selectorUsage Example (Pills)
import { ProductVariantSelector } from '@haus-storefront-react/product-variant-selector'
const productId = '123'
const initialVariantId = 'variant-456'
<ProductVariantSelector.Root
productId={productId}
initialVariantId={initialVariantId}
>
{({ optionGroups }) => (
<div>
{/* Render option groups */}
{optionGroups.map((optionGroup) => (
<ProductVariantSelector.OptionGroup
key={optionGroup.code}
optionGroup={optionGroup}
>
{({ options, selectOption, selectedOptionId }) => (
<div>
<label>{optionGroup.name}</label>
{/* Pill-style options */}
<div style={{ display: 'flex', gap: '8px' }}>
{options.map((option) => (
<ProductVariantSelector.Option
key={option.id}
optionId={option.id}
>
{({ option: optionData, selectOption, isSelected, isAvailable }) => (
<button
onClick={() => selectOption(optionData.id)}
disabled={!isAvailable}
style={{
padding: '8px 16px',
borderRadius: '21px',
border: isSelected ? '2px solid #2c5282' : '1px solid #ccc',
backgroundColor: isSelected ? '#2c5282' : 'white',
color: isSelected ? 'white' : '#333',
opacity: isAvailable ? 1 : 0.5,
cursor: isAvailable ? 'pointer' : 'not-allowed',
}}
>
{optionData.name}
</button>
)}
</ProductVariantSelector.Option>
))}
</div>
</div>
)}
</ProductVariantSelector.OptionGroup>
))}
</div>
)}
</ProductVariantSelector.Root>Dropdown Example
<ProductVariantSelector.Root productId={productId} initialVariantId={initialVariantId}>
{({ optionGroups, selectedVariant }) => (
<div>
{optionGroups.map((optionGroup) => (
<ProductVariantSelector.OptionGroup key={optionGroup.code} optionGroup={optionGroup}>
{({ options, selectOption, selectedOptionId }) => (
<div>
<label>{optionGroup.name}</label>
<select
onChange={(e) => {
if (e.target.value) {
selectOption(e.target.value)
}
}}
value={selectedOptionId || ''}
>
<option value="">Select {optionGroup.name}</option>
{options.map((option) => (
<ProductVariantSelector.Option key={option.id} optionId={option.id} asChild>
{({ option: optionData, isSelected, isAvailable }) => (
<option value={optionData.id} disabled={!isAvailable} selected={isSelected}>
{optionData.name}
</option>
)}
</ProductVariantSelector.Option>
))}
</select>
</div>
)}
</ProductVariantSelector.OptionGroup>
))}
</div>
)}
</ProductVariantSelector.Root>Features
- 🎯 Product variant selection that can be used with multiple UI patterns
- ♿ Accessibility-first, platform-agnostic
- 🔄 Real-time option availability tracking
- 🎨 Headless, fully customizable
- ⚡ TypeScript support
- 🔗 Compound component pattern
- 📱 Responsive design support
- 🎛️ Flexible option rendering (pills, dropdowns, radio buttons)
API Reference
<ProductVariantSelector.Root>
Context provider for product variant selection functionality.
Props:
productId?: string– Product ID for fetching variant dataproductSlug?: string– Product slug (alternative to productId)initialVariantId?: string– Initial selected variant IDautoSelectOnInvalidOption?: boolean– Auto-select first available variant when current selection becomes invalidhideUnavailableOptions?: boolean– Hide options that are not available with current selectionchildren: (context) => ReactNode– Render prop with variant selector context
Context:
{
optionGroups: ProductOptionGroup[]
selectedVariant: ProductVariant | undefined
}<ProductVariantSelector.OptionGroup>
Wraps a group of related options (e.g., "Size", "Color").
Props:
optionGroup: ProductOptionGroup– The option group objectasChild?: boolean– Render as child componentchildren: (context) => ReactNode– Render prop with option group context
Context:
{
options: ProductOption[]
selectOption: (optionId: string) => void
selectedOptionId?: string
}<ProductVariantSelector.Option>
Represents a single option within an option group.
Props:
optionId: string– The option IDasChild?: boolean– Render as child component (useful for native HTML elements like<option>)children: (context) => ReactNode– Render prop with option context
Context:
{
option: ProductOption
selectOption: (optionId: string) => void
isSelected: boolean
isAvailable: boolean
}UI Patterns
Pill/Button Pattern
Use for visual, interactive option selection:
<ProductVariantSelector.Option optionId={option.id}>
{({ option, selectOption, isSelected, isAvailable }) => (
<button
onClick={() => selectOption(option.id)}
disabled={!isAvailable}
className={isSelected ? 'selected' : isAvailable ? 'available' : 'disabled'}
>
{option.name}
</button>
)}
</ProductVariantSelector.Option>Dropdown Pattern
Use with native <select> elements:
<ProductVariantSelector.Option optionId={option.id} asChild>
{({ option, isSelected, isAvailable }) => (
<option value={option.id} disabled={!isAvailable} selected={isSelected}>
{option.name}
</option>
)}
</ProductVariantSelector.Option>Radio Button Pattern
Use for form-like selection:
<ProductVariantSelector.Option optionId={option.id}>
{({ option, selectOption, isSelected, isAvailable }) => (
<label>
<input
type="radio"
checked={isSelected}
disabled={!isAvailable}
onChange={() => selectOption(option.id)}
/>
{option.name}
</label>
)}
</ProductVariantSelector.Option>State Management
The component automatically manages:
- Selected options per option group
- Available options based on current selection
- Variant matching to find the selected variant
- Option availability tracking
Option Availability
Options become unavailable when:
- They don't exist in any variant with the current selection
Selection Logic
- Selecting an option updates all option groups
- Unavailable options that does not exsist in a available product variant are automatically filtered (if
hideUnavailableOptionsis true) - Invalid selections are automatically corrected (if
autoSelectOnInvalidOptionis true)
