Compatibility: Dawn version 2.5 below only
I have several request to make a vertical slider for Dawn 2.5 and below. If you have Dawn 2.5 and above, please refer to this link instead.
You can reposition the slider from left to right and vice versa.
Check DEMO store here 💻. Password: made4uo
What is a Vertical Product Image Slider for Shopify Dawn Theme 2.5 and below?
A vertical slider (or carousel) displays content in a vertical scrolling or sliding format. Instead of moving horizontally, the content transitions up or down, creating a dynamic and space-saving way to showcase multiple items.
Why Use a Vertical Product Image Slider for Shopify Dawn Theme 2.5 and below?
- Space Optimization: Vertical sliders are great for maximizing space, especially on mobile devices where horizontal space is limited.
- Engaging Presentation: The vertical scrolling motion can be more engaging than static displays, capturing visitors' attention.
- Showcase Multiple Items: Display a selection of products or other content in a compact and visually appealing way.
- Modern Look: Vertical sliders can give your store a modern and up-to-date feel.
- Improved Mobile Experience: Vertical scrolling is a natural gesture on mobile devices, making vertical sliders particularly well-suited for mobile users.
Additional Considerations:
- Content Length: Keep the content within each slide concise and easy to digest.
- Visual Appeal: Design the slider to be visually appealing and consistent with your brand.
- Mobile Responsiveness: Ensure the slider works well on mobile devices.
- Speed and Transitions: Choose appropriate slider speed and transition effects.
- Navigation: Provide clear navigation (arrows or dots) if needed.
What you are buying:
- Vertical slider
- Zoom in hover available in desktop view
- Works with variant changes
What makes our code better:
- We do not use external libraries, with that being said, our code will have no to minimal effect to your website's speed performance
- We do not leave or add codes use to advertise for our website
- Our code is mobile friendly
Any issues related to the code will be fix with no additional cost, excluding code customization requests. Simply contact us with "Chat with us." We are just a button away.
[]
Steps on how to add Vertical Product Image Slider for Shopify Dawn Theme 2.5 and below
Step #1. Open your code editor
From you Admin page, go to Online store, then Themes. Choose the theme you want to edit, then click the three dots, then Edit code.
Step #2. Open the Sections folder
And look for "main-product.liquid". Replace the existing code with the code below.
{% comment %}theme-check-disable TemplateLength{% endcomment %} {{ 'section-main-product.css' | asset_url | stylesheet_tag }} {{ 'component-accordion.css' | asset_url | stylesheet_tag }} {{ 'component-price.css' | asset_url | stylesheet_tag }} {{ 'component-rte.css' | asset_url | stylesheet_tag }} {{ 'product-slider.css' | asset_url | stylesheet_tag }} {{ 'component-rating.css' | asset_url | stylesheet_tag }} {{ 'component-loading-overlay.css' | asset_url | stylesheet_tag }} <link rel="stylesheet" href="{{ 'component-deferred-media.css' | asset_url }}" media="print" onload="this.media='all'"> <script src="{{ 'product-form.js' | asset_url }}" defer="defer"></script> {%- assign first_3d_model = product.media | where: 'media_type', 'model' | first -%} {%- if first_3d_model -%} {{ 'component-product-model.css' | asset_url | stylesheet_tag }} <link id="ModelViewerStyle" rel="stylesheet" href="https://cdn.shopify.com/shopifycloud/model-viewer-ui/assets/v1.0/model-viewer-ui.css" media="print" onload="this.media='all'" > <link id="ModelViewerOverride" rel="stylesheet" href="{{ 'component-model-viewer-ui.css' | asset_url }}" media="print" onload="this.media='all'" > {%- endif -%} <section class="page-width"> <div class="product grid grid--1-col {% if product.media.size > 0 %}grid--2-col-tablet{% else %}product--no-media{% endif %}"> <div class="grid__item product__media-wrapper"> <vertical-slider-component> <div class="slider-container" style="flex-direction: {{section.settings.slider_placement}}"> <a class="skip-to-content-link button visually-hidden" href="#ProductInfo-{{ section.id }}"> {{ 'accessibility.skip_to_product_info' | t }} </a> <div class="mainContainer"> <ul class="mainBox"> {%- for media in product.media -%} <li class=" mainBoxItem product__media-item grid__item slider__slide {% if media.media_type != 'image' %} hide-modal {% endif %} {% if media.media_type == 'image' %}image-zoom {% endif %} " data-media-id="{{ section.id }}-{{ media.id }}" data-media-index="{{forloop.index}}" > {% render 'product-thumbnail', media: media, position: forloop.index, loop: section.settings.enable_video_looping, modal_id: section.id, xr_button: true %} </li> {%- endfor -%} </ul> <div class="slider-buttons no-js-hidden{% if product.media.size < 2 %} small-hide{% endif %}"> <button type="button" class="slider-button button--prev" name="previous" aria-label="{{ 'accessibility.previous_slide' | t }}" > {% render 'icon-caret' %} </button> <div class="slider-counter caption"> <span class="slider-counter--current">1</span> <span aria-hidden="true"> / </span> <span class="visually-hidden">{{ 'accessibility.of' | t }}</span> <span class="slider-counter--total" data-total="{% if section.settings.hide_variants %}{{ product.media.size | minus: variant_images.size | plus: 1 }}{% else %}{{ product.media.size }}{% endif %}" > {%- if section.settings.hide_variants -%} {{- product.media.size | minus: variant_images.size | plus: 1 -}} {%- else -%} {{- product.media.size -}} {%- endif -%} </span> </div> <button type="button" class="slider-button button--next" name="next" aria-label="{{ 'accessibility.next_slide' | t }}" > {% render 'icon-caret' %} </button> </div> </div> <div class="slider-wrapper thumbContainer"> <ul class="thumbBox slider" role="list"> {%- assign variant_images = product.images | where: 'attached_to_variant?', true | map: 'src' -%} {%- for media in product.media -%} <li class="thumbBoxItem product-slider slider__slide" data-thumb-id="{{ section.id }}-{{ media.id }}" {% if section.settings.hide_variants and variant_images contains media.src %} style="display: none;" {% endif %} > {% if media.media_type == 'video' %} <div class="video-btn" style="display: block"> <span class="product__media-icon motion-reduce v-btn" aria-hidden="true"> {%- liquid case media.media_type when 'video', 'external_video' render 'icon-play' when 'model' render 'icon-3d-model' else render 'icon-zoom' endcase -%} </span> <img class="slide-image {% if forloop.first %}active-thumb {% endif %}" src="{{ media.preview_image | img_url: 'large' }}" alt="{{ thumbnailAlt }}" {% if section.settings.square_image == true %} style="aspect-ratio: 1;" {% endif %} > </div> {% else %} <img class="slide-image {% if forloop.first %}active-thumb {% endif %}" src="{{ media.preview_image | img_url: 'large' }}" alt="{{ thumbnailAlt }}" {% if section.settings.square_image == true %} style="aspect-ratio: 1;" {% endif %} > {% endif %} </li> {% endfor %} </ul> <div class="button-wrapper {% if product.media.size <= 5 %} hidden {% endif %}"> <button type="button" class="slider-button slider-button--prev bigger-slider" name="previous" aria-label="{{ 'accessibility.previous_slide' | t }}" > {% render 'icon-caret' %} </button> <button type="button" class="slider-button slider-button--next bigger-slider" name="next" aria-label="{{ 'accessibility.next_slide' | t }}" > {% render 'icon-caret' %} </button> </div> </div> </div> </vertical-slider-component> {%- if first_3d_model -%} <button class="button button--full-width product__xr-button" type="button" aria-label="{{ 'products.product.xr_button_label' | t }}" data-shopify-xr data-shopify-model3d-id="{{ first_3d_model.id }}" data-shopify-title="{{ product.title | escape }}" data-shopify-xr-hidden > {% render 'icon-3d-model' %} {{ 'products.product.xr_button' | t }} </button> {%- endif -%} </div> <div class="product__info-wrapper grid__item"> <div id="ProductInfo-{{ section.id }}" class="product__info-container{% if section.settings.enable_sticky_info %} product__info-container--sticky{% endif %}" > {%- assign product_form_id = 'product-form-' | append: section.id -%} {%- for block in section.blocks -%} {%- case block.type -%} {%- when '@app' -%} {% render block %} {%- when 'text' -%} <p class="product__text{% if block.settings.text_style == 'uppercase' %} caption-with-letter-spacing{% elsif block.settings.text_style == 'subtitle' %} subtitle{% endif %}" {{ block.shopify_attributes }} > {{- block.settings.text -}} </p> {%- when 'title' -%} <h1 class="product__title" {{ block.shopify_attributes }}> {{ product.title | escape }} </h1> {%- when 'price' -%} <div class="no-js-hidden" id="price-{{ section.id }}" {{ block.shopify_attributes }}> {%- render 'price', product: product, use_variant: true, show_badges: true, price_class: 'price--large' -%} </div> {%- if shop.taxes_included or shop.shipping_policy.body != blank -%} <div class="product__tax caption rte"> {%- if shop.taxes_included -%} {{ 'products.product.include_taxes' | t }} {%- endif -%} {%- if shop.shipping_policy.body != blank -%} {{ 'products.product.shipping_policy_html' | t: link: shop.shipping_policy.url }} {%- endif -%} </div> {%- endif -%} <div {{ block.shopify_attributes }}> {%- form 'product', product, id: 'product-form-installment', class: 'installment caption-large' -%} <input type="hidden" name="id" value="{{ product.selected_or_first_available_variant.id }}"> {{ form | payment_terms }} {%- endform -%} </div> {%- when 'description' -%} {%- if product.description != blank -%} <div class="product__description rte"> {{ product.description }} </div> {%- endif -%} {%- when 'custom_liquid' -%} {{ block.settings.custom_liquid }} {%- when 'collapsible_tab' -%} <div class="product__accordion accordion" {{ block.shopify_attributes }}> <details> <summary> <div class="summary__title"> {% render 'icon-accordion', icon: block.settings.icon %} <h2 class="h4 accordion__title"> {{ block.settings.heading | default: block.settings.page.title }} </h2> </div> {% render 'icon-caret' %} </summary> <div class="accordion__content rte"> {{ block.settings.content }} {{ block.settings.page.content }} </div> </details> </div> {%- when 'quantity_selector' -%} <div class="product-form__input product-form__quantity" {{ block.shopify_attributes }}> <label class="form__label" for="Quantity-{{ section.id }}"> {{ 'products.product.quantity.label' | t }} </label> <quantity-input class="quantity"> <button class="quantity__button no-js-hidden" name="minus" type="button"> <span class="visually-hidden"> {{- 'products.product.quantity.decrease' | t: product: product.title | escape -}} </span> {% render 'icon-minus' %} </button> <input class="quantity__input" type="number" name="quantity" id="Quantity-{{ section.id }}" min="1" value="1" form="product-form-{{ section.id }}" > <button class="quantity__button no-js-hidden" name="plus" type="button"> <span class="visually-hidden"> {{- 'products.product.quantity.increase' | t: product: product.title | escape -}} </span> {% render 'icon-plus' %} </button> </quantity-input> </div> {%- when 'popup' -%} <modal-opener class="product-popup-modal__opener no-js-hidden" data-modal="#PopupModal-{{ block.id }}" {{ block.shopify_attributes }} > <button id="ProductPopup-{{ block.id }}" class="product-popup-modal__button link" type="button" aria-haspopup="dialog" > {{ block.settings.text | default: block.settings.page.title }} </button> </modal-opener> <a href="{{ block.settings.page.url }}" class="product-popup-modal__button link no-js"> {{- block.settings.text -}} </a> {%- when 'share' -%} <share-button class="share-button" {{ block.shopify_attributes }}> <button class="share-button__button hidden"> {% render 'icon-share' %} {{ block.settings.share_label | escape }} </button> <details> <summary class="share-button__button"> {% render 'icon-share' %} {{ block.settings.share_label | escape }} </summary> <div id="Product-share-{{ section.id }}" class="share-button__fallback motion-reduce"> <div class="field"> <span id="ShareMessage-{{ section.id }}" class="share-button__message hidden" role="status"> </span> <input type="text" class="field__input" id="url" value="{{ shop.url | append: product.url }}" placeholder="{{ 'general.share.share_url' | t }}" onclick="this.select();" readonly > <label class="field__label" for="url">{{ 'general.share.share_url' | t }}</label> </div> <button class="share-button__close hidden no-js-hidden"> {% render 'icon-close' %} <span class="visually-hidden">{{ 'general.share.close' | t }}</span> </button> <button class="share-button__copy no-js-hidden"> {% render 'icon-clipboard' %} <span class="visually-hidden">{{ 'general.share.copy_to_clipboard' | t }}</span> </button> </div> </details> </share-button> <script src="{{ 'share.js' | asset_url }}" defer="defer"></script> {%- when 'variant_picker' -%} {%- unless product.has_only_default_variant -%} {%- if block.settings.picker_type == 'button' -%} <variant-radios class="no-js-hidden" data-section="{{ section.id }}" data-url="{{ product.url }}" {{ block.shopify_attributes }} > {%- for option in product.options_with_values -%} <fieldset class="js product-form__input"> <legend class="form__label">{{ option.name }}</legend> {%- for value in option.values -%} <input type="radio" id="{{ section.id }}-{{ option.name }}-{{ forloop.index0 }}" name="{{ option.name }}" value="{{ value | escape }}" form="product-form-{{ section.id }}" {% if option.selected_value == value %} checked {% endif %} > <label for="{{ section.id }}-{{ option.name }}-{{ forloop.index0 }}"> {{ value }} </label> {%- endfor -%} </fieldset> {%- endfor -%} <script type="application/json"> {{ product.variants | json }} </script> </variant-radios> {%- else -%} <variant-selects class="no-js-hidden" data-section="{{ section.id }}" data-url="{{ product.url }}" {{ block.shopify_attributes }} > {%- for option in product.options_with_values -%} <div class="product-form__input product-form__input--dropdown"> <label class="form__label" for="Option-{{ section.id }}-{{ forloop.index0 }}"> {{ option.name }} </label> <div class="select"> <select id="Option-{{ section.id }}-{{ forloop.index0 }}" class="select__select" name="options[{{ option.name | escape }}]" form="product-form-{{ section.id }}" > {%- for value in option.values -%} <option value="{{ value | escape }}" {% if option.selected_value == value %} selected="selected" {% endif %} > {{ value }} </option> {%- endfor -%} </select> {% render 'icon-caret' %} </div> </div> {%- endfor -%} <script type="application/json"> {{ product.variants | json }} </script> </variant-selects> {%- endif -%} {%- endunless -%} <noscript class="product-form__noscript-wrapper-{{ section.id }}"> <div class="product-form__input{% if product.has_only_default_variant %} hidden{% endif %}"> <label class="form__label" for="Variants-{{ section.id }}"> {{- 'products.product.product_variants' | t -}} </label> <div class="select"> <select name="id" id="Variants-{{ section.id }}" class="select__select" form="product-form"> {%- for variant in product.variants -%} <option {% if variant == product.selected_or_first_available_variant %} selected="selected" {% endif %} {% if variant.available == false %} disabled {% endif %} value="{{ variant.id }}" > {{ variant.title }} {%- if variant.available == false %} - {{ 'products.product.sold_out' | t }}{% endif %} - {{ variant.price | money | strip_html }} </option> {%- endfor -%} </select> {% render 'icon-caret' %} </div> </div> </noscript> {%- when 'buy_buttons' -%} <div {{ block.shopify_attributes }}> <product-form class="product-form"> <div class="product-form__error-message-wrapper" role="alert" hidden> <svg aria-hidden="true" focusable="false" role="presentation" class="icon icon-error" viewBox="0 0 13 13" > <circle cx="6.5" cy="6.50049" r="5.5" stroke="white" stroke-width="2"/> <circle cx="6.5" cy="6.5" r="5.5" fill="#EB001B" stroke="#EB001B" stroke-width="0.7"/> <path d="M5.87413 3.52832L5.97439 7.57216H7.02713L7.12739 3.52832H5.87413ZM6.50076 9.66091C6.88091 9.66091 7.18169 9.37267 7.18169 9.00504C7.18169 8.63742 6.88091 8.34917 6.50076 8.34917C6.12061 8.34917 5.81982 8.63742 5.81982 9.00504C5.81982 9.37267 6.12061 9.66091 6.50076 9.66091Z" fill="white"/> <path d="M5.87413 3.17832H5.51535L5.52424 3.537L5.6245 7.58083L5.63296 7.92216H5.97439H7.02713H7.36856L7.37702 7.58083L7.47728 3.537L7.48617 3.17832H7.12739H5.87413ZM6.50076 10.0109C7.06121 10.0109 7.5317 9.57872 7.5317 9.00504C7.5317 8.43137 7.06121 7.99918 6.50076 7.99918C5.94031 7.99918 5.46982 8.43137 5.46982 9.00504C5.46982 9.57872 5.94031 10.0109 6.50076 10.0109Z" fill="white" stroke="#EB001B" stroke-width="0.7"> </svg> <span class="product-form__error-message"></span> </div> {%- form 'product', product, id: product_form_id, class: 'form', novalidate: 'novalidate', data-type: 'add-to-cart-form' -%} <input type="hidden" name="id" value="{{ product.selected_or_first_available_variant.id }}"> <div class="product-form__buttons"> <button type="submit" name="add" class="product-form__submit button button--full-width {% if block.settings.show_dynamic_checkout and product.selling_plan_groups == empty %}button--secondary{% else %}button--primary{% endif %}" {% if product.selected_or_first_available_variant.available == false %} disabled {% endif %} > <span> {%- if product.selected_or_first_available_variant.available -%} {{ 'products.product.add_to_cart' | t }} {%- else -%} {{ 'products.product.sold_out' | t }} {%- endif -%} </span> <div class="loading-overlay__spinner hidden"> <svg aria-hidden="true" focusable="false" role="presentation" class="spinner" viewBox="0 0 66 66" xmlns="http://www.w3.org/2000/svg" > <circle class="path" fill="none" stroke-width="6" cx="33" cy="33" r="30"></circle> </svg> </div> </button> {%- if block.settings.show_dynamic_checkout -%} {{ form | payment_button }} {%- endif -%} </div> {%- endform -%} </product-form> {{ 'component-pickup-availability.css' | asset_url | stylesheet_tag }} {%- assign pick_up_availabilities = product.selected_or_first_available_variant.store_availabilities | where: 'pick_up_enabled', true -%} <pickup-availability class="product__pickup-availabilities no-js-hidden" {% if product.selected_or_first_available_variant.available and pick_up_availabilities.size > 0 %} available {% endif %} data-base-url="{{ shop.url }}{{ routes.root_url }}" data-variant-id="{{ product.selected_or_first_available_variant.id }}" data-has-only-default-variant="{{ product.has_only_default_variant }}" > <template> <pickup-availability-preview class="pickup-availability-preview"> {% render 'icon-unavailable' %} <div class="pickup-availability-info"> <p class="caption-large">{{ 'products.product.pickup_availability.unavailable' | t }}</p> <button class="pickup-availability-button link link--text underlined-link"> {{ 'products.product.pickup_availability.refresh' | t }} </button> </div> </pickup-availability-preview> </template> </pickup-availability> </div> <script src="{{ 'pickup-availability.js' | asset_url }}" defer="defer"></script> {%- when 'rating' -%} {%- if product.metafields.reviews.rating.value != blank -%} {% liquid assign rating_decimal = 0 assign decimal = product.metafields.reviews.rating.value.rating | modulo: 1 if decimal >= 0.3 and decimal <= 0.7 assign rating_decimal = 0.5 elsif decimal > 0.7 assign rating_decimal = 1 endif %} <div class="rating" role="img" aria-label="{{ 'accessibility.star_reviews_info' | t: rating_value: product.metafields.reviews.rating.value, rating_max: product.metafields.reviews.rating.value.scale_max }}" > <span aria-hidden="true" class="rating-star color-icon-{{ settings.accent_icons }}" style="--rating: {{ product.metafields.reviews.rating.value.rating | floor }}; --rating-max: {{ product.metafields.reviews.rating.value.scale_max }}; --rating-decimal: {{ rating_decimal }};" ></span> </div> <p class="rating-text caption"> <span aria-hidden="true"> {{- product.metafields.reviews.rating.value }} / {{ product.metafields.reviews.rating.value.scale_max -}} </span> </p> <p class="rating-count caption"> <span aria-hidden="true">({{ product.metafields.reviews.rating_count }})</span> <span class="visually-hidden"> {{- product.metafields.reviews.rating_count }} {{ 'accessibility.total_reviews' | t -}} </span> </p> {%- endif -%} {%- endcase -%} {%- endfor -%} </div> </div> </div> <product-modal id="ProductModal-{{ section.id }}" class="product-media-modal media-modal"> <div class="product-media-modal__dialog" role="dialog" aria-label="{{ 'products.modal.label' | t }}" aria-modal="true" tabindex="-1" > <button id="ModalClose-{{ section.id }}" type="button" class="product-media-modal__toggle" aria-label="{{ 'accessibility.close' | t }}" > {% render 'icon-close' %} </button> <div class="product-media-modal__content" role="document" aria-label="{{ 'products.modal.label' | t }}" tabindex="0" > {%- liquid if product.selected_or_first_available_variant.featured_media != null assign media = product.selected_or_first_available_variant.featured_media render 'product-media', media: media, loop: section.settings.enable_video_looping, variant_image: section.settings.hide_variants endif -%} {%- for media in product.media -%} {%- liquid if section.settings.hide_variants and variant_images contains media.src assign variant_image = true else assign variant_image = false endif unless media.id == product.selected_or_first_available_variant.featured_media.id render 'product-media', media: media, loop: section.settings.enable_video_looping, variant_image: variant_image endunless -%} {%- endfor -%} </div> </div> </product-modal> {% assign popups = section.blocks | where: 'type', 'popup' %} {%- for block in popups -%} <modal-dialog id="PopupModal-{{ block.id }}" class="product-popup-modal" {{ block.shopify_attributes }}> <div role="dialog" aria-label="{{ block.settings.text }}" aria-modal="true" class="product-popup-modal__content" tabindex="-1" > <button id="ModalClose-{{ block.id }}" type="button" class="product-popup-modal__toggle" aria-label="{{ 'accessibility.close' | t }}" > {% render 'icon-close' %} </button> <div class="product-popup-modal__content-info"> <h1 class="h2">{{ block.settings.page.title }}</h1> {{ block.settings.page.content }} </div> </div> </modal-dialog> {%- endfor -%} </section> {% javascript %} class ProductModal extends ModalDialog { constructor() { super(); } hide() { super.hide(); } show(e) { super.show(e); this.showActiveMedia(); } showActiveMedia() { this.querySelectorAll( `[data-media-id]:not([data-media-id="${this.openedBy.getAttribute( "data-media-id" )}"])` ).forEach((e) => { e.classList.remove("active"); }); const e = this.querySelector( `[data-media-id="${this.openedBy.getAttribute("data-media-id")}"]` ); const t = e.querySelector("template"); const o = t ? t.content : null; e.classList.add("active"); e.scrollIntoView(); const d = this.querySelector('[role="document"]'); d.scrollLeft = (e.width - d.clientWidth) / 2; if (e.nodeName == "DEFERRED-MEDIA" && o && o.querySelector(".js-youtube")) e.loadContent(); } } customElements.define("product-modal", ProductModal); {% endjavascript %} <script> document.addEventListener('DOMContentLoaded', function() { function isIE() { const ua = window.navigator.userAgent; const msie = ua.indexOf('MSIE '); const trident = ua.indexOf('Trident/'); return (msie > 0 || trident > 0); } if (!isIE()) return; const hiddenInput = document.querySelector('#{{ product_form_id }} input[name="id"]'); const noScriptInputWrapper = document.createElement('div'); const variantSwitcher = document.querySelector('variant-radios[data-section="{{ section.id }}"]') || document.querySelector('variant-selects[data-section="{{ section.id }}"]'); noScriptInputWrapper.innerHTML = document.querySelector('.product-form__noscript-wrapper-{{ section.id }}').textContent; variantSwitcher.outerHTML = noScriptInputWrapper.outerHTML; document.querySelector('#Variants-{{ section.id }}').addEventListener('change', function(event) { hiddenInput.value = event.currentTarget.value; }); }); </script> {%- if first_3d_model -%} <script type="application/json" id="ProductJSON-{{ product.id }}"> {{ product.media | where: 'media_type', 'model' | json }} </script> <script src="{{ 'product-model.js' | asset_url }}" defer></script> {%- endif -%} <script type="application/ld+json"> { "@context": "http://schema.org/", "@type": "Product", "name": {{ product.title | json }}, "url": {{ shop.url | append: product.url | json }}, {%- if product.selected_or_first_available_variant.featured_media -%} {%- assign media_size = product.selected_or_first_available_variant.featured_media.preview_image.width | append: 'x' -%} "image": [ {{ product.selected_or_first_available_variant.featured_media | img_url: media_size | prepend: "https:" | json }} ], {%- endif -%} "description": {{ product.description | strip_html | json }}, {%- if product.selected_or_first_available_variant.sku != blank -%} "sku": {{ product.selected_or_first_available_variant.sku | json }}, {%- endif -%} "brand": { "@type": "Thing", "name": {{ product.vendor | json }} }, "offers": [ {%- for variant in product.variants -%} { "@type" : "Offer", {%- if variant.sku != blank -%} "sku": {{ variant.sku | json }}, {%- endif -%} "availability" : "http://schema.org/{% if variant.available %}InStock{% else %}OutOfStock{% endif %}", "price" : {{ variant.price | divided_by: 100.00 | json }}, "priceCurrency" : {{ cart.currency.iso_code | json }}, "url" : {{ shop.url | append: variant.url | json }} }{% unless forloop.last %},{% endunless %} {%- endfor -%} ] } </script> {% schema %} { "name": "t:sections.main-product.name", "tag": "section", "class": "product-section spaced-section", "blocks": [ { "type": "@app" }, { "type": "text", "name": "t:sections.main-product.blocks.text.name", "settings": [ { "type": "text", "id": "text", "default": "Text block", "label": "t:sections.main-product.blocks.text.settings.text.label" }, { "type": "select", "id": "text_style", "options": [ { "value": "body", "label": "t:sections.main-product.blocks.text.settings.text_style.options__1.label" }, { "value": "subtitle", "label": "t:sections.main-product.blocks.text.settings.text_style.options__2.label" }, { "value": "uppercase", "label": "t:sections.main-product.blocks.text.settings.text_style.options__3.label" } ], "default": "body", "label": "t:sections.main-product.blocks.text.settings.text_style.label" } ] }, { "type": "title", "name": "t:sections.main-product.blocks.title.name", "limit": 1 }, { "type": "price", "name": "t:sections.main-product.blocks.price.name", "limit": 1 }, { "type": "quantity_selector", "name": "t:sections.main-product.blocks.quantity_selector.name", "limit": 1 }, { "type": "variant_picker", "name": "t:sections.main-product.blocks.variant_picker.name", "limit": 1, "settings": [ { "type": "select", "id": "picker_type", "options": [ { "value": "dropdown", "label": "t:sections.main-product.blocks.variant_picker.settings.picker_type.options__1.label" }, { "value": "button", "label": "t:sections.main-product.blocks.variant_picker.settings.picker_type.options__2.label" } ], "default": "button", "label": "t:sections.main-product.blocks.variant_picker.settings.picker_type.label" } ] }, { "type": "buy_buttons", "name": "t:sections.main-product.blocks.buy_buttons.name", "limit": 1, "settings": [ { "type": "checkbox", "id": "show_dynamic_checkout", "default": true, "label": "t:sections.main-product.blocks.buy_buttons.settings.show_dynamic_checkout.label", "info": "t:sections.main-product.blocks.buy_buttons.settings.show_dynamic_checkout.info" } ] }, { "type": "description", "name": "t:sections.main-product.blocks.description.name", "limit": 1 }, { "type": "share", "name": "t:sections.main-product.blocks.share.name", "limit": 1, "settings": [ { "type": "text", "id": "share_label", "label": "t:sections.main-product.blocks.share.settings.text.label", "default": "Share" }, { "type": "paragraph", "content": "t:sections.main-product.blocks.share.settings.featured_image_info.content" }, { "type": "paragraph", "content": "t:sections.main-product.blocks.share.settings.title_info.content" } ] }, { "type": "custom_liquid", "name": "t:sections.main-product.blocks.custom_liquid.name", "settings": [ { "type": "liquid", "id": "custom_liquid", "label": "t:sections.main-product.blocks.custom_liquid.settings.custom_liquid.label", "info": "t:sections.main-product.blocks.custom_liquid.settings.custom_liquid.info" } ] }, { "type": "collapsible_tab", "name": "t:sections.main-product.blocks.collapsible_tab.name", "settings": [ { "type": "text", "id": "heading", "default": "Collapsible tab", "info": "t:sections.main-product.blocks.collapsible_tab.settings.heading.info", "label": "t:sections.main-product.blocks.collapsible_tab.settings.heading.label" }, { "type": "richtext", "id": "content", "label": "t:sections.main-product.blocks.collapsible_tab.settings.content.label" }, { "type": "page", "id": "page", "label": "t:sections.main-product.blocks.collapsible_tab.settings.page.label" }, { "type": "select", "id": "icon", "options": [ { "value": "none", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__1.label" }, { "value": "box", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__2.label" }, { "value": "chat_bubble", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__3.label" }, { "value": "check_mark", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__4.label" }, { "value": "dryer", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__5.label" }, { "value": "eye", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__6.label" }, { "value": "heart", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__7.label" }, { "value": "iron", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__8.label" }, { "value": "leaf", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__9.label" }, { "value": "leather", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__10.label" }, { "value": "lock", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__11.label" }, { "value": "map_pin", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__12.label" }, { "value": "pants", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__13.label" }, { "value": "plane", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__14.label" }, { "value": "price_tag", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__15.label" }, { "value": "question_mark", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__16.label" }, { "value": "return", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__17.label" }, { "value": "ruler", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__18.label" }, { "value": "shirt", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__19.label" }, { "value": "shoe", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__20.label" }, { "value": "silhouette", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__21.label" }, { "value": "star", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__22.label" }, { "value": "truck", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__23.label" }, { "value": "washing", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__24.label" } ], "default": "check_mark", "label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.label" } ] }, { "type": "popup", "name": "t:sections.main-product.blocks.popup.name", "settings": [ { "type": "text", "id": "text", "default": "Pop-up link text", "label": "t:sections.main-product.blocks.popup.settings.link_label.label" }, { "id": "page", "type": "page", "label": "t:sections.main-product.blocks.popup.settings.page.label" } ] }, { "type": "rating", "name": "t:sections.main-product.blocks.rating.name", "limit": 1, "settings": [ { "type": "paragraph", "content": "t:sections.main-product.blocks.rating.settings.paragraph.content" } ] } ], "settings": [ { "type": "checkbox", "id": "enable_sticky_info", "default": true, "label": "t:sections.main-product.settings.enable_sticky_info.label" }, { "type": "header", "content": "t:sections.main-product.settings.header.content", "info": "t:sections.main-product.settings.header.info" }, { "type": "checkbox", "id": "hide_variants", "default": false, "label": "t:sections.main-product.settings.hide_variants.label" }, { "type": "checkbox", "id": "enable_video_looping", "default": false, "label": "t:sections.main-product.settings.enable_video_looping.label" } ] } {% endschema %} |
Step #3. Add CSS Code
Next, we need to have the CSS. Open the Asset folder, then "create a new asset." Name it "product-slider." Open the file, then place the code below.
.slider-container { margin: 0 auto; overflow: auto; display: flex; gap: 10px; position: relative; } .mainContainer { width: 100%; } .mainBox { width: 100%; position: relative; overflow: hidden; } .mainBoxItem { max-width: 100%; min-width: 50%; width: max-content; max-height: 100%; padding: 0; position: relative; margin: 0 auto; } .xoomImage { position: absolute; top: 0; left: 0; } .mainBoxItem:not(:first-child) { display: none !important; } ul { padding-inline-start: 0px !important; } slider-component { position: relative; display: block; } .slider-wrapper { display: flex; flex-direction: column; align-items: center; } .slider__slide { scroll-snap-align: start; flex-shrink: 0; } .thumbBox { flex-wrap: inherit; overflow-y: auto; scroll-snap-type: y mandatory; scroll-behavior: smooth; -webkit-overflow-scrolling: touch; display: flex; z-index: 1; flex-direction: column; align-items: center; margin: 0; width: 6vw; height: 34vw; max-width: 85px; max-height: calc(390px + 5vw); min-width: 30px; min-height: calc(215px + 5vw); } @media only screen and (max-width: 750px) { .thumbBox { width: 12vw; height: 65vw; padding: 5px; } } .product-slider { height: max-content; padding: 10px; display: inline-block; aspect-ratio: 1; } .slide-image { width: 100%; height: max-content; object-fit: contain; aspect-ratio: 1; } .slider { scrollbar-color: rgb(var(--color-foreground)) rgba(var(--color-foreground), 0.04); -ms-overflow-style: none; scrollbar-width: none; } .slider::-webkit-scrollbar { height: 0.4rem; width: 0.4rem; display: none; } .no-js .slider { -ms-overflow-style: auto; scrollbar-width: auto; } .no-js .slider::-webkit-scrollbar { display: initial; } .slider::-webkit-scrollbar-thumb { background-color: rgb(var(--color-foreground)); border-radius: 0.4rem; border: 0; } .slider::-webkit-scrollbar-track { background: rgba(var(--color-foreground), 0.04); border-radius: 0.4rem; } .button-wrapper { display: flex; justify-content: space-between; align-items: center; } .slider-container::-webkit-scrollbar { display: none; } .slider-buttons { display: none; align-items: center; justify-content: center; } .slider-button { color: rgba(var(--color-foreground), 0.75); background: transparent; border: none; cursor: pointer; height: 44px; } .slider-button:not([disabled]):hover { color: rgb(var(--color-foreground)); } .slider-button .icon { height: 1.2rem; } .slider-button[disabled] .icon { color: rgba(var(--color-foreground), 0.3); } .slider-button--next .icon { margin-right: -0.2rem; transform: rotate(0deg) translateX(0.15rem); } .slider-button--prev .icon { margin-left: -0.2rem; transform: rotate(180deg) translateX(-0.15rem); } .slider-button--next:not([disabled]):hover .icon { transform: rotate(0deg) translateX(0.15rem) scale(1.07); } .button--prev:not([disabled]):hover .icon { transform: rotate(90deg) translateX(-0.15rem) scale(1.07); } .button--next .icon { margin-right: -0.2rem; transform: rotate(-90deg) translateX(0.15rem); } .button--prev .icon { margin-left: -0.2rem; transform: rotate(90deg) translateX(-0.15rem); } .button--next:not([disabled]):hover .icon { transform: rotate(-90deg) translateX(0.15rem) scale(1.07); } .button--prev:not([disabled]):hover .icon { transform: rotate(90deg) translateX(-0.15rem) scale(1.07); } .video-btn { width: 100%; height: 100%; position: relative; } .v-btn { top: 2% !important; left: 2% !important; } .active-thumb { border: 3px solid white; outline: 2px solid black; } @media screen and (min-width: 750px) { .custom-width { padding: 0 10% !important; } } @media screen and (min-width: 990px) { .product__media-wrapper { max-width: 50% !important; width: calc(50% - 1rem / 2); } .product__info-wrapper { padding-left: 5rem !important; max-width: 50% !important; width: calc(50% - 1rem / 2); } } .mainBox > li:nth-child(1) > modal-opener > span { display: none !important; } .mainBox > li:nth-child(1) > modal-opener > button { display: none !important; } .mainBox > li > modal-opener > div { padding-top: 0 !important; } .mainBox > li > modal-opener > div > img { overflow: hidden; position: relative !important; } .hide-modal modal-opener { display: none; } @media only screen and (max-width: 750px) { .mainContainer .slider-buttons { display: flex; } .slider-container { display: block; } .thumbContainer { display: none; } .mainContainer .caption { margin: auto 15px; } .slider-button .icon { height: 0.7rem; } } |
Step #4. Still in the Asset folder
Open the global.js. Then place the code below at the very bottom.
class VerticalSliderComponent extends HTMLElement { constructor() { super(); this.mainContainer = this.querySelector(".mainContainer"); this.mainSlider = this.querySelector(".mainBox "); this.sliderContainer = this.querySelector(".thumbContainer"); this.slider = this.querySelector(".thumbBox"); this.sliderItems = this.querySelectorAll(".thumbBoxItem"); this.pageCount = this.mainContainer.querySelector( ".slider-counter--current" ); this.pageTotal = this.mainContainer.querySelector(".slider-counter--total"); this.prevButtonMain = this.mainContainer.querySelector( 'button[name="previous"]' ); this.nextButtonMain = this.mainContainer.querySelector( 'button[name="next"]' ); this.prevButton = this.sliderContainer.querySelector( 'button[name="previous"]' ); this.nextButton = this.sliderContainer.querySelector('button[name="next"]'); if (!this.slider || !this.nextButton) return; const resizeObserver = new ResizeObserver((entries) => this.initPages()); resizeObserver.observe(this.slider); this.slider.addEventListener("wheel", (e) => e.preventDefault(), { passive: false, }); this.slider.addEventListener("scroll", this.update.bind(this)); this.prevButtonMain.addEventListener("click", this.onMainClick.bind(this)); this.nextButtonMain.addEventListener("click", this.onMainClick.bind(this)); this.prevButton.addEventListener("click", this.onButtonClick.bind(this)); this.nextButton.addEventListener("click", this.onButtonClick.bind(this)); this.sliderItems.forEach((thumb) => thumb.addEventListener("click", this.updateImage.bind(this)) ); this.index = 1; } initPages() { const sliderItemsToShow = Array.from(this.sliderItems).filter( (element) => element.clientHeight > 0 ); this.sliderLastItem = sliderItemsToShow[sliderItemsToShow.length - 1]; if (sliderItemsToShow.length === 0) return; this.slidesPerPage = Math.floor( this.slider.clientHeight / sliderItemsToShow[0].clientHeight ); this.totalPages = sliderItemsToShow.length - this.slidesPerPage + 1; this.update(); this.zoom(); } update() { if (!this.pageCount || !this.pageTotal) return; this.currentPage = Math.round(this.slider.scrollTop / this.sliderLastItem.clientHeight) + 1; if (this.currentPage === 1) { this.prevButton.setAttribute("disabled", true); } else { this.prevButton.removeAttribute("disabled"); } if (this.currentPage === this.totalPages) { this.nextButton.setAttribute("disabled", true); } else { this.nextButton.removeAttribute("disabled"); } this.pageCount.textContent = this.currentPage; this.pageTotal.textContent = this.pageTotal.dataset.total; } onButtonClick(event) { event.preventDefault(); this.slideScrollPosition = event.currentTarget.name === "next" ? this.slider.scrollTop + 4 * this.sliderLastItem.clientHeight : this.slider.scrollTop - 4 * this.sliderLastItem.clientHeight; this.slider.scrollTo({ top: Math.floor(this.slideScrollPosition), }); this.currSlide = Math.round( this.slideScrollPosition / this.sliderLastItem.clientHeight ); if (this.currSlide <= 0) { this.prevButton.setAttribute("disabled", true); } else { this.prevButton.removeAttribute("disabled"); } if (this.currSlide >= this.totalPages - 1) { this.nextButton.setAttribute("disabled", true); } else { this.nextButton.removeAttribute("disabled"); } } onMainClick(event) { event.preventDefault(); this.totalImage = this.pageTotal.dataset.total; if (event.currentTarget.name == "next") { this.index++; if (this.index > this.totalImage) { this.index = 1; } } else { this.index--; if (this.index <= 0) { this.index = this.totalImage; } } this.updatePageCount(this.index); this.newImage = this.querySelector(`[data-media-index="${this.index}"]`); if (!this.newImage) return; this.parentData = this.newImage.parentElement; this.parentData.prepend(this.newImage); this.newImage.style.display = "block"; } updatePageCount(currentIndex) { this.pageCount.textContent = currentIndex; this.pageTotal.textContent = this.pageTotal.dataset.total; } updateImage(event) { this.clickedImage = event.currentTarget.dataset.thumbId; const newImage = document.querySelector( `[data-media-id="${this.clickedImage}"]` ); const parentData = newImage.parentElement; parentData.prepend(newImage); newImage.style.display = "block"; this.sliderItems.forEach((thumb) => thumb.querySelector("img").classList.remove("active-thumb") ); event.currentTarget.querySelector("img").classList.add("active-thumb"); this.zoom(); } updateThumb(newMedia) { const newId = newMedia.dataset.mediaId; this.newThumb = this.querySelector(`[data-thumb-id="${newId}"]`); this.thumbLocate = this.newThumb["offsetTop"]; this.querySelectorAll(".slide-image").forEach((thumb) => thumb.classList.remove("active-thumb") ); this.querySelector(`[data-thumb-id="${newId}"] > img`).classList.add( "active-thumb" ); this.slider.scrollTo({ top: this.thumbLocate }); this.zoom(); } zoom() { const mediaSmall = window.matchMedia("( min-width: 400px )"); const zoomItem = this.querySelector(".image-zoom:nth-child(1)"); this.mainSlider.addEventListener("mouseout", function () { zoomItem.style.transform = "scale(1)"; }); if (mediaSmall.matches) { this.mainSlider.addEventListener("mouseover", function () { zoomItem.style.transform = "scale(1.8)"; }); this.mainSlider.addEventListener("mousemove", function (e) { const itemWidth = zoomItem.getBoundingClientRect().width; const itemLeft = zoomItem.getBoundingClientRect().left; const itemHt = zoomItem.getBoundingClientRect().height; const itemTop = zoomItem.getBoundingClientRect().top + window.scrollY; const translateX = Math.round(((e.pageX - itemLeft) / itemWidth) * 100); const translateY = Math.round(((e.pageY - itemTop) / itemHt) * 100); zoomItem.style.transformOrigin = `${translateX}% ${translateY}%`; }); } } } customElements.define("vertical-slider-component", VerticalSliderComponent); |
Step #5. Find the "VariantSelect",
Under it find the updateMedia function. Before the window.setTimeout, insert the code below. Please refer to the image for placement.
| this.verticalSlider = document.querySelector('vertical-slider-component'); this.verticalSlider.updateThumb(newMedia); |

Step #6. Save the changes
Once you've added the code, save your changes to the theme code by clicking the SAVE button on the right hand upper corner.
That's it (",). If you have questions, feel free to contact me by using the "Chat with us."
Conclusion
Implementing a vertical slider in Shopify, particularly for Dawn theme versions 2.5 and below, offers a compelling way to showcase products or other content in a visually engaging and space-saving manner. By utilizing a vertical scrolling format, you can maximize the use of screen real estate, especially on mobile devices, and capture visitors' attention with dynamic transitions. While custom coding is possible, leveraging a JavaScript library like Slick or Swiper is highly recommended for ease of implementation and access to pre-built features. Remember to prioritize mobile responsiveness, visual appeal, and smooth transitions to create a seamless and effective vertical slider that enhances the user experience and contributes to a more modern and engaging online store.
3 comments
Hi @XJ,
Sorry I do not get the question.
Hi @Melissa,
Please message me at “Chat with us”, I need your website to be able to assist you
When a variant is selected (the header link has a variant id), and then go to refresh, the attribute is black and the image is red.
Hi, I was able to successfully create a vertical product slider thanks to your help! However, I was wondering if there’s a way to make the height of all of the product images the same, so that when I scroll through them on my phone, they don’t vary so much in height?