variant-selection
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseVariant 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:
| Product | Attributes | Variant ID |
|---|---|---|
| T-Shirt | Black + Medium | |
| T-Shirt | Black + Large | |
| T-Shirt | White + Medium | |
The mutation requires a specific . Without selecting ALL attributes, there's no variant to add.
checkoutLinesAddvariantId加入购物车的是变体,而非产品。 每个变体对应一组特定的属性组合:
| 产品 | 属性 | 变体ID |
|---|---|---|
| T恤 | 黑色 + 中码 | |
| T恤 | 黑色 + 大码 | |
| T恤 | 白色 + 中码 | |
checkoutLinesAddvariantIdTwo Types of Variant Attributes
两种变体属性类型
Saleor distinguishes between two types of variant attributes:
| Type | | Purpose | UI | Passed to Cart? |
|---|---|---|---|---|
| Selection | | Identify which variant (color, size) | Interactive picker | No - just the |
| Non-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 . All attributes are already stored on the variant in Saleor.
variantIdgraphql
undefinedSaleor 将变体属性分为两类:
| 类型 | | 用途 | UI表现 | 是否传入购物车? |
|---|---|---|---|---|
| 选择类 | | 识别变体(如颜色、尺寸) | 交互式选择器 | 否 - 仅传入 |
| 非选择类 | | 描述变体(如材质、品牌) | 仅展示徽章 | 否 - 已存储在Saleor的变体中 |
关键要点: 两类属性都无需传入结账流程。只需传入 即可,所有属性已预先存储在Saleor的变体中。
variantIdgraphql
undefinedGraphQL 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.tsutils.ts
中的核心函数
utils.ts| Function | Purpose |
|---|---|
| Extract unique attribute values from variants |
| Find variant matching ALL selected attributes |
| Get options with availability/compatibility info |
| Clear conflicting selections when needed |
| Detect dead-end selections |
For detailed function signatures and usage, see UTILS_REFERENCE.md.
| 函数 | 用途 |
|---|---|
| 从变体中提取唯一属性值 |
| 查找与所有选中属性匹配的变体 |
| 获取包含可用性/兼容性信息的选项 |
| 必要时清除冲突的选择项 |
| 检测无结果的选择状态 |
如需详细的函数签名和使用方法,请查看 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 | ✗ |
| 状态 | 含义 | 视觉表现 | 是否可点击? |
|---|---|---|---|
| 可用 | 有库存 | 正常样式 | ✓ |
| 不兼容 | 不存在包含当前选择项+此选项的变体 | 灰色暗淡 | ✓(会清除其他选择项) |
| 缺货 | 变体存在但库存数量为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 param is only set when ALL attributes are selected.
variant选择项会存储在URL参数中:
?color=black&size=m&variant=abc123
↑ ↑ ↑
颜色选择 尺寸选择 匹配的变体(自动设置)只有当所有属性都被选中时,才会设置 参数。
variantDiscount 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 is set.
discountPercent选项可以显示折扣百分比:
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
// ...
}当设置了 时,渲染器会显示一个小型徽章。
discountPercentExamples
示例
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:
| 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.
选择系统包含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 in boolean checks for prices - Use
❌ Don't make non-selection attributes interactive - They're display-only (badges, not toggles)
❌ Don't block incompatible options - Let users click, clear others
❌ Don't assume single attribute - Products can have multiple
❌ Don't use
0typeof === "number"❌ Don't make non-selection attributes interactive - They're display-only (badges, not toggles)
❌ 不要在未完成全选时启用「加入购物车」按钮——需要变体ID
❌ 不要阻止不兼容的选项——允许用户点击,自动清除其他选择项
❌ 不要假设产品只有单个属性——产品可包含多个属性
❌ 不要在价格的布尔检查中使用——请使用
❌ 不要让非选择类属性可交互——它们仅用于展示(徽章,而非开关)
❌ 不要阻止不兼容的选项——允许用户点击,自动清除其他选择项
❌ 不要假设产品只有单个属性——产品可包含多个属性
❌ 不要在价格的布尔检查中使用
0typeof === "number"❌ 不要让非选择类属性可交互——它们仅用于展示(徽章,而非开关)