[{"data":1,"prerenderedAt":254},["ShallowReactive",2],{"project-en-product-options":3,"recommendation-mention-sources":192},{"id":4,"title":5,"body":6,"category":162,"context":163,"cover":164,"coverAlt":165,"demo":166,"description":167,"extension":168,"featured":169,"locale":170,"meta":171,"navigation":172,"order":173,"path":174,"published":172,"repository":166,"results":175,"role":176,"seo":177,"slug":178,"stack":179,"stem":189,"year":190,"__hash__":191},"projects\u002Fen\u002Fprojects\u002Fproduct-options.md","Product options and add-ons in storefront carts",{"type":7,"value":8,"toc":147},"minimark",[9,14,18,21,25,33,37,42,51,55,58,62,84,95,99,102,111,115,118,122,144],[10,11,13],"h2",{"id":12},"the-problem","The problem",[15,16,17],"p",{},"Merchants wanted to sell extras and services with a product: gift packaging, a greeting card, a ribbon. Some choices are required, some optional, and each group has its own limits. Shoppers need to see the final price before they add the item to the cart.",[15,19,20],{},"The hard part was the cart. The shared storefront library, CommonJS, treated a cart line as a product variant. With add-ons that stopped being true: a Classic bouquet in standard wrap and the same bouquet in a gift box with a card are two different purchases with different prices. Every place that found a cart item by variant ID could now hit the wrong line: quantity changes, deletion, counters on the product page and availability checks.",[10,22,24],{"id":23},"my-role","My role",[15,26,27,28,32],{},"I built the storefront side in CommonJS: the ",[29,30,31],"code",{},"ui-accessories"," component, pricing, cart operations, how options appear in the cart, and documentation for store developers. Backend developers provided the cart endpoint that accepts option values. My code went through review and QA.",[10,34,36],{"id":35},"what-i-built","What I built",[38,39,41],"h3",{"id":40},"one-component-for-required-and-optional-groups","One component for required and optional groups",[15,43,44,46,47,50],{},[29,45,31],{}," attaches through ",[29,48,49],{},"data-accessories=\"{{ product.id }}\""," and renders option groups from markup templates, so it works in standard widgets and in custom designs. Each group carries its minimum and maximum count. In the demo store, packaging is a required single choice and extras allow up to two. I chose to render options from data attributes instead of tying them to the product page: AJAX-loaded products needed the same rendering, and adding a product with required options from a category preview needs the option data too.",[38,52,54],{"id":53},"price-follows-the-variant-and-the-add-ons","Price follows the variant and the add-ons",[15,56,57],{},"The displayed price recalculates when the shopper changes either the variant or the options. In the demo store, a gift box (+$5) and a greeting card (+$3) take the Classic bouquet from $40 to $48, and the same extras take Grand from $60 to $68. The price checks couldn't treat zero as \"no value\": a variant can legitimately cost nothing, and standard wrap adds $0.",[38,59,61],{"id":60},"a-cart-line-is-a-variant-plus-its-options","A cart line is a variant plus its options",[15,63,64,65,68,69,68,72,75,76,79,80,83],{},"I extended ",[29,66,67],{},"Cart.add",", ",[29,70,71],{},"Cart.set",[29,73,74],{},"Cart.delete"," and the cart's task queue to carry ",[29,77,78],{},"accessory_value_ids"," together with the variant ID, grouped under ",[29,81,82],{},"accessoriable_variant_ids",". A quantity change or deletion now targets one exact combination, not every line with that variant. The cart widget shows the selected option names and their prices on each line.",[15,85,86,91],{},[87,88],"img",{"alt":89,"src":90},"Cart with the same bouquet variant as two lines: standard wrap, and gift box with greeting card","\u002Fimages\u002Fwork\u002Fproduct-options\u002Fcart-same-variant-two-lines.webp",[92,93,94],"em",{},"Demo store: one Classic variant, two cart lines with their own options, quantities and prices.",[38,96,98],{"id":97},"counters-and-availability-across-combinations","Counters and availability across combinations",[15,100,101],{},"Two questions that used to have one answer now needed different ones. The product page counter and the \"Add to cart\" button follow the selected combination. Switching from standard wrap to the gift configuration changes the counter, and deleting the standard line brings back \"Add to cart\" while the gift line stays in the cart. Availability belongs to the variant: lines with different options are still units of the same product, so the check adds up their quantities. I went through the places that looked up cart items by variant ID and made each one ask the right question. The notification widget also got its own error for a wrong number of selected options.",[15,103,104,108],{},[87,105],{"alt":106,"src":107},"Cart with 7 standard and 9 gift units of one variant and a maximum quantity warning","\u002Fimages\u002Fwork\u002Fproduct-options\u002Fcart-combined-quantity-limit.webp",[92,109,110],{},"Demo store: 7 + 9 units of the same variant in two lines; adding more shows the limit warning.",[38,112,114],{"id":113},"compatibility-and-documentation","Compatibility and documentation",[15,116,117],{},"Options had to work in the product widget, AJAX products, category previews, modals, multi-warehouse carts and older templates. I wrote the test checklist for adding, changing and deleting regular products, multi-warehouse products and products with options in each of those places, plus price changes when switching variants and options. I also documented the options API for store developers who build product cards and carts in their own templates.",[10,119,121],{"id":120},"results","Results",[123,124,125,129,132,135],"ul",{},[126,127,128],"li",{},"Shoppers can choose required and optional add-ons, see the final price and add that exact combination to the cart.",[126,130,131],{},"The same variant can sit in the cart as separate lines; quantity changes and deletion affect only the chosen line.",[126,133,134],{},"Store developers got a component and a documented cart API for options in their own templates.",[126,136,137,138,143],{},"Options work in product widgets and AJAX-loaded products. The library this was built into is described in ",[139,140,142],"a",{"href":141},"\u002Fwork\u002Fcommonjs-storefront-library\u002F","the CommonJS case",".",[15,145,146],{},"The screenshots come from an inSales demo store with licensed stock photos. Products and prices are made up for the demo; they are not sales data.",{"title":148,"searchDepth":149,"depth":149,"links":150},"",2,[151,152,153,161],{"id":12,"depth":149,"text":13},{"id":23,"depth":149,"text":24},{"id":35,"depth":149,"text":36,"children":154},[155,157,158,159,160],{"id":40,"depth":156,"text":41},3,{"id":53,"depth":156,"text":54},{"id":60,"depth":156,"text":61},{"id":97,"depth":156,"text":98},{"id":113,"depth":156,"text":114},{"id":120,"depth":149,"text":121},"Storefront platform","inSales","\u002Fimages\u002Fwork\u002Fproduct-options\u002Fproduct-page-gift-box-and-card.webp","Demo-store product page for a bouquet with Classic size, gift box and greeting card selected and the price updated to 48 dollars",null,"I built product options for the inSales storefront library, from the selection component and pricing to cart operations, so shoppers can buy the same variant with different extras as separate cart lines.","md",false,"en",{},true,10,"\u002Fen\u002Fprojects\u002Fproduct-options",[],"Frontend developer: ui-accessories component, pricing, cart API, developer docs",{"title":5,"description":167},"product-options",[180,181,182,183,184,185,186,187,188],"JavaScript","API Development","AJAX","Front-End Development","E-Commerce","Technical Documentation","HTML5","CSS","Debugging","en\u002Fprojects\u002Fproduct-options","Jan 2025 – Apr 2025","vsrFmRdJlhWYX3TyX_Gq1OR6nFH0z4Qui4pnhmxm1lg",[193,204,209,224,234,239,244,249],{"stem":194,"name":195,"date":196,"featured":149,"published":172,"mentions":197},"recommendations\u002Fandrey-stikheev","Andrey Stikheev","2026-09-08",[198,201],{"project":199,"quote":200},"product-image-editor","Among his most notable projects was a sophisticated browser-based image editor built with TypeScript and Fabric.js, now used by thousands of merchants.",{"project":202,"quote":203},"abandoned-carts","He also developed the frontend for a paid abandoned-cart product and contributed to the production migration from Vue 2 to Vue 3.",{"stem":205,"name":206,"date":207,"featured":166,"published":172,"mentions":208},"recommendations\u002Fdaniil-churakov","Daniil Churakov","2026-08-27",[],{"stem":210,"name":211,"date":212,"featured":156,"published":172,"mentions":213},"recommendations\u002Fdmitry-sadovsky","Dmitry Sadovsky","2026-08-22",[214,217,219,222],{"project":215,"quote":216},"visual-storefront","For years he was the driving force behind our visual storefront editor — a large, complex application where merchants build their online stores.",{"project":199,"quote":218},"It became the tool merchants use to design product images right inside the admin panel — the kind of component companies usually buy rather than build.",{"project":220,"quote":221},"image-editor","What impressed me most was the infographic editor he built essentially from scratch: a full canvas-based graphic editor with artboards, shapes, text styling, templates, and undo\u002Fredo history, powered by an editor core he developed himself.",{"project":202,"quote":223},"Our abandoned carts functionality — with multi-channel customer messaging, communication history, and analytics — was largely his work, from UI down to the details of how requests are cancelled and states stay in sync.",{"stem":225,"name":226,"date":227,"featured":228,"published":172,"mentions":229},"recommendations\u002Fegor-fedkin","Egor Fedkin","2026-08-31",1,[230,232],{"project":199,"quote":231},"Today, the tool is actively used by around 3,000 users.",{"project":202,"quote":233},"It has already generated approximately twice the revenue needed to cover its development costs.",{"stem":235,"name":236,"date":237,"featured":166,"published":172,"mentions":238},"recommendations\u002Femmanuel-stephen","Emmanuel Stephen","2026-10-11",[],{"stem":240,"name":241,"date":242,"featured":166,"published":172,"mentions":243},"recommendations\u002Fevgeny-lonshakov","Евгений Лоншаков","2026-09-21",[],{"stem":245,"name":246,"date":247,"featured":166,"published":172,"mentions":248},"recommendations\u002Froman-gorbachev","Роман Горбачев","2026-09-17",[],{"stem":250,"name":251,"date":252,"featured":166,"published":172,"mentions":253},"recommendations\u002Fvladimir-bedarev","Bedarev Vladimir","2026-09-16",[],1793195987992]