Skip to content

Custom Template

Overview

This allows developers the ability to add custom functionality that is currently not achievable by the tabbed widget settings. These templates can use any globally accessible functions. Developers can add additional Vue.js/HTML to these templates as needed. As these templates are written in Vue.js they accept any Vue.js attributes such as v-if for conditional rendering.

A tabbed widget renders one tab per data source. Every tab fetches its products when the page loads, so switching tabs never makes a request. Each tab owns its own product display (columns per screen size and an optional carousel), an optional description shown under the tab name, and an optional add-to-cart label. The widget is always embedded: there is no popup display type, timer or close control.

Setting up Custom Templates

There are a couple of ways these templates can be used. They can target all widgets of that widget type or, they can target a specific widget if you do not want the custom work to be applied to all widgets of that specific widget type.

Typically developers will create a new snippet named something like "rebuy-templates.liquid" and paste the contents of the code in that file. Then render that snippet in the associated file or in the theme.liquid file if they want the template to be globally accessible.

Tab Data Available in the Template

Expression Description
getTabbedWidgetLabel(key, tab?) Any language string: the tab's own language entry first, then the widget language, then empty. Every piece of copy in the template goes through it, so custom templates pick up copy changes.
getConfigTabs() The authored tabs, in order. Tab identity is the array index (tab_index).
shouldShowTab(tab_index) false once a tab's data source returned no products; such tabs render no tab button.
shouldRenderTab(tab_index) true for the active tab while the strip still shows it; only the active panel is in the DOM, and a tab that settled empty never leaves a blank panel behind.
getTabProducts(tab) The products fetched for a tab.
getTabButtonLabel(product, tab) The add-to-cart label: the tab's own language.add_to_cart when set, otherwise the widget label.
getTabLayoutClasses(tab) large-columns-N medium-columns-N small-columns-N from the tab's product_display.breakpoints.
hasTabSplideCarousel(tab) Whether the tab's panel runs as a carousel (product_display.carousel.enabled).
handleSelectingTab(tab_index) / handleTabKeydown($event, tab_index) Tab selection by click and by keyboard (Arrow keys, Home, End).
isTabLoading(tab) / isTabProductPlaceholder(product) A tab whose source has not settled shows skeleton placeholders (one per desktop column) instead of an empty panel.
getTabProductKey(tab_index, product) Vue key per card; includes the selected variant so variants-as-products clones stay distinct.
getTabProductSaleBadge(product) / getTabProductHoverImage(product) Percent-off badge from the compare-at price (it carries the rebuy-money sale classes, so your sale-price color paints it), and the second product image shown on hover when images.show_second_image_on_hover is on.
shouldShowTabProductSaleBadge(product) Gates the badge: shown only while product_options.show_saving_badge is on (the default; Show Savings Badge in the admin's Product settings) and the card is actually on sale.
getTabbedWidgetCardClasses() Root modifier class for the card image ratio (images.aspect_ratio: 1:1, 3:4 or 9:16), applied through the --rebuy-tabbed-widget-image-ratio custom property.

Line items added from a tab carry the _r_tab_index (0-based) and _r_tab_title properties alongside the widget attribution. The tab a shopper selects is remembered for the rest of their browsing session (per widget, in sessionStorage under _r_tab_<widgetId>) and is the active tab on the next page that renders the widget; if that tab has no products on the new page, the first tab with products shows instead, without overwriting the remembered choice. Nothing is remembered across sessions. Tab switches and per-tab loads are exposed as events; see Event Listeners.

Per-tab discounts

Each tab can override the widget discount. In the admin, open the tab's settings and turn on Widget Discount in the tab's Discount card, then pick a type, an amount, an optional message and an optional quantity. The tab's products are priced with that discount on the card (variantPrice, variantCompareAtPrice and the savings badge all read it), and when the widget is Discounted By Functions (the default for this widget type) Shopify applies the same discount at checkout, matched through the _r_tab_index line property. A tab set to None gets no discount even while the widget has one; a tab with the toggle off inherits the widget discount. Discounted By, Discounted From and protection stay on the widget's Discount screen and cover every tab, so a per-tab override behaves exactly like the widget discount under whichever mechanism the widget uses.

In settings.tabs[] the override is stored as discount: { enabled, type, amount, message, quantity } (null = inherit). quantity is the tab's own cap on how many units of one cart line get the discount; leave it out to use the widget's Quantity.

Tabbed Widget Default Template

Last Updated: September 14th, 2026
<script id="rebuy-tabbed-widget-template" type="text/template">
    <div class="rebuy-widget rebuy-tabbed-widget"
    v-cloak
    v-bind:id="'rebuy-widget-' + id"
    v-bind:class="['widget-type-tabbed-widget', shouldShowTabbedWidget() ? 'is-visible' : 'is-hidden', getTabbedWidgetCardClasses()]"
    role="region"
    aria-label="Tabbed product recommendations">

        <h3 class="super-title" v-if="getTabbedWidgetLabel('super_title')" v-html="getTabbedWidgetLabel('super_title')"></h3>

        <h4 class="primary-title" v-if="getTabbedWidgetLabel('title')" v-html="getTabbedWidgetLabel('title')"></h4>

        <div class="description" v-if="getTabbedWidgetLabel('description')" v-html="getTabbedWidgetLabel('description')"></div>

        <div
            v-bind:class="getTabStripClasses()"
            role="tablist"
            aria-label="Product tabs">
            <template v-for="(tab, tab_index) in getConfigTabs()">
                <button
                    v-if="shouldShowTab(tab_index)"
                    v-bind:key="'rebuy-tabbed-widget__tab-' + tab_index"
                    v-bind:id="getTabButtonId(tab_index)"
                    v-bind:class="getTabClasses(tab_index)"
                    v-bind:aria-selected="getTabAriaSelected(tab_index)"
                    v-bind:aria-controls="getTabAriaControls(tab_index)"
                    v-bind:tabindex="getTabTabindex(tab_index)"
                    v-on:click="handleSelectingTab(tab_index)"
                    v-on:keydown="handleTabKeydown($event, tab_index)"
                    role="tab"
                    type="button">
                    <span class="rebuy-tabbed-widget__tab-title" v-html="tab.title"></span>
                    <span class="rebuy-tabbed-widget__tab-description" v-if="hasTabDescription(tab)" v-html="tab.description"></span>
                </button>
            </template>
        </div>

        <template v-for="(tab, tab_index) in getConfigTabs()">
        <div
            v-if="shouldRenderTab(tab_index)"
            v-bind:key="getTabPanelKey(tab_index, tab)"
            v-bind:id="getTabPanelId(tab_index)"
            v-bind:aria-labelledby="getTabButtonId(tab_index)"
            class="rebuy-tabbed-widget__panel"
            role="tabpanel">

            <div
                class="rebuy-product-grid start-column"
                v-bind:class="[getTabLayoutClasses(tab), 'rebuy-product-grid__tab-' + tab_index]"
                v-bind:role="getTabGridRole(tab)"
                v-bind:aria-busy="getTabGridAriaBusy(tab)"
                v-bind:aria-roledescription="getTabGridAriaRoleDescription(tab)"
                v-bind:aria-label="getTabGridAriaLabel(tab)">

                <div
                    v-for="(product, product_index) in getTabProducts(tab)"
                    v-bind:key="getTabProductKey(tab_index, product)"
                    v-bind:class="getTabProductBlockClasses(product, tab)"
                    v-bind:aria-label="getTabProductBlockAriaLabel(product, tab)"
                    v-bind:aria-roledescription="getTabProductBlockAriaRoleDescription(tab)"
                    v-bind:role="getTabProductBlockRole(tab)"
                    class="rebuy-product-block"
                >

                    <div v-if="isTabProductPlaceholder(product)" class="rebuy-tabbed-widget__product-skeleton" aria-hidden="true">
                        <span class="rebuy-tabbed-widget__product-skeleton-media"></span>
                        <span class="rebuy-tabbed-widget__product-skeleton-line"></span>
                        <span class="rebuy-tabbed-widget__product-skeleton-line"></span>
                    </div>

                    <template v-else>
                    <div class="rebuy-product-media">
                        <span class="rebuy-tabbed-widget__badge rebuy-money sale" v-if="shouldShowTabProductSaleBadge(product)">
                            <span v-html="getTabProductSaleBadge(product)"></span>
                        </span>
                        <a
                            v-if="shouldShowTabProductImageLink()"
                            class="rebuy-product-image"
                            tabindex="-1"
                            v-bind:href="learnMoreURL(product)"
                            v-bind:style="imageStyles"
                            v-on:click="learnMore(product, $event);"
                            v-bind:class="getTabLearnMoreClasses()"
                            rel="nofollow"
                        >
                            <img v-bind:loading="getTabProductImageLoading(product_index)" v-bind:src="itemImage(product, product.selected_variant, '400x400')" v-bind:alt="itemImageAlt(product, product.selected_variant)">
                            <img v-if="getTabProductHoverImage(product)" class="rebuy-tabbed-widget__hover-image" v-bind:src="getTabProductHoverImage(product)" alt="" aria-hidden="true" loading="lazy">
                        </a>
                        <button
                            v-if="shouldShowTabProductImageTrigger()"
                            class="rebuy-product-image-trigger"
                            v-bind:style="imageStyles"
                            v-on:click="openQuickViewVariantModal(product)"
                            v-bind:aria-label="'Quick view ' + product.title"
                            type="button"
                        >
                            <img v-bind:loading="getTabProductImageLoading(product_index)" v-bind:src="itemImage(product, product.selected_variant, '400x400')" v-bind:alt="itemImageAlt(product, product.selected_variant)">
                            <img v-if="getTabProductHoverImage(product)" class="rebuy-tabbed-widget__hover-image" v-bind:src="getTabProductHoverImage(product)" alt="" aria-hidden="true" loading="lazy">
                        </button>
                    </div>
                    <div class="rebuy-product-info">
                        <h5
                            class="rebuy-product-title"
                            v-bind:class="getTabLearnMoreClasses()">
                            <a
                                v-bind:href="learnMoreURL(product)"
                                class="rebuy-product-title-link"
                                v-on:click="learnMore(product, $event);"
                                v-html="product.title"
                                v-bind:class="getTabLearnMoreClasses()"
                                v-bind:aria-label="'View ' + product.title"
                                rel="nofollow"
                            ></a>
                        </h5>
                        <div
                            class="rebuy-product-vendor-container"
                            v-if="showProductVendor(product.vendor)"
                        >
                            <a
                                class="rebuy-product-vendor"
                                v-bind:class="getTabVendorLinkClasses()"
                                v-html="product.vendor"
                                v-bind:href="vendorURL(product.vendor)"
                                v-bind:aria-label="'View ' + product.vendor + ' products'"
                                rel="nofollow"
                            ></a>
                        </div>
                        <div class="rebuy-variant-title" v-if="showVariantTitle(product)" v-html="product.selected_variant.title"></div>
                        <div class="rebuy-product-review" v-if="hasProductReviews(product)" aria-label="product star rating">
                            <span class="rebuy-star-rating">
                                <span
                                    v-if="product.reviews.star_rating"
                                    class="rebuy-star-rating-value sr-only"
                                    v-html="product.reviews.star_rating + ' stars out of 5 stars'"
                                >
                                </span>
                                <span class="rebuy-star-rating-background"></span>
                                <span class="rebuy-star-rating-foreground" v-bind:style="{ width: productReviewRatingPercentage(product) }"></span>
                            </span>
                            <span class="rebuy-review-count" v-html="productReviewCount(product)"></span>
                        </div>

                        <div class="rebuy-product-price">
                            <div v-if="isTabProductOnSale(product)">
                                <span class="rebuy-money sale">
                                    <span class="sr-only">Sale price</span>
                                    <span v-html="formatMoney(variantPrice(product, product.selected_variant))"></span>
                                </span>
                                <span class="rebuy-money compare-at">
                                    <span class="sr-only">Original price</span>
                                    <span v-html="formatMoney(getTabProductComparePrice(product))"></span>
                                </span>
                            </div>
                            <div v-else>
                                <span class="rebuy-money">
                                    <span class="sr-only">Price</span>
                                    <span v-html="formatMoney(variantPrice(product, product.selected_variant))"></span>
                                </span>
                            </div>
                        </div>
                        <div class="rebuy-product-description" v-if="showProductDescription(product)" v-html="text(product.body_html)"></div>
                    </div>
                    <div class="rebuy-product-options" v-if="showVariantSelect(product)">
                        <select
                            title="Select product variant"
                            :id="id + '-' + tab_index + '-select-' + product_index"
                            v-bind:class="getTabVariantSelectClasses()"
                            class="rebuy-select"
                            v-bind:aria-label="'variant of ' + product.title"
                            v-model="product.selected_variant_id"
                            v-on:change="selectVariant(product)">
                            <option
                                v-for="variant in product.variants"
                                class="rebuy-product-options__option"
                                :class="{ 'rebuy-product-options__option--oos' : !variantAvailable(variant) }"
                                v-bind:value="variant.id"
                                :disabled="!variantAvailable(variant)"
                                v-html="formatVariantOptionTitle(variant)">
                            </option>
                        </select>

                        <div
                            class="rebuy-product-options__button-swatches-container"
                            v-bind:class="getTabSwatchContainerClasses()"
                            >
                            <div
                                v-for="option in product.options"
                                :key="'option-' + tab_index + '-' + product.id + '-' + option.name"
                                class="rebuy-product-options__button-swatches rebuy-size-swatches"
                                >
                                <div
                                    v-for="(value, value_index) in filterOOSOptions(option, product)"
                                    :key="'value-' + tab_index + '-' + product.id + '-' + option.name + '-' + value + '-' + value_index"
                                    >
                                    <div v-if="displayColorSwatches(option)" class="rebuy-product-options__button-swatch rebuy-color-swatch">
                                        <input
                                            :name="id + '-' + tab_index + '-color-' + product_index"
                                            :id="id + '-' + tab_index + '-color-' + product_index + '-' + value + '-' + value_index"
                                            :checked="hasSwatchOptionSelected(product, value, value_index, 'color')"
                                            :value="value"
                                            type="radio"
                                            class="rebuy-color-input hide"
                                            v-on:change="selectVariantByOption(product, option.name, value)"
                                            :disabled="isDisabledOptionValue(product, option.name, value)"
                                        />

                                        <label
                                            :for="id + '-' + tab_index + '-color-' + product_index + '-' + value + '-' + value_index"
                                            :style="getMerchantThemeColorAssetStyle(value)"
                                            :title="value"
                                            class="rebuy-color-label"
                                            :class="{ 'rebuy-product-options__button-swatch-label--disabled' : isDisabledOptionValue(product, option.name, value) }"
                                        >
                                        </label>
                                    </div>

                                    <div v-if="!displayColorSwatches(option)" class="rebuy-product-options__button-swatch rebuy-size-swatch">
                                        <input
                                            :name="id + '-' + tab_index + '-' + option.name + '-' + product_index"
                                            :id="id + '-' + tab_index + '-' + option.name + '-' + product_index + '-' + value"
                                            :checked="hasSwatchOptionSelected(product, value, value_index, option.name)"
                                            :value="value"
                                            type="radio"
                                            class="rebuy-size-input hide"
                                            v-on:change="selectVariantByOption(product, option.name, value)"
                                            :disabled="isDisabledOptionValue(product, option.name, value)"
                                        />

                                        <label
                                            :for="id + '-' + tab_index + '-' + option.name + '-' + product_index + '-' + value"
                                            class="rebuy-size-label"
                                            :class="{ 'rebuy-product-options__button-swatch-label--disabled' : isDisabledOptionValue(product, option.name, value) }"
                                        >
                                            {{ value }}
                                        </label>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>

                    <div class="rebuy-product-actions">
                        <button
                            v-if="shouldShowTabProductQuickViewButton()"
                            class="product-quick-view-button rebuy-button outline"
                            v-on:click="openQuickViewVariantModal(product)"
                            v-bind:aria-label="'Quick view ' + product.title"
                            type="button">
                            <span v-html="getQuickViewButtonText()"></span>
                        </button>

                        <div class="subscription-checkbox" v-if="showSubscriptionOptions(product)">
                            <label class="rebuy-checkbox-label">
                                <input
                                    class="checkbox-input rebuy-checkbox"
                                    v-model="product.subscription"
                                    v-on:change="toggleSubscription(product)"
                                    type="checkbox"
                                    :aria-label="upgradeToSubscriptionAriaLabel(product)"
                                />
                                <span class="checkbox-label" v-html="upgradeToSubscriptionLabel(product)"></span>
                            </label>
                        </div>

                        <div class="subscription-frequency" v-if="showSubscriptionFrequency(product)">
                            <select
                                class="rebuy-select"
                                aria-label="subscription frequency"
                                v-model="product.subscription_frequency"
                                v-on:change="updateSubscriptionFrequency(product)">
                                <option v-for="frequency in product.subscription_frequencies" v-bind:value="frequency">{{ frequencyLabel(frequency, product.subscription_interval) }}</option>
                            </select>
                        </div>

                        <div class="product-quantity" v-if="hasQuantityInputEnabled()">
                            <div class="rebuy-select-wrapper">
                                <label class="rebuy-label" :for="id + '-' + tab_index + '-quantity-' + product_index">Quantity</label>
                                <select
                                    class="rebuy-select"
                                    :id="id + '-' + tab_index + '-quantity-' + product_index"
                                    v-model="product.quantity">
                                    <option v-for="n in maxQuantityInputValue()" v-bind:value="n">{{ n }}</option>
                                </select>
                            </div>
                        </div>

                        <button
                            class="rebuy-button"
                            v-bind:class="{ working: isTabProductWorking(product) }"
                            v-bind:disabled="isTabProductAddDisabled(product)"
                            v-bind:aria-label="buttonAriaLabel(product)"
                            v-on:click="addToCart(product)"
                            type="button">
                                <span v-html="getTabButtonLabel(product, tab)"></span>
                        </button>
                    </div>
                    </template>
                </div>

            </div>

        </div>
        </template>

        <div class="powered-by-rebuy">
            <a v-bind:href="getTabbedWidgetPoweredByURL()" target="_blank" rel="noopener">
                Powered by Rebuy
            </a>
        </div>
    </div>
</script>
See something that needs updating? Suggest an edit