Product Card Quick Options
Design Log 02: Product Card Quick Options
Written for AI agents. See Log Methodology Note below for details.
Background
The store product page has "Add to Cart" buttons on product cards. However, for products with options (Size, Color) and modifiers, those must be selected before adding to cart.
The goal is to extend the product card with:
- Options/variants data (from Wix Stores API)
- A hover UI showing option buttons overlaying the product image
- Replace simple "Add to Cart" with option selection when needed
Reference UI: Size buttons (XXS, XS, S, M, L, XL, XXL) appearing on hover over the product card image, with out-of-stock sizes greyed out.
Problem
- Contract gap:
product-card.jay-contractlacks options/modifiers/variants data - Duplicate contracts: Options/modifiers are defined separately in
product-page.jay-contract - Data gap:
stores-actions.tsdoesn't fetch options/variants for product cards - UI gap:
page.jay-htmlhas no hover controls for option selection
Questions and Answers
Q1: Should we consolidate options/modifiers contracts from product-card and product-page into a shared contract?
A: Yes. Create a shared product-options.jay-contract that both can reference via link:.
Q2: Should quick-add support ALL options or just the primary one (e.g., Size)? A: Only ONE option. Clicking a choice directly adds to cart (no two-step selection).
Q3: What about products with only modifiers (no options)? A: Send to product page for full configuration.
Q4: How should we handle multi-option products (Size + Color)? A: Send to product page for full configuration.
Q5: Should we display option buttons on hover or always visible? A: On hover. The "Select {option}" hint is replaced by option buttons when hovering over the card.
UX Summary
| Product Type | Behavior |
|---|---|
| No options | Regular "Add to Cart" button |
| Single option | Option choices on hover, click = add to cart |
| Multiple options or modifiers | "View Options" button → product page |
Proposed Design
1. Shared Contract: product-options.jay-contract
name: product-options
tags:
# Single option (Size, Color, etc.)
- tag: _id
type: data
dataType: string
description: Option GUID
- tag: name
type: data
dataType: string
description: 'Option name (e.g., "Size", "Color")'
- tag: optionRenderType
type: variant
dataType: enum (TEXT_CHOICES | COLOR_SWATCH_CHOICES)
description: How the option should be rendered
- tag: choices
type: sub-contract
repeated: true
trackBy: choiceId
description: Available choices for this option
tags:
- { tag: choiceId, type: data, dataType: string }
- { tag: name, type: data, dataType: string, description: 'Choice display name (e.g., "XL")' }
- { tag: choiceType, type: variant, dataType: 'enum (CHOICE_TEXT | ONE_COLOR)' }
- { tag: colorCode, type: data, dataType: string, description: 'HEX color (for swatches)' }
- { tag: inStock, type: variant, dataType: boolean, phase: fast+interactive }
- { tag: isSelected, type: variant, dataType: boolean, phase: fast+interactive }
- { tag: choiceButton, type: interactive, elementType: HTMLButtonElement }
2. Extend product-card.jay-contract
Add these tags:
# Quick-add behavior variants
- tag: quickAddType
type: variant
dataType: enum (SIMPLE | SINGLE_OPTION | NEEDS_CONFIGURATION)
description: |
SIMPLE = no options, show Add to Cart
SINGLE_OPTION = one option, show choices on hover (click = add)
NEEDS_CONFIGURATION = multiple options or modifiers, link to product page
- tag: quickOption
type: sub-contract
description: Primary option for quick selection (only when quickAddType = SINGLE_OPTION)
link: ./product-options
- tag: viewOptionsButton
type: interactive
elementType: HTMLButtonElement
description: Button to navigate to product page (when quickAddType = NEEDS_CONFIGURATION)
3. Update stores-actions.ts and product-mapper.ts
// In queryProducts, add fields:
fields: ['CURRENCY', 'VARIANT_OPTION_CHOICE_NAMES']
// New enum for quick add behavior
enum QuickAddType {
SIMPLE = 'SIMPLE', // No options
SINGLE_OPTION = 'SINGLE_OPTION', // One option, quick add on hover
NEEDS_CONFIGURATION = 'NEEDS_CONFIGURATION' // Multi-option or modifiers
}
// In mapProductToCard, add:
function getQuickAddType(product): QuickAddType {
const optionCount = product.options?.length ?? 0;
const hasModifiers = (product.modifiers?.length ?? 0) > 0;
if (hasModifiers || optionCount > 1) return QuickAddType.NEEDS_CONFIGURATION;
if (optionCount === 1) return QuickAddType.SINGLE_OPTION;
return QuickAddType.SIMPLE;
}
quickAddType: getQuickAddType(product),
quickOption: product.options?.length === 1
? mapQuickOption(product.options[0], product.variantsInfo)
: null,
4. Update page.jay-html
<article class="product-card" forEach="productSearch.searchResults" trackBy="_id">
<div class="product-card-wrapper">
<a href="{productUrl}" class="product-card-image">
<img src="{thumbnail.url}" alt="{thumbnail.altText}" loading="lazy" />
<!-- Badges only - no overlay -->
<span class="badge badge-accent ribbon" if="hasRibbon">{ribbon.name}</span>
</a>
<!-- Product content -->
<div class="product-card-content">
<!-- ... name, price content ... -->
<!-- SIMPLE: Regular add to cart -->
<button
class="btn btn-secondary btn-block"
ref="productSearch.searchResults.addToCartButton"
if="quickAddType === SIMPLE && inventory.availabilityStatus === IN_STOCK"
>
Add to Cart
</button>
<!-- SINGLE_OPTION: Quick options in button area -->
<div class="quick-options-area" if="quickAddType === SINGLE_OPTION">
<!-- Default: "Select {option}" hint -->
<span class="select-option-hint">Select {quickOption.name}</span>
<!-- On hover: Show option buttons (replaces hint) -->
<div class="quick-options-buttons">
<button
class="quick-option-btn {!inStock ? out-of-stock}"
forEach="quickOption.choices"
trackBy="choiceId"
ref="productSearch.searchResults.quickOption.choices.choiceButton"
disabled="{!inStock}"
>
{name}
</button>
</div>
</div>
<!-- NEEDS_CONFIGURATION: Link to product page -->
<a
href="{productUrl}"
class="btn btn-secondary btn-block"
ref="productSearch.searchResults.viewOptionsButton"
if="quickAddType === NEEDS_CONFIGURATION"
>
View Options
</a>
</div>
</div>
</article>
5. CSS for Hover Button Swap
/* Container for hint + buttons */
.quick-options-area {
position: relative;
min-height: 48px;
}
/* "Select Color" hint - visible by default */
.select-option-hint {
display: flex;
align-items: center;
justify-content: center;
padding: 12px;
border: 1px dashed var(--border);
border-radius: var(--radius-md);
transition: opacity 0.15s;
}
/* Hide hint on hover */
.product-card:hover .quick-options-area .select-option-hint {
opacity: 0;
pointer-events: none;
position: absolute;
inset: 0;
}
/* Option buttons - hidden by default */
.quick-options-buttons {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
opacity: 0;
pointer-events: none;
position: absolute;
inset: 0;
transition: opacity 0.15s;
}
/* Show buttons on hover */
.product-card:hover .quick-options-buttons {
opacity: 1;
pointer-events: auto;
position: relative;
}
.quick-option-btn {
min-width: 48px;
padding: 10px 16px;
border: 2px solid var(--border);
background: var(--bg-secondary);
border-radius: var(--radius-md);
font-weight: 600;
cursor: pointer;
}
.quick-option-btn:hover:not(:disabled) {
background: var(--accent);
color: #fff;
border-color: var(--accent);
}
.quick-option-btn.out-of-stock {
opacity: 0.4;
text-decoration: line-through;
}
Implementation Plan
Phase 1: Shared Contract
- Create
product-options.jay-contractwith shared option/choice structure - Update
product-page.jay-contractto uselink: ./product-options - Extend
product-card.jay-contractwithquickAddType,quickOption,viewOptionsButton - Run contract type generation
Phase 2: Action/Mapper Updates
- Update
product-mapper.ts:- Add
QuickAddTypeenum - Add
getQuickAddType()function - Add
mapQuickOption()function (maps option + variants to choices with stock)
- Add
- Update
searchProductsandgetAllProductsto include variant data fields
Phase 3: Component Updates
- Update
product-search.tsinteractive phase:- Handle
quickOption.choices.choiceButton.onclick→ find variant → add to cart - Handle
viewOptionsButton(navigation to product page)
- Handle
Phase 4: HTML/CSS
- Update
page.jay-htmlwith:- Quick options overlay for
SINGLE_OPTION - "View Options" button for
NEEDS_CONFIGURATION - Conditional rendering based on
quickAddType
- Quick options overlay for
- Add hover overlay CSS to
store-theme.css - Test interactions
Trade-offs
| Decision | Pros | Cons |
|---|---|---|
| Single option only | Simple UI, one-click add | Multi-option products redirect to page |
| Click = Add (not select) | Faster UX, fewer clicks | No "preview" before add |
| Shared contract | DRY, consistency | Additional indirection |
| Buttons replace hint on hover | Cleaner than overlay, no image obstruction | Mobile shows only hint |
| "View Options" for complex | Clear expectation | Extra navigation step |
Verification Criteria
- SIMPLE products: Show regular "Add to Cart" button, clicking adds to cart
- SINGLE_OPTION products: Show option choices on hover, clicking a choice adds that variant to cart
- NEEDS_CONFIGURATION products: Show "View Options" button linking to product page
- Out-of-stock choices appear disabled/greyed and cannot be clicked
- Cart correctly receives the variant ID corresponding to the clicked choice
- Cart indicator updates after successful add
Implementation Results
Files Created
wix/packages/wix-stores/lib/contracts/product-options.jay-contract- Shared option/choice contract
Files Modified
wix/packages/wix-stores/lib/contracts/product-card.jay-contract- AddedquickAddType,quickOption,viewOptionsButtonwix/packages/wix-stores/lib/utils/product-mapper.ts- AddedgetQuickAddType(),mapQuickOption()functionswix/packages/wix-stores/lib/actions/stores-actions.ts- AddedVARIANT_OPTION_CHOICE_NAMESto query fieldswix/packages/wix-stores/lib/components/product-search.ts- Added quick option click and view options handlerswix/examples/store/src/pages/products/page.jay-html- Added quick options overlay and conditional buttonswix/examples/store/src/styles/store-theme.css- Added hover overlay styles
Deviations from Design
- Did not update
product-page.jay-contractto use shared contract (kept existing structure to minimize changes) - Mobile fallback shows "Select {option name}" hint text instead of tap-to-reveal
Next Steps
- Run contract type generation to resolve linter errors
- Test with real product data
- Consider adding loading spinner to quick option buttons during add-to-cart
Log Methodology Note
Note: These design logs are written primarily for AI agents as part of the Design Log methodology and made accessible here for human readers. The language and structure are optimized for machine consumption — expect precise, specification-style prose rather than narrative documentation.