FoxrFoxr

The five templates

Every prop each template receives, plus a starting recipe for each one.

Each template receives a props object with everything it needs already prepared — plain values, formatted strings, and ready-to-use functions. This page lists the props that matter most for each of the five templates. Values marked honor it exist for a reason — ignoring them lets shoppers add items they shouldn't be able to.

The exact prop list for the template you're editing is also shown in the app, right inside the editor.

Full layout — the whole arrangement

Root class: foxr-bundle-builder-layout

Replaces where every piece sits rather than what any one of them looks like. It's the biggest of the five and has its own guide, covering the props.slots pieces, the wrapper class that decides whether the sidebar is a sticky column or a full-width block, and three ready-to-paste recipes.

Product card — one card per product

Root class: foxr-bb-product-card

Rendered once per product in the active category. Column count (including the mobile Cards Per Row setting) is controlled by the grid, so keep your card fluid — avoid fixed widths.

PropWhat it is
props.productThe product being rendered — title, image, variants, prices, category.
props.selectedVariant / props.onVariantChangeThe chosen variant, and a function to change it.
props.formattedPrice / props.formattedComparePricePrices already formatted in the store's currency — use these instead of formatting yourself.
props.discountPercentageRounded percent off, or 0 when not discounted. Ready for a sale badge.
props.isPriceLoadingTrue while market pricing resolves — show a placeholder rather than a possibly-wrong price.
props.quantityHow many of this variant are already in the kit.
props.onAddToKit / props.onRemoveFromKitAdd or remove one unit.
props.isAddButtonDisabledHonor it. True when the kit is full, the variant is unavailable, or pricing hasn't resolved.
props.isOutOfStockTrue when the selected variant can't be purchased.
props.buttonText / props.outOfStockTextMerchant-set button labels.

Recipe — sale badge only when discounted:

{props.discountPercentage > 0 && (
  <span className="my-badge">Save {props.discountPercentage}%</span>
)}

Category tabs — the switcher above the grid

Root class: foxr-bb-tabs

Rendered once.

PropWhat it is
props.tabsEvery tab to render, in order, including the leading "All" tab when the merchant hasn't hidden it.
props.activeCategoryId / props.onCategoryChangeThe selected tab, and a function to switch it. Pass "all" for all products.
props.categoryImagesCategory id → image URL, from the Category images setting.
props.alignmentThe merchant's tab alignment setting.

Recipe — starting template:

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

Root class: foxr-bb-kit-sidebar

Rendered once, beside the products grid. This template also replaces the Buy Box layout (Sidebar Widget → Template 3) when active.

PropWhat it is
props.kitItemsEverything currently in the kit, one entry per unit.
props.maxItems / props.remainingSlots / props.isCompleteHow big the selected box is, how many slots are left, and whether the kit is ready to check out.
props.onRemoveItem / props.onAddToCartRemove a kit item by index, or check out the whole bundle.
props.isAddToCartDisabledHonor it. True while the kit is incomplete or a cart request is in flight.
props.formattedOriginalPrice / props.formattedFinalPricePrices already formatted, ready to strike through the original.
props.boxes / props.selectedBoxId / props.onBoxChangeEvery configured box size, which one's selected, and a function to switch.
props.showBoxSelectorTrue when the merchant enabled quantity-as-option.
props.getBoxBadgeText(box)Renders the merchant's savings badge for a box, tokens already substituted. Returns "" when the badge is off.
props.title / props.instructionText / props.buttonTextMerchant-set sidebar text, tokens already substituted.
props.skipCartCheckout / props.checkoutUrlSee the callout below.

Honor skipCartCheckout

When props.skipCartCheckout is true, render the call-to-action as an anchor pointing at props.checkoutUrl rather than a plain <button> — some checkout providers only detect a real link. onAddToCart still performs the redirect for you; you only need to change the element:

<a
  href={props.checkoutUrl}
  onClick={(e) => {
    e.preventDefault();
    if (!props.isAddToCartDisabled) props.onAddToCart();
  }}
>
  {props.buttonText}
</a>

Recipe — box tabs with savings badges:

{props.showBoxSelector && (
  <div className="my-boxes">
    {props.boxes.map((box) => (
      <button
        key={box.id}
        className={"my-box " + (box.id === props.selectedBoxId ? "is-selected" : "")}
        onClick={() => props.onBoxChange(box.id)}
      >
        <span>{box.title}</span>
        {props.getBoxBadgeText(box) && (
          <span className="my-box-badge">{props.getBoxBadgeText(box)}</span>
        )}
      </button>
    ))}
  </div>
)}

Toast — the confirmation message

Root class: foxr-bb-toast

Rendered only while a message is active.

PropWhat it is
props.messageThe message to show. Never empty when this template renders.

Recipe — starting template:

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

What happens if the app adds new props later?

Existing templates keep working — new capabilities are added to props, never removed. New props show up in the in-app props reference and here.

On this page