variant-selection

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Variant Selection System

变体选择系统

Source: Saleor Docs - Attributes - How product/variant attributes work
来源Saleor Docs - Attributes - 产品/变体属性的工作原理

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:
ProductAttributesVariant ID
T-ShirtBlack + Medium
abc123
T-ShirtBlack + Large
def456
T-ShirtWhite + Medium
ghi789
The
checkoutLinesAdd
mutation requires a specific
variantId
. Without selecting ALL attributes, there's no variant to add.
加入购物车的是变体,而非产品。 每个变体对应一组特定的属性组合:
产品属性变体ID
T恤黑色 + 中码
abc123
T恤黑色 + 大码
def456
T恤白色 + 中码
ghi789
checkoutLinesAdd
变更需要特定的
variantId
。若未选中所有属性,则没有可加入的变体。

Two Types of Variant Attributes

两种变体属性类型

Saleor distinguishes between two types of variant attributes:
Type
variantSelection
PurposeUIPassed to Cart?
Selection
VARIANT_SELECTION
Identify which variant (color, size)Interactive pickerNo - just the
variantId
Non-Selection
NOT_VARIANT_SELECTION
Describe the variant (material, brand)Display-only badgesNo - 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
undefined
Saleor 将变体属性分为两类:
类型
variantSelection
用途UI表现是否传入购物车?
选择类
VARIANT_SELECTION
识别变体(如颜色、尺寸)交互式选择器否 - 仅传入
variantId
非选择类
NOT_VARIANT_SELECTION
描述变体(如材质、品牌)仅展示徽章否 - 已存储在Saleor的变体中
关键要点: 两类属性都无需传入结账流程。只需传入
variantId
即可,所有属性已预先存储在Saleor的变体中。
graphql
undefined

GraphQL queries use the variantSelection filter:

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.
selectionAttributes: attributes(variantSelection: VARIANT_SELECTION) { ... } nonSelectionAttributes: attributes(variantSelection: NOT_VARIANT_SELECTION) { ... }

非选择类属性仅用于**展示**——以信息徽章形式呈现,而非交互式选择器。

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)
src/ui/components/pdp/variant-selection/
├── index.ts                      # 公共导出
├── types.ts                      # TypeScript 接口
├── utils.ts                      # 数据转换与逻辑
├── variant-selector.tsx          # 单个属性选择器
├── variant-selection-section.tsx # 主容器
├── optional-attributes.tsx       # 非选择类属性徽章
└── renderers/
    ├── color-swatch-option.tsx   # 颜色色板选项(圆形)
    └── button-option.tsx         # 尺寸/文本按钮选项(统一样式)

Key Functions in
utils.ts

utils.ts
中的核心函数

FunctionPurpose
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.
函数用途
groupVariantsByAttributes()
从变体中提取唯一属性值
findMatchingVariant()
查找与所有选中属性匹配的变体
getOptionsForAttribute()
获取包含可用性/兼容性信息的选项
getAdjustedSelections()
必要时清除冲突的选择项
getUnavailableAttributeInfo()
检测无结果的选择状态
如需详细的函数签名和使用方法,请查看 UTILS_REFERENCE.md

Option States

选项状态

StateMeaningVisualClickable?
AvailableIn stockNormal
IncompatibleNo variant with this + current selectionsDimmed✓ (clears others)
Out of stockVariant exists but quantity = 0Strikethrough
状态含义视觉表现是否可点击?
可用有库存正常样式
不兼容不存在包含当前选择项+此选项的变体灰色暗淡✓(会清除其他选择项)
缺货变体存在但库存数量为0删除线

URL Parameter Pattern

URL 参数模式

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.
选择项会存储在URL参数中:
?color=black&size=m&variant=abc123
  ↑           ↑       ↑
颜色选择  尺寸选择  匹配的变体(自动设置)
只有当所有属性都被选中时,才会设置
variant
参数。

Discount Badges

折扣徽章

Options can show discount percentages:
typescript
// 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.
选项可以显示折扣百分比:
typescript
// 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
	// ...
}
当设置了
discountPercent
时,渲染器会显示一个小型徽章。

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.
当用户选择不兼容的选项时:
初始状态:?color=red(红色仅存在于S码)
用户点击:L码
结果:?size=l(红色被清除,而非被阻止)
用户永远不会陷入「卡住」的状态——他们总能探索所有选项。

Dead End Detection

无结果检测

typescript
const deadEnd = getUnavailableAttributeInfo(variants, groups, selections);
// Returns: { slug: "size", name: "Size", blockedBy: "Red" }
// UI shows: "No size available in Red"
typescript
const deadEnd = getUnavailableAttributeInfo(variants, groups, selections);
// 返回:{ slug: "size", name: "Size", blockedBy: "Red" }
// UI 显示:「红色无可用尺寸」

Custom Renderers

自定义渲染器

tsx
<VariantSelectionSection
	variants={variants}
	renderers={{
		color: MyCustomColorPicker,
		size: MySizeChart,
	}}
/>
tsx
<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:
StateAdd to CartDescription
EmptyNo selections
PartialSome attributes selected
CompleteAll selected, variant found
ConflictAuto-clears to Partial
DeadEndSelection blocks other groups
Key behavior: When user selects an incompatible option, other selections are cleared automatically (not blocked). Users can always explore all options.
选择系统包含5种状态,并具备自动冲突解决机制。完整的状态图和转换规则,请查看 STATE_MACHINE.md
快速参考:
状态可加入购物车?描述
空选择未选择任何属性
部分选择已选择部分属性
完全选择已选择所有属性,找到匹配变体
冲突自动清除为部分选择状态
无结果当前选择阻止其他属性组的选择
关键行为: 当用户选择不兼容的选项时,其他选择项会被自动清除(而非被阻止)。用户总能探索所有选项。

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)
不要在未完成全选时启用「加入购物车」按钮——需要变体ID
不要阻止不兼容的选项——允许用户点击,自动清除其他选择项
不要假设产品只有单个属性——产品可包含多个属性
不要在价格的布尔检查中使用
0
——请使用
typeof === "number"

不要让非选择类属性可交互——它们仅用于展示(徽章,而非开关)