OxifyOxify

Recipes

Copy-paste starting points for common custom template jobs — variant swatches, category headings, kit slots, and your own subscribe checkbox.

Each recipe is written for one template — the heading says which. A complete template can be pasted into that template's editor as it is. A snippet goes inside your own markup, wherever that piece should appear. Swap the my-… class names for your own and style them in Custom CSS Settings.

Every prop used here is listed in The five templates.

Product card

Variant buttons instead of a dropdown

Snippet. Each variant's title is the full combination, such as Black / XS.

<div className="my-variants">
  {props.product.variants.map((variant) => (
    <button
      key={variant.id}
      className={"my-variant " + (variant.id === props.selectedVariant ? "is-selected" : "")}
      onClick={() => props.onVariantChange(variant.id)}
    >
      {variant.title}
    </button>
  ))}
</div>

Variant swatches that swap the card image

Complete template. Renders a thumbnail button per variant; clicking one selects the variant, and the main image follows displayImage. getVariantImage returns null for variants without their own photo, so the fallback keeps every button non-empty. Variant photos arrive with live pricing, so on the Design tab's Preview every swatch shows the product image.

<div className="oxify-bb-product-card">
  <img src={props.displayImage} alt={props.product.title} />
 
  <div className="my-swatches">
    {props.product.variants.map((variant) => (
      <button
        key={variant.id}
        className={variant.id === props.selectedVariant ? "my-swatch my-swatch--active" : "my-swatch"}
        onClick={() => props.onVariantChange(variant.id)}
        title={variant.title}
      >
        <img src={props.getVariantImage(variant.id) || props.product.image} alt={variant.title} />
      </button>
    ))}
  </div>
 
  <div>{props.formattedPrice}</div>
 
  <button onClick={props.onAddToKit} disabled={props.isAddButtonDisabled}>
    {props.isOutOfStock ? props.outOfStockText : props.buttonText}
  </button>
</div>

Don't show a price while it loads

Snippet. Market pricing resolves a moment after the page loads. A placeholder avoids flashing the wrong price at shoppers.

<div className="my-price">
  {props.isPriceLoading ? <span>&nbsp;</span> : props.formattedPrice}
</div>

Sale price with the old price struck through

Snippet. formattedComparePrice is null when the variant isn't on sale, so the strike-through only appears when it should.

<div className="my-price">
  {props.formattedComparePrice && <s>{props.formattedComparePrice}</s>}
  <b>{props.formattedPrice}</b>
</div>

A quantity stepper

Snippet. Put it inside your card. Wrap it in props.showQuantitySelector && (…) if you want it to follow the merchant's Enable quantity selector setting.

<div className="my-stepper">
  <button type="button" onClick={props.onRemoveFromKit} disabled={props.quantity <= 0}>−</button>
  <span>{props.quantity}</span>
  <button type="button" onClick={props.onAddToKit} disabled={props.isAddButtonDisabled}>+</button>
</div>

A heading above each category

Show every category on one page, each under its own heading, instead of making shoppers click through the tabs. The heading has to be a sibling of the card, not inside it: the grid lays out each element you return as its own cell, so returning a fragment is what lets oxify-bb-category-heading — which spans every column — sit on its own row above the run. Test categoryName as well as isFirstInCategory, so a category left unnamed doesn't leave an empty heading row.

Complete template — replace the comment with your own card markup:

<>
  {props.isFirstInCategory && props.product.categoryName && (
    <div className="oxify-bb-category-heading">{props.product.categoryName}</div>
  )}
 
  <div className="oxify-bb-product-card">
    {/* …your existing card markup… */}
  </div>
</>

Set the builder up to match: stay on the All tab (leave Hide "All Products" Tab off), switch the Tab Button Template row off or hide the tab bar with Custom CSS (.oxify-bb-tabs { display: none; }), and turn the Category Title switch off too, since it would only say All Products.

Category tabs

Tabs that show the All tab's picture

Complete template. The All tab's own image is always null; if the merchant uploaded an All Products image under Category Images, it's on props.categoryImages.all.

<div className="oxify-bb-tabs" data-alignment={props.alignment}>
  {props.tabs.map((tab) => {
    const image = tab.isAllTab ? props.categoryImages.all : tab.image;
    return (
      <button
        key={tab.id}
        className={"oxify-bb-tab " + (tab.isActive ? "active" : "")}
        onClick={() => props.onCategoryChange(tab.id)}
      >
        {image && <img src={image} alt={tab.name} />}
        <span>{tab.name}</span>
      </button>
    );
  })}
</div>

An "only N to go" hint

Snippet.

{props.remainingSlots > 0 && (
  <div className="my-hint">Add {props.remainingSlots} more to complete your kit</div>
)}

Empty slots as placeholders

Snippet. One filled slot per item, then one empty slot for each place still to fill.

<div className="my-slots">
  {props.kitItems.map((item, index) => (
    <div key={index} className="my-slot is-filled">
      <img src={item.image} alt={item.title} />
      <button onClick={() => props.onRemoveItem(index)}>×</button>
    </div>
  ))}
  {Array.from({ length: props.remainingSlots }).map((_, i) => (
    <div key={"empty" + i} className="my-slot is-empty">+</div>
  ))}
</div>

A savings line

Snippet. Guard it: originalPrice is null when there's no discount, and without the guard you'd print a negative saving.

{props.originalPrice && (
  <div>You save {props.formatPrice(props.originalPrice - props.finalPrice)}</div>
)}

Your own Subscribe & Save checkbox

Complete template. Replaces the built-in card with your own control while keeping the cart wiring. The thing that makes it work: show formattedSubscriptionPrice while isSubscribed, because formattedFinalPrice is always the one-time price and would under-report what the shopper is about to pay. Keep the whole control behind subscriptionEnabled — it's false whenever the plan can't actually be applied, and rendering the checkbox anyway would let shoppers build a bundle that fails at Add to Cart.

If you don't need a custom control, {props.subscriptionBlock} is the ready-made checkbox card: drop it anywhere in your markup.

<div className="oxify-bb-kit-sidebar">
  {/* …your kit items… */}
 
  <div className="oxify-bb-kit-price">
    {props.isSubscribed ? props.formattedSubscriptionPrice : props.formattedFinalPrice}
  </div>
 
  {props.subscriptionEnabled && (
    <label className="my-subscribe">
      <input
        type="checkbox"
        checked={props.isSubscribed}
        onChange={(e) => props.onSubscriptionToggle(e.target.checked)}
      />
      <span>{props.subscribeTitle}</span>
      {props.subscriptionSavings > 0 && (
        <span className="my-subscribe-badge">
          {"Save an extra " + props.formatPrice(props.subscriptionSavings)}
        </span>
      )}
    </label>
  )}
 
  <button onClick={props.onAddToCart} disabled={props.isAddToCartDisabled}>
    {props.buttonText}
  </button>
</div>

Toast

The toast's message isn't always good news — it can be a selection rule message too — so keep its markup neutral. The starting template is all most builders need:

<div className="oxify-bb-toast">
  <div className="oxify-bb-toast-content">{props.message}</div>
</div>

Layouts

Moving the sidebar, wrapping cards, and one-section-per-category pages are on the Full layout page.