This document describes the robust product variation system that supports independent and dependent variations with proper ordering and validation.
Source layout: Implementation code lives under packages/bodegacat/src/ (e.g. lib/variationEngine.ts, components). Snippets below use relative imports like ../lib/... as they would from a file inside that package.
The new variation system allows you to define complex product configurations that handle real-world scenarios like:
- Shirts: Size (independent) → Color (dependent on size)
- Art Prints: Material (independent) → Size (dependent on material)
- Electronics: Model (independent) → Storage (dependent on model) → Color (dependent on model + storage)
- Always shown to customers
- Not affected by other selections
- Examples: Size, Material, Model
- Only shown when their dependencies are satisfied
- Options may change based on parent selections
- Examples: Color (depends on Size), Storage (depends on Model)
The order of variations matters for dependent relationships:
- Size → Color: Picking a size shows different color options
- Color → Size: Picking a color shows different size options
Each option can define which parent selections it's available for:
{
id: "red",
name: "red",
displayName: "Red",
availableFor: [
{ variationId: "size", optionIds: ["s", "m", "l", "xl"] }
]
}interface ProductVariationDefinition {
id: string; // Unique identifier
name: string; // Internal name (e.g., "size")
displayName: string; // User-facing name (e.g., "Size")
type: "independent" | "dependent";
order: number; // Display/processing order
required: boolean; // Must be selected
dependsOn?: string[]; // Array of variation IDs this depends on
options: ProductVariationOptionDefinition[];
}interface ProductVariationOptionDefinition {
id: string; // Unique identifier
name: string; // Internal name (e.g., "xl")
displayName: string; // User-facing name (e.g., "XL")
priceModifier: number; // Price adjustment in cents
available: boolean; // Whether this option is available
images?: string[]; // Option-specific images
availableFor?: Array<{ // For dependent options
variationId: string;
optionIds: string[];
}>;
}const shirtVariations: ProductVariationDefinition[] = [
{
id: "size",
name: "size",
displayName: "Size",
type: "independent",
order: 1,
required: true,
options: [
{ id: "xs", name: "xs", displayName: "XS", priceModifier: 0, available: true },
{ id: "s", name: "s", displayName: "S", priceModifier: 0, available: true },
{ id: "m", name: "m", displayName: "M", priceModifier: 0, available: true },
{ id: "l", name: "l", displayName: "L", priceModifier: 0, available: true },
{ id: "xl", name: "xl", displayName: "XL", priceModifier: 500, available: true },
{ id: "xxl", name: "xxl", displayName: "XXL", priceModifier: 1000, available: true },
]
},
{
id: "color",
name: "color",
displayName: "Color",
type: "dependent",
order: 2,
required: true,
dependsOn: ["size"],
options: [
{
id: "red",
name: "red",
displayName: "Red",
priceModifier: 0,
available: true,
availableFor: [{ variationId: "size", optionIds: ["s", "m", "l", "xl"] }]
},
{
id: "blue",
name: "blue",
displayName: "Blue",
priceModifier: 0,
available: true,
availableFor: [{ variationId: "size", optionIds: ["m", "l", "xl", "xxl"] }]
},
{
id: "green",
name: "green",
displayName: "Green",
priceModifier: 200,
available: true,
availableFor: [{ variationId: "size", optionIds: ["l", "xl"] }]
}
]
}
];const artVariations: ProductVariationDefinition[] = [
{
id: "material",
name: "material",
displayName: "Material",
type: "independent",
order: 1,
required: true,
options: [
{ id: "canvas", name: "canvas", displayName: "Canvas", priceModifier: 0, available: true },
{ id: "paper", name: "paper", displayName: "Paper", priceModifier: -500, available: true },
{ id: "metal", name: "metal", displayName: "Metal", priceModifier: 1000, available: true },
]
},
{
id: "size",
name: "size",
displayName: "Size",
type: "dependent",
order: 2,
required: true,
dependsOn: ["material"],
options: [
{
id: "small",
name: "small",
displayName: "Small (8x10)",
priceModifier: 0,
available: true,
availableFor: [{ variationId: "material", optionIds: ["canvas", "paper"] }]
},
{
id: "medium",
name: "medium",
displayName: "Medium (16x20)",
priceModifier: 1500,
available: true,
availableFor: [{ variationId: "material", optionIds: ["canvas", "paper", "metal"] }]
},
{
id: "large",
name: "large",
displayName: "Large (24x36)",
priceModifier: 3000,
available: true,
availableFor: [{ variationId: "material", optionIds: ["canvas", "metal"] }]
}
]
}
];import { createVariationState } from "../lib/variationEngine";
const variationState = createVariationState(
product.variationDefinitions,
currentSelections
);import { updateSelection } from "../lib/variationEngine";
const newSelections = updateSelection(
product.variationDefinitions,
currentSelections,
variationId,
optionId
);import { getAvailableOptions } from "../lib/variationEngine";
const availableOptions = getAvailableOptions(
variation,
allVariations,
currentSelections
);import { getCurrentVariationImage } from "../lib/variationEngine";
const currentImage = getCurrentVariationImage(
product.variationDefinitions,
selections,
product.images
);The VariationManager component provides a full interface for:
- Adding/removing variations
- Setting variation types (independent/dependent)
- Defining dependencies
- Managing options and their availability
- Setting price modifiers
- Reordering variations
- Loading product-type templates
- Template Loading: Pre-configured variations for common product types
- Drag & Drop Ordering: Reorder variations with up/down buttons
- Dependency Management: Visual interface for setting up dependent relationships
- Option Availability: Configure which options are available for which parent selections
- Price Modifiers: Set price adjustments for each option
- Validation: Real-time validation of required selections
- Use lowercase, underscore-separated names for IDs:
"product_size","shirt_color" - Use proper case for display names:
"Product Size","Shirt Color"
- Put independent variations first
- Order dependent variations based on logical flow
- Consider user experience when setting order
- Keep dependency chains simple (avoid deep nesting)
- Ensure all dependent options have proper
availableForconfigurations - Test all possible selection combinations
- Use positive values for upgrades
- Use negative values for downgrades
- Keep modifiers reasonable relative to base price
- Always provide at least one available option for each variation
- Consider inventory constraints when setting availability
- Test edge cases where no options might be available
The old variation system used a simpler structure:
// Old system
variations: ProductVariation[] // Simple array of variations
// New system
variationDefinitions: ProductVariationDefinition[] // Robust definitionsTo migrate existing products:
- Convert existing variations to the new structure
- Set appropriate types (independent/dependent)
- Define dependencies and availability rules
- Update price calculations to use modifiers instead of absolute prices
Test your variation configurations with:
- All possible selection combinations
- Edge cases (no available options)
- Price calculations
- Image display logic
- Validation messages
The system includes comprehensive validation and will show appropriate error messages for missing required selections.