@commerce-atoms/variants
v0.0.3
Published
Pure variant selection logic for Shopify products
Maintainers
Readme
@commerce-atoms/variants
Pure variant selection logic for Shopify products.
Purpose
Provides utilities for variant selection UX that Hydrogen doesn't standardize:
- Finding variants by selected options
- Picking default variants with policies
- Computing availability maps (which options are still valid)
- URL ↔ selection state synchronization
- Normalizing selected options
Non-goals
This package explicitly does NOT:
- ❌ Provide UI components (
<VariantSelector />) - ❌ Fetch products from Shopify
- ❌ Manage React state or context
- ❌ Include routing logic
- ❌ Handle cart operations
API
Core Functions
// Find a variant from selected options
import {findVariant} from '@commerce-atoms/variants/findVariant';
const result = findVariant(product, [
{name: 'Color', value: 'Red'},
{name: 'Size', value: 'Large'},
]);
if (result.found) {
console.log('Variant:', result.variant.id);
} else {
console.log('Not found:', result.reason); // 'NO_MATCH' | 'INCOMPLETE' | 'INVALID_OPTION'
}// Pick a default variant with policy
import {pickDefaultVariant} from '@commerce-atoms/variants/pickDefaultVariant';
const variant = pickDefaultVariant(product, 'first-available');
// Policies: 'first-available' | 'cheapest-available' | 'first' | 'cheapest'// Get availability map for partial selection
import {getAvailabilityMap} from '@commerce-atoms/variants/getAvailabilityMap';
const availabilityMap = getAvailabilityMap(product, [
{name: 'Color', value: 'Red'},
]);
// Check if "Size: Large" is available given Color: Red
const availableSizes = availabilityMap.get('Size');
const isLargeAvailable = availableSizes?.has('Large');
// Note: Returns Map for efficient lookups. For JSON/debugging:
// Array.from(availabilityMap.entries())URL Helpers
// Parse selected options from URL
import {getSelectedOptionsFromUrl} from '@commerce-atoms/variants/getSelectedOptionsFromUrl';
// Recommended: pass optionKeys for stability (option names are merchant-editable)
const options = getSelectedOptionsFromUrl(
new URLSearchParams('?Color=Red&Size=Large'),
['Color', 'Size'], // Filters to only these keys, ignores other URL params
);
// [{ name: 'Color', value: 'Red' }, { name: 'Size', value: 'Large' }]// Serialize selected options to URL params
import {selectedOptionsToUrlParams} from '@commerce-atoms/variants/selectedOptionsToUrlParams';
const params = selectedOptionsToUrlParams([
{name: 'Color', value: 'Red'},
{name: 'Size', value: 'Large'},
]);
// URLSearchParams: Color=Red&Size=LargeUtilities
// Normalize selected options (trim, case, sort)
import {normalizeSelectedOptions} from '@commerce-atoms/variants/normalizeSelectedOptions';
const normalized = normalizeSelectedOptions(options, {
casing: 'lowercase',
trim: true,
sort: true,
});// Validate a selection
import {isSelectionValid} from '@commerce-atoms/variants/isSelectionValid';
const isValid = isSelectionValid(product, selectedOptions);Type Philosophy
Uses structural types with generics to preserve your original types:
// Shoppy accepts any object matching the shape
// Returns YOUR type, not a Shoppy type
const variant = pickDefaultVariant(hydrogenProduct, 'first-available');
// ^? Returns Hydrogen's variant type, not VariantLikeYour types must structurally match the expected shape; no runtime validation is performed.
Minimal required shape:
interface VariantLike {
id: string;
availableForSale: boolean;
selectedOptions: Array<{name: string; value: string}>;
price: {amount: string; currencyCode: string};
}Status
- 0.x
- ESM only, Node >=18
Compatibility
- Works with Hydrogen, custom backends, anything
- Types match Shopify shapes but don't require them
Delete-ability
Removing this package = replacing imports. That's it.
