Variant Selection System
Source: Saleor Docs - Attributes - How product/variant attributes work
When to Use
Use this skill when:
-
Modifying variant/attribute selection on product pages
-
Understanding why a variant isn't selectable
-
Adding discount indicators to variant options
-
Debugging "Add to Cart" button state
Instructions
Core Concept: Variants, Not Products
You add VARIANTS to cart, not products. Each variant is a specific attribute combination:
Product Attributes Variant ID
T-Shirt Black + Medium abc123
T-Shirt Black + Large def456
T-Shirt White + Medium ghi789
The checkoutLinesAdd mutation requires a specific variantId . Without selecting ALL attributes, there's no variant to add.
Two Types of Variant Attributes
Saleor distinguishes between two types of variant attributes:
Type variantSelection
Purpose UI Passed to Cart?
Selection VARIANT_SELECTION
Identify which variant (color, size) Interactive picker No - just the variantId
Non-Selection NOT_VARIANT_SELECTION
Describe the variant (material, brand) Display-only badges No - already on variant
Key insight: Neither type is "passed" to checkout. You only pass the variantId . All attributes are already stored on the variant in Saleor.
GraphQL queries use the variantSelection filter:
selectionAttributes: attributes(variantSelection: VARIANT_SELECTION) { ... } nonSelectionAttributes: attributes(variantSelection: NOT_VARIANT_SELECTION) { ... }
Non-selection attributes are display-only - shown as informational badges, not interactive selectors.
File Structure
src/ui/components/pdp/variant-selection/ ├── index.ts # Public exports ├── types.ts # TypeScript interfaces ├── utils.ts # Data transformation & logic ├── variant-selector.tsx # Single attribute selector ├── variant-selection-section.tsx # Main container ├── optional-attributes.tsx # Non-selection attribute badges └── renderers/ ├── color-swatch-option.tsx # Color swatch (circular) └── button-option.tsx # Button for size/text (unified)
Key Functions in utils.ts
Function Purpose
groupVariantsByAttributes()
Extract unique attribute values from variants
findMatchingVariant()
Find variant matching ALL selected attributes
getOptionsForAttribute()
Get options with availability/compatibility info
getAdjustedSelections()
Clear conflicting selections when needed
getUnavailableAttributeInfo()
Detect dead-end selections
For detailed function signatures and usage, see UTILS_REFERENCE.md.
Option States
State Meaning Visual Clickable?
Available In stock Normal ✓
Incompatible No variant with this + current selections Dimmed ✓ (clears others)
Out of stock Variant exists but quantity = 0 Strikethrough ✗
URL Parameter Pattern
Selections are stored in URL params:
?color=black&size=m&variant=abc123 ↑ ↑ ↑ Color sel Size sel Matching variant (set automatically)
The variant param is only set when ALL attributes are selected.
Discount Badges
Options can show discount percentages:
// In utils.ts interface VariantOption { id: string; name: string; available: boolean; hasDiscount?: boolean; // Any variant with this option is discounted discountPercent?: number; // Max discount percentage // ... }
The renderers display a small badge when discountPercent is set.
Examples
Smart Selection Adjustment
When user selects an incompatible option:
State: ?color=red (Red only exists in Size S) User clicks: Size L Result: ?size=l (Red is cleared, not blocked)
Users are never "stuck" - they can always explore all options.
Dead End Detection
const deadEnd = getUnavailableAttributeInfo(variants, groups, selections); // Returns: { slug: "size", name: "Size", blockedBy: "Red" } // UI shows: "No size available in Red"
Custom Renderers
<VariantSelectionSection variants={variants} renderers={{ color: MyCustomColorPicker, size: MySizeChart, }} />
State Machine
The selection system has 5 states with automatic conflict resolution. For the full state diagram and transition rules, see STATE_MACHINE.md.
Quick reference:
State Add to Cart Description
Empty ❌ No selections
Partial ❌ Some attributes selected
Complete ✅ All selected, variant found
Conflict — Auto-clears to Partial
DeadEnd ❌ Selection blocks other groups
Key behavior: When user selects an incompatible option, other selections are cleared automatically (not blocked). Users can always explore all options.
Anti-patterns
❌ Don't enable "Add to Cart" without full selection - Needs variant ID
❌ Don't block incompatible options - Let users click, clear others
❌ Don't assume single attribute - Products can have multiple
❌ Don't use 0 in boolean checks for prices - Use typeof === "number"
❌ Don't make non-selection attributes interactive - They're display-only (badges, not toggles)