Blog Articles
the blog image
Enhancing Shopify Dawn Theme with Scrolling Collections

Technical Guide & Strategic UX Design

Scrolling collection lists offer a dynamic way to showcase products while improving user engagement. Building on our previous guide for scrolling text banners, this tutorial focuses on implementing horizontal scroll behavior for collections in Shopify’s Dawn theme, optimized for modern eCommerce needs.

Why Scrolling Collections Drive Conversions

  1. 38% Longer Session Times: Stores using horizontal scroll report increased browsing depth compared to static grids.

  2. Mobile-First Design: 72% of users find horizontal swiping more intuitive than pagination on mobile devices.

  3. Visual Storytelling: Curated collection scrolling enables thematic merchandising (e.g., "Summer Essentials" → "Back-to-School Picks").

Strategic Use Cases:

  • New Arrivals Carousel: Auto-scroll latest collections.

  • Category Highlights: Horizontal navigation between product types.

  • Trending Now: Algorithm-driven collection displays.

Technical Implementation

Before making any code modifications, always ensure you create a copy of the theme first. Test all your changes on this copy to confirm everything works perfectly before publishing it to the live theme. This precaution helps prevent any issues from affecting your main theme and keeps your work safe.

Step 1: Create Collection Template

In the sections folder, create a new file named collection-list-marquee.liquid:

{{ 'section-collection-list.css' | asset_url | stylesheet_tag }}
{{ 'component-card.css' | asset_url | stylesheet_tag }}
{{ 'component-slider.css' | asset_url | stylesheet_tag }}

{%- style -%}
  .section-{{ section.id }}-padding {
    padding-top: {{ section.settings.padding_top | times: 0.75 | round: 0 }}px;
    padding-bottom: {{ section.settings.padding_bottom | times: 0.75 | round: 0 }}px;
  }

  @media screen and (min-width: 750px) {
    .section-{{ section.id }}-padding {
      padding-top: {{ section.settings.padding_top }}px;
      padding-bottom: {{ section.settings.padding_bottom }}px;
    }
  }
  
    @media screen and (min-width: 990px) {
  .grid--6-col-desktop .grid__item {
        width: calc(8.33% - var(--grid-desktop-horizontal-spacing)* 11 / 12);
        max-width: calc(8.33% - var(--grid-desktop-horizontal-spacing)* 11 / 12);
    }
  .grid--5-col-desktop .grid__item {
        width: calc(10% - var(--grid-desktop-horizontal-spacing)* 9 / 10);
        max-width: calc(10% - var(--grid-desktop-horizontal-spacing)* 9 / 10);
    }
  .grid--4-col-desktop .grid__item {
        width: calc(12.5% - var(--grid-desktop-horizontal-spacing)* 7 / 8);
        max-width: calc(12.5% - var(--grid-desktop-horizontal-spacing)* 7 / 8);
    }
  .grid--3-col-desktop .grid__item {
        width: calc(16.66% - var(--grid-desktop-horizontal-spacing)* 5 / 6);
        max-width: calc(16.66% - var(--grid-desktop-horizontal-spacing)* 5 / 6);
    }
  }
{%- endstyle -%}

{%- liquid
  assign columns_mobile_int = section.settings.columns_mobile | plus: 0
  assign show_mobile_slider = false
  if section.settings.swipe_on_mobile and section.blocks.size > columns_mobile_int
    assign show_mobile_slider = true
  endif
-%}

<div class="color-{{ section.settings.color_scheme }} gradient">
  <div class="collection-marquee collection-list-wrapper page-width isolate{% if show_mobile_slider %} page-width-desktop{% endif %}{% if section.settings.title == blank %} no-heading{% endif %}{% if section.settings.show_view_all == false or section.blocks.size > collections.size %} no-mobile-link{% endif %} section-{{ section.id }}-padding">
    {%- unless section.settings.title == blank -%}
      <div class="title-wrapper-with-link{% if show_mobile_slider %} title-wrapper--self-padded-tablet-down{% else %} title-wrapper--self-padded-mobile{% endif %} title-wrapper--no-top-margin">
        <h2
          id="SectionHeading-{{ section.id }}"
          class="collection-list-title inline-richtext {{ section.settings.heading_size }}{% if settings.animations_reveal_on_scroll %} scroll-trigger animate--slide-in{% endif %}"
        >
          {{ section.settings.title }}
        </h2>

        {%- if section.settings.show_view_all and show_mobile_slider -%}
          <a
            href="{{ routes.collections_url }}"
            id="ViewAll-{{ section.id }}"
            class="link underlined-link large-up-hide{% if settings.animations_reveal_on_scroll %} scroll-trigger animate--slide-in{% endif %}"
            aria-labelledby="ViewAll-{{ section.id }} SectionHeading-{{ section.id }}"
          >
            {{- 'sections.collection_list.view_all' | t -}}
          </a>
        {%- endif -%}
      </div>
    {%- endunless -%}

    <slider-component class="slider-mobile-gutter{% if settings.animations_reveal_on_scroll %} scroll-trigger animate--slide-in{% endif %}">
      <ul
        class="collection-list contains-card contains-card--collection{% if settings.card_style == 'standard' %} contains-card--standard{% endif %} grid grid--{{ section.settings.columns_desktop }}-col-desktop grid--{{ section.settings.columns_mobile }}-col-tablet-down{% if section.settings.swipe_on_mobile %} slider slider--tablet grid--peek{% endif %} collection-list--{{ section.blocks.size }}-items"
        id="Slider-{{ section.id }}"
        role="list"
      >
        {%- liquid
          assign columns = section.blocks.size
          if columns > 3
            assign columns = 3
          endif
        -%}
        {%- for block in section.blocks -%}
          <li
            id="Slide-{{ section.id }}-{{ forloop.index }}"
            class="collection-list__item grid__item{% if show_mobile_slider %} slider__slide{% endif %}{% if block.settings.collection.featured_image == nil %} collection-list__item--no-media{% endif %}{% if settings.animations_reveal_on_scroll %} scroll-trigger animate--slide-in{% endif %}"
            {{ block.shopify_attributes }}
            {% if settings.animations_reveal_on_scroll %}
              data-cascade
              style="--animation-order: {{ forloop.index }};"
            {% endif %}
          >
            {%- assign placeholder_image_index = forloop.index0 | modulo: 4 | plus: 1 -%}
            {%- assign placeholder_image = 'collection-apparel-' | append: placeholder_image_index -%}
            {% render 'card-collection',
              card_collection: block.settings.collection,
              media_aspect_ratio: section.settings.image_ratio,
              columns: columns,
              placeholder_image: placeholder_image
            %}
          </li>
        {%- endfor -%}
                {%- for block in section.blocks -%}
          <li
            id="Slide-{{ section.id }}-{{ forloop.index }}-1"
            class="collection-list__item desktop-only grid__item{% if show_mobile_slider %} slider__slide{% endif %}{% if block.settings.collection.featured_image == nil %} collection-list__item--no-media{% endif %}{% if settings.animations_reveal_on_scroll %} scroll-trigger animate--slide-in{% endif %}"
            {{ block.shopify_attributes }}
            {% if settings.animations_reveal_on_scroll %}
              data-cascade
              style="--animation-order: {{ forloop.index }};"
            {% endif %}
          >
            {%- assign placeholder_image_index = forloop.index0 | modulo: 4 | plus: 1 -%}
            {%- assign placeholder_image = 'collection-apparel-' | append: placeholder_image_index -%}
            {% render 'card-collection',
              card_collection: block.settings.collection,
              media_aspect_ratio: section.settings.image_ratio,
              columns: columns,
              placeholder_image: placeholder_image
            %}
          </li>
        {%- endfor -%}
      </ul>
      {%- if show_mobile_slider -%}
        <div class="slider-buttons">
          <button
            type="button"
            class="slider-button slider-button--prev"
            name="previous"
            aria-label="{{ 'general.slider.previous_slide' | t }}"
          >
           {{- 'icon-caret.svg' | inline_asset_content -}}
          </button>
          <div class="slider-counter caption">
            <span class="slider-counter--current">1</span>
            <span aria-hidden="true"> / </span>
            <span class="visually-hidden">{{ 'general.slider.of' | t }}</span>
            <span class="slider-counter--total">{{ section.blocks.size }}</span>
          </div>
          <button
            type="button"
            class="slider-button slider-button--next"
            name="next"
            aria-label="{{ 'general.slider.next_slide' | t }}"
          >
            {{- 'icon-caret.svg' | inline_asset_content -}}
          </button>
        </div>
      {%- endif -%}
    </slider-component>

    {%- if section.settings.show_view_all and section.blocks.size < collections.size -%}
      <div
        class="center collection-list-view-all{% if show_mobile_slider %} small-hide medium-hide{% endif %}{% if settings.animations_reveal_on_scroll %} scroll-trigger animate--slide-in{% endif %}"
        {% if settings.animations_reveal_on_scroll %}
          data-cascade
        {% endif %}
      >
        <a
          href="{{ routes.collections_url }}"
          class="button"
          id="ViewAllButton-{{ section.id }}"
          aria-labelledby="ViewAllButton-{{ section.id }} SectionHeading-{{ section.id }}"
        >
          {{- 'sections.collection_list.view_all' | t -}}
        </a>
      </div>
    {%- endif -%}
  </div>
</div>

{% schema %}
{
  "name": "Collection list marqueen",
  "tag": "section",
  "class": "section section-collection-list",
  "max_blocks": 15,
  "disabled_on": {
    "groups": ["header", "footer"]
  },
  "settings": [
    {
      "type": "inline_richtext",
      "id": "title",
      "default": "t:sections.collection-list.settings.title.default",
      "label": "t:sections.collection-list.settings.title.label"
    },
    {
      "type": "select",
      "id": "heading_size",
      "options": [
        {
          "value": "h2",
          "label": "t:sections.all.heading_size.options__1.label"
        },
        {
          "value": "h1",
          "label": "t:sections.all.heading_size.options__2.label"
        },
        {
          "value": "h0",
          "label": "t:sections.all.heading_size.options__3.label"
        },
        {
          "value": "hxl",
          "label": "t:sections.all.heading_size.options__4.label"
        },
        {
          "value": "hxxl",
          "label": "t:sections.all.heading_size.options__5.label"
        }
      ],
      "default": "h1",
      "label": "t:sections.all.heading_size.label"
    },
    {
      "type": "select",
      "id": "image_ratio",
      "options": [
        {
          "value": "adapt",
          "label": "t:sections.collection-list.settings.image_ratio.options__1.label"
        },
        {
          "value": "portrait",
          "label": "t:sections.collection-list.settings.image_ratio.options__2.label"
        },
        {
          "value": "square",
          "label": "t:sections.collection-list.settings.image_ratio.options__3.label"
        }
      ],
      "default": "square",
      "label": "t:sections.collection-list.settings.image_ratio.label",
      "info": "t:sections.collection-list.settings.image_ratio.info"
    },
    {
      "type": "range",
      "id": "columns_desktop",
      "min": 1,
      "max": 6,
      "step": 1,
      "default": 3,
      "label": "t:sections.collection-list.settings.columns_desktop.label"
    },
    {
      "type": "color_scheme",
      "id": "color_scheme",
      "label": "t:sections.all.colors.label",
      "info": "t:sections.all.colors.has_cards_info",
      "default": "scheme-1"
    },
    {
      "type": "checkbox",
      "id": "show_view_all",
      "default": false,
      "label": "t:sections.collection-list.settings.show_view_all.label"
    },
    {
      "type": "header",
      "content": "t:sections.collection-list.settings.header_mobile.content"
    },
    {
      "type": "select",
      "id": "columns_mobile",
      "options": [
        {
          "value": "1",
          "label": "t:sections.collection-list.settings.columns_mobile.options__1.label"
        },
        {
          "value": "2",
          "label": "t:sections.collection-list.settings.columns_mobile.options__2.label"
        }
      ],
      "default": "1",
      "label": "t:sections.collection-list.settings.columns_mobile.label"
    },
    {
      "type": "checkbox",
      "id": "swipe_on_mobile",
      "default": false,
      "label": "t:sections.collection-list.settings.swipe_on_mobile.label"
    },
    {
      "type": "header",
      "content": "t:sections.all.padding.section_padding_heading"
    },
    {
      "type": "range",
      "id": "padding_top",
      "min": 0,
      "max": 100,
      "step": 4,
      "unit": "px",
      "label": "t:sections.all.padding.padding_top",
      "default": 36
    },
    {
      "type": "range",
      "id": "padding_bottom",
      "min": 0,
      "max": 100,
      "step": 4,
      "unit": "px",
      "label": "t:sections.all.padding.padding_bottom",
      "default": 36
    }
  ],
  "blocks": [
    {
      "type": "featured_collection",
      "name": "t:sections.collection-list.blocks.featured_collection.name",
      "settings": [
        {
          "type": "collection",
          "id": "collection",
          "label": "t:sections.collection-list.blocks.featured_collection.settings.collection.label"
        }
      ]
    }
  ],
  "presets": [
    {
      "name": "t:sections.collection-list.presets.name",
      "blocks": [
        {
          "type": "featured_collection"
        },
        {
          "type": "featured_collection"
        },
        {
          "type": "featured_collection"
        }
      ]
    }
  ]
}
{% endschema %}

<style>
  .desktop-only {
    display: none;
  }
@media screen and (min-width: 990px) {
    .collection-marquee .slider-mobile-gutter {
    overflow:hidden;
  }
  .collection-marqueer .slider-mobile-gutter ul {
    animation: marquee 18s linear infinite;
    width: 200%;
  }
  .collection-marquee .slider-mobile-gutter ul li {
    float: left;
  }
  .desktop-only {
    display: block;
  }
  @keyframes marquee {
			from {
				transform: translateX( 0% );
			}
			to {
				transform: translateX( -50% );
			}
  }
}

</style>

Step 2: Add to Theme

  1. In Shopify Admin:

    1. Navigate to Online Store > Themes > Customize.

    2. Add the "Collection Scroll" section to your homepage or collection templates.

Note: This currently supports only 3 to 6 collections.

Conclusion

By integrating scrolling collections into the Dawn theme, eCommerce stores can showcase a greater variety of products and encourage deeper engagement. Following the outlined steps allows merchants to create a dynamic and visually appealing storefront that enhances product discovery.

ByShinetech Changchun

Thu Feb 06 2025
the blog image
Enhancing Shopify Dawn Theme with Scrolling Text

Technical Guide & Strategic Implementation

Scrolling text marquees have become a powerful tool for eCommerce stores to communicate urgent messages and boost conversions. This technical guide demonstrates how to implement a scrolling text feature in Shopify’s Dawn theme while exploring strategic use cases and optimization tactics.

Why Scrolling Text Matters in eCommerce

  1. 72% Conversion Lift: Stores using scrolling marquees report an average 72% increase in conversion rates by highlighting time-sensitive offers.

  2. Mobile Optimization: With 65% of Shopify traffic from mobile devices, compact scrolling text efficiently utilizes limited screen space.

  3. Attention Retention: Animated elements capture 3x more visual attention than static text

Common business scenarios include:

  • Flash Sales: "24-HOUR SALE: 50% OFF ALL OUTERWEAR – USE CODE HURRICANE"

  • Shipping Alerts: "FREE EXPRESS SHIPPING FOR ORDERS ABOVE $100"

  • New Launches: "JUST IN: SUMMER 2025 COLLECTION – SHOP NOW"

  • Trust Badges: "⭐ 4.9/5 RATED BY 15K CUSTOMERS | 30-DAY RETURNS"

Implementation Steps

Before making any code modifications, always ensure you create a copy of the theme first. Test all your changes on this copy to confirm everything works perfectly before publishing it to the live theme. This precaution helps prevent any issues from affecting your main theme and keeps your work safe.

Step 1: Create Liquid Template

In your theme code editor, create a new file named my-marquee.liquid in the sections folder:

<link rel="stylesheet" href="{{ 'section-scrolling-text.min.css' | asset_url }}" media="all">
{%- assign get = section.settings -%}
{% style %}
    .ccpro-scrolling-text#section_{{ section.id }} {
        background: {{ get.background_color }};
        color: {{ get.text_color }};
        padding-top: {{ get.spacing_top_mobile }}px;
        padding-bottom: {{ get.spacing_bottom_mobile }}px;
        --text-font-weight: {{ get.text_font_weight }};
        font-size: {{ get.text_size_mobile }}px;
        --marquee-speed: {{ get.speed_mobile }}s;
        --icon-size: {{ get.icon_size_mobile }}px;
    }

    @media (min-width:992px) {
        .ccpro-scrolling-text#section_{{ section.id }} {
            padding-top: {{ get.spacing_top_desktop }}px;
            padding-bottom: {{ get.spacing_bottom_desktop }}px;
            font-size: {{ get.text_size_desktop }}px;
            --marquee-speed: {{ get.speed_desktop }}s;
            --icon-size: {{ get.icon_size_desktop }}px;
        }
    }


{% endstyle %}

<div class="ccpro-scrolling-text" id="section_{{ section.id }}">
    <div class="{% if get.enable_container %}page-width{% endif %}">
        <div class="ccpro-st__inner ccpro-st__direction-{{ get.direction }}">

            {% for i in (1..5) %}
            <div>
                {%- for block in section.blocks -%}
                    {%- assign get = block.settings -%}
                    {%- if get.text != blank or get.selected_icon != blank -%}
                        <div class="ccpro-st__item" {{ block.shopify_attributes }}>

                            {%- if get.selected_icon != blank -%}
                            <div class="ccpro-st__item-icon">
                                <div class="ccpro-aspect-ratio" style="padding-top:{{ 1 | divided_by: get.selected_icon.aspect_ratio | times: 30 }}%">
                                  {{ get.selected_icon | image_url: width: 18, height:18 | image_tag  }}
                                </div>
                            </div>
                            {%- endif -%}

                            {%- if get.text != blank -%}
                            <span class="ccpro-st__item-text">{{ get.text }}</span>
                            {%- endif -%}

                        </div>
                        {%- endif -%}
                {%- endfor -%}
            </div>
            {% endfor %}

        </div>
    </div>
</div>

{% schema %}
{
  "name": "Scrolling Annoucement Bar",
  "settings": [
    {
        "type": "paragraph",
        "content": "Subscribe our channel [Shinetech ChangChun](https:\/\/youtube.com\/@ShinetechShopidev?sub_confirmation=1)"
    },
    {
        "type": "color",
        "id": "background_color",
        "label": "Background Color",
        "default": "#fff"
    },
    {
        "type": "color",
        "id": "text_color",
        "label": "Text Color",
        "default": "#000"
    },
    {
        "type": "checkbox",
        "id": "enable_container",
        "label": "Enable Container",
        "default": true
    },
    {
        "type": "header",
        "content": "Texts"
    },
    {
        "type": "range",
        "id": "text_font_weight",
        "label": "Font Weight",
        "min": 100,
        "max": 900,
        "step": 100,
        "default": 500
    },
    {
        "type": "range",
        "id": "text_size_desktop",
        "label": "Text Size (Desktop)",
        "unit": "px",
        "min": 10,
        "max": 200,
        "step": 2,
        "default": 48
    },
    {
        "type": "range",
        "id": "text_size_mobile",
        "label": "Text Size (Mobile)",
        "unit": "px",
        "min": 8,
        "max": 200,
        "step": 2,
        "default": 32
    },
    {
        "type": "header",
        "content": "Icon/Logo Size"
    },
    {
        "type": "range",
        "id": "icon_size_desktop",
        "label": "Size (Desktop)",
        "unit": "px",
        "min": 0,
        "max": 200,
        "step": 2,
        "default": 36
    },
    {
        "type": "range",
        "id": "icon_size_mobile",
        "label": "Size (Mobile)",
        "unit": "px",
        "min": 0,
        "max": 100,
        "step": 1,
        "default": 25
    },
    {
        "type": "select",
        "id": "direction",
        "label": "Direction",
        "options": [
            {
                "value": "left",
                "label": "Left"
            },
            {
                "value": "right",
                "label": "Right"
            }
        ]
    },
    {
        "type": "range",
        "id": "speed_desktop",
        "label": "Speed (Desktop)",
        "unit": "s",
        "min": 1,
        "max": 100,
        "step": 1,
        "default": 10
    },
    {
        "type": "range",
        "id": "speed_mobile",
        "label": "Speed (Mobile)",
        "unit": "s",
        "min": 1,
        "max": 100,
        "step": 1,
        "default": 10
    },
    {
        "type": "header",
        "content": "Spacing"
    },
    {
        "type": "range",
        "id": "spacing_top_desktop",
        "label": "Spacing Top (Desktop)",
        "unit": "px",
        "min": 0,
        "max": 200,
        "step": 5,
        "default": 10
    },
    {
        "type": "range",
        "id": "spacing_bottom_desktop",
        "label": "Spacing Bottom (Desktop)",
        "unit": "px",
        "min": 0,
        "max": 200,
        "step": 5,
        "default": 10
    },
    {
        "type": "range",
        "id": "spacing_top_mobile",
        "label": "Spacing Top (Mobile)",
        "unit": "px",
        "min": 0,
        "max": 200,
        "step": 5,
        "default": 10
    },
    {
        "type": "range",
        "id": "spacing_bottom_mobile",
        "label": "Spacing Bottom (Mobile)",
        "unit": "px",
        "min": 0,
        "max": 200,
        "step": 5,
        "default": 10
    }
  ],
  "blocks": [
    {
        "type": "text",
        "name": "Text",
        "settings": [
            {
                "type": "image_picker",
                "id": "selected_icon",
                "label": "Select Icon",
                "info": "Recommended to have consistent Icon/Logo dimension"
            },
            {
                "type": "inline_richtext",
                "id": "text",
                "label": "Content",
                "default": "Add campaign text here."
            }
        ]
    }
  ],
  "presets": [
    {
      "name": "Scrolling Annoucement Bar",
      "blocks": [
        {
            "type": "text",
            "settings": {
                "text": "Add your text here."
            }
        },
        {
            "type": "text",
            "settings": {
                "text": "Add your text here."
            }
        },
        {
            "type": "text",
            "settings": {
                "text": "Add your text here."
            }
        }
      ]
    }
  ]
}
{% endschema %}

Step 2: Add CSS Animation

Create a new file named section-scrolling-text.min.css in the assets folder:

.ccpro-scrolling-text .ccpro-st__inner{display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center;white-space:nowrap;font-size:inherit;font-weight:bold;overflow:hidden;position:relative;z-index:2}
.ccpro-scrolling-text .ccpro-st__inner>div{display:-webkit-flex;display:flex;-webkit-align-items:center;align-items:center;-webkit-flex-shrink:0;flex-shrink:0;-webkit-animation:marqueeLeft var(--marquee-speed) linear infinite;
                                         animation:marqueeLeft var(--marquee-speed) linear infinite}
.ccpro-scrolling-text .ccpro-st__inner.ccpro-st__direction-right>div{-webkit-animation:marqueeRight var(--marquee-speed) linear infinite;animation:marqueeRight var(--marquee-speed) linear infinite}
.ccpro-scrolling-text .ccpro-st__item{pointer-events:none;display:-webkit-inline-flex;display:inline-flex;-webkit-align-items:center;align-items:center;padding:0 30px}
.ccpro-scrolling-text .ccpro-st__item span{transition:all .25s cubic-bezier(0.104,0.204,0.492,1)}.ccpro-scrolling-text .ccpro-st__item-icon{width:var(--icon-size);margin-right:10px}
.ccpro-scrolling-text .ccpro-st__item-text{font-weight:var(--text-font-weight)}.ccpro-scrolling-text .ccpro-st__item-text a{color:inherit}

    @keyframes marqueeLeft {
    from {
      -webkit-transform:translateX(0);
      transform: translateX(0);
    }
    to {
      -webkit-transform:translateX(-100%);
      transform: translateX(-100%);
    }
  }

      @keyframes marqueeRight {
    from {
      -webkit-transform:translateX(-100%);
      transform: translateX(-100%);
    }
    to {
       -webkit-transform:translateX(0);
      transform: translateX(0);
    }
  }

Step 3: Enable in Theme Editor

  1. In Shopify Admin, navigate to Online Store > Themes > Customize.

  2. Add your new "Custom Marquee" section to the header or product template.

Strategic Optimization Tips

  1. Speed & Readability:

    • Optimal duration: 15-25 seconds per cycle.

    • Contrast ratio ≥ 4.5:1 for accessibility.

  2. Content Best Practices:

    • Keep messages under 120 characters.

    • Use emojis sparingly: 🚚 Free Shipping | 🔥 50% Off

Final Thoughts

By integrating scrolling text into the Dawn theme, eCommerce stores can effectively communicate critical messages and drive higher engagement. Following the steps in this guide enables merchants to create urgency and improve customer interaction, similar to successful strategies employed by top industry players. For those who prefer a more straightforward approach, public apps in the Shopify app store offer easy-to-use alternatives that still align with brand aesthetics and functionality.

ByShinetech Changchun

Thu Feb 06 2025
the blog image
How to Add a Custom Message to Your Packing Slip Template

Background

Adding a custom message to your packing slip template can significantly enhance your customer's shopping experience. This feature allows your customers to leave a note for a product specifically, sharing their requirements with you. This is especially helpful if you provide customization services such as engraving on a product, for example, on a pen or a box. Or it can be used to include gift messages, special handling instructions, or dietary preferences if you're selling consumable goods. The idea originated from a Shopify community post where users discussed ways to show their order note on the packing slips to better satisfy the customers.

Step-by-Step Guide

Before making any code modifications, always ensure you create a copy of the theme first. Test all your changes on this copy to confirm everything works perfectly before publishing it to the live theme. This precaution helps prevent any issues from affecting your main theme and keeps your work safe.

  1. Add the Custom Message Field to Your Product Page:

    • Open your product page's code, typically found in main-product.liquid.

    • Insert the following code snippet to add a custom message field:

                        <p class="line-item-property__field">
                          <label for="message">Message</label>
                          <input id="message" type="text" name="properties[message]"  form="{{ product_form_id }}">
                        </p>
  2. Edit Your Packing Slip Template:

    • Go to your Shopify admin panel.

    • Navigate to Settings > Shipping and Delivery.

    • Click on Edit Packing Slip Template.

    • Paste the following code into the template:

                  {%- for property in line_item.properties -%}
                    {%- assign property_first_char = property.first | slice: 0 -%}
                    {%- if property.last != blank and property_first_char != '_' -%}
                      <span class="line-item-description-line">
                        {{ property.first }}:{{ property.last }}
                      </span>
                    {%- break -%}
                    {%- endif -%}
                  {%- endfor -%}

By following these steps, you can successfully add a custom message to your packing slip template, enhancing your customer's experience and reinforcing your brand identity.

Additional Examples:

  • Gift Messages: Allow customers to add personalized notes when sending gifts.

  • Special Handling Instructions: Useful for fragile items or specific delivery requirements.

  • Dietary Preferences: For businesses selling food products, allow customers to specify any dietary restrictions or preferences.

Implementing this feature can increase customer satisfaction and loyalty by providing a more personalized shopping experience. Happy coding!

ByShinetech Changchun

Sun Jan 26 2025
the blog image
Adding Universally Editable Text in Shopify

Personalizing your Shopify store can significantly impact customer engagement and user experience. Store owners may also want to display a piece of text in multiple places on the website and be able to edit it easily together. This guide will show you how to add a custom field to your theme settings, make it editable via the Shopify theme editor, and display the custom text on your pages—all without hard-coding changes that would be difficult to update later.

Why It's Needed

Customizing the content on your Shopify store’s pages can serve many purposes. Whether you want to give personalized instructions, provide welcoming messages, or include additional information, being able to edit this text directly from the theme editor all at once makes it much easier to manage. This flexibility is particularly useful for store owners who may not be comfortable with coding but still want to maintain control over their site’s content.

In our example below, we will add a piece of text to the login page of Shopify. Please note that you need to set your customer account to legacy mode, as the new customer account page does not allow editing.

Steps to Implement

Before making any code modifications, always ensure you create a copy of the theme first. Test all your changes on this copy to confirm everything works perfectly before publishing it to the live theme. This precaution helps prevent any issues from affecting your main theme and keeps your work safe.

Step 1: Add the Custom Field to the Theme Settings

First, we need to add a custom field to the theme settings. This field will allow you to edit the login page text from the Shopify admin panel. Here's how to do it:

  1. Navigate to the Online Store: In your Shopify Admin, go to Online Store.

  2. Customize Your Theme: Under Themes, click Customize next to the Dawn theme (or your active theme).

  3. Open Theme Settings: In the theme editor, locate Theme settings at the bottom left.

  4. Check for Existing Options: Look for options related to Accounts or Login. If the field for the text you want to edit doesn't exist, you'll need to create a new custom setting.

To do this, we will edit the settings_schema.json file.

Step 2: Add a Custom Field in settings_schema.json

You need to add a custom field in the settings_schema.json file to make the text editable from the admin panel. Follow these steps:

  1. Edit the Code: In your Shopify Admin, go to Online Store > Themes > Actions > Edit Code.

  2. Locate the File: Open the config/settings_schema.json file.

  3. Add the Custom Setting: Add a new setting for your custom text using the following structure:

{
"name": "Login Page Text",
"settings": [
{
"type": "textarea",
"label": "Custom Text for Login Page",
"id": "login_page_text",
"default": "Enter your credentials and select the 'Sign In' button to access your account.",
"info": "This text will be displayed on the login page."
}
]
}

Step 3: Update the main-login.liquid File to Display the Custom Text

Next, you need to update the main-login.liquid file to display the custom text. Here’s how:

  1. Open the Liquid File: In the theme editor, find and open the main-login.liquid file (or the relevant Liquid file where you want the text to appear).

  2. Insert the Custom Text: Use the following code to pull the custom text from the settings:

<p>
{{ settings.login_page_text }}
</p>

Step 4: Customize the Text in Shopify Admin

Finally, after adding the setting and updating the Liquid file, you will be able to change the text from the Shopify Admin. Here are the steps:

  1. Access the Theme Settings: Go to Online Store > Themes.

  2. Customize the Theme: Click Customize next to your theme.

  3. Find the Custom Text Field: Go to Theme Settings (bottom-left corner) and look for the Custom Text for Login Page field under Accounts or Login settings.

  4. Edit and Save: Edit the text as needed and save your changes.

Now, you can easily modify the login page text directly from the Shopify Admin without needing to dive back into the code each time you want to make a change. Additionally, you can add this piece of text to other places on the site by repeating step 3.

ByShinetech Changchun

Mon Jan 20 2025
the blog image
Optimize Dawn: Image Banners for Every Device

As more people use their phones for online shopping, providing a responsive user experience is more critical than ever. In today’s mobile-first world, optimizing your website for various devices isn't just a bonus—it's a necessity. One key feature that can enhance the user experience on your e-commerce site is displaying different image banners based on the device being used. Different devices have different screen sizes, resolutions, and user interactions. By tailoring your image banners for specific devices, you create a seamless and engaging experience, which can lead to higher engagement rates and increased conversions. This approach keeps your site visually appealing and functional, whether your visitors are using a mobile phone, tablet, or desktop.

In addition, using different image sizes for different screens can improve your site's performance, especially on mobile devices, as smaller images load faster. This not only enhances the overall user experience but also contributes to better site performance.

In this blog, we’ll walk you through the steps to show different image banners on different devices for the Dawn theme in Shopify.

Steps to Add Device-Specific Image Banners

Before making any code modifications, always ensure you create a copy of the theme first. Test all your changes on this copy to confirm everything works perfectly before publishing it to the live theme. This precaution helps prevent any issues from affecting your main theme and keeps your work safe.

  1. Add Two Image Banner Sections to Your Homepage

First, you'll need to add two image banner sections to your homepage. This allows you to create and customize banners specifically designed for desktop and mobile views.

  1. Preview the Theme and Note the Section IDs

Next, preview your theme and make a note of the section IDs. The section IDs are unique identifiers for each section in your theme, and you'll need them to specify which banners should be shown on which devices.

  1. Edit Your theme.liquid File

Now, open your theme.liquid file. This file controls the overall layout and structure of your theme. Scroll to the bottom of the file and add the following code snippet. Ensure you replace your_section_id with the actual section IDs you noted earlier.

    <style>
    @media screen and (min-width: 751px) {
         #shopify-section-template--23119250981154__image_banner {
            display: none
          }     
      }
    @media screen and (max-width: 750px) {
         #shopify-section-template--23119250981154__image_banner_PJ3nfy {
            display: none
          }     
      }

    </style>

Note: It's crucial to replace your_section_id_desktop and your_section_id_mobile with the corresponding section IDs for your desktop and mobile banners.

By following these steps, you can enhance your e-commerce site’s responsiveness and visual appeal, ensuring that your customers enjoy a seamless browsing experience regardless of the device they use. This small tweak can make a big difference in user satisfaction and engagement, ultimately driving more conversions and sales for your online store.

ByShinetech Changchun

Wed Jan 15 2025
the blog image
Shopify Tutorial: How to Display Only Selected Variant Images

Recently, a Shopify store owner approached us with a specific requirement: they wanted to showcase only the images of the selected variant in their product gallery. This need arose from having multiple variants for a single product, each with its own set of images. By displaying only the relevant images, the store owner aimed to streamline the user experience, allowing customers to focus on the variant they were interested in without distraction.

Benefits of Displaying Only Selected Variant Images

Implementing this feature can significantly enhance your store's functionality and user experience. Here are some key advantages:

  • Improved User Experience: Customers can easily view and compare images of the selected variant without being overwhelmed by irrelevant images.

  • Enhanced Visual Appeal: A cleaner and more organized product gallery makes your store visually appealing, encouraging customers to explore further.

  • Increased Conversion Rates: Simplifying product presentation helps customers make informed purchasing decisions, potentially boosting conversion rates.

Steps to Implement Selected Variant Images in Shopify

Before making any code modifications, always ensure you create a copy of the theme first. Test all your changes on this copy to confirm everything works perfectly before publishing it to the live theme. This precaution helps prevent any issues from affecting your main theme and keeps your work safe.

To display only the images of the selected variant in your Shopify store, follow these straightforward steps:

  1. Access Your Theme Code Editor: Log into your Shopify admin panel and navigate to "Online Store" > "Themes." Click on "Actions" and select "Edit code" for your active theme.

  2. Modify sections/main-product.liquid: Open the sections/main-product.liquid file. Add below code to the file.

    ,
        {
          "type": "header",
          "content": "Selected Variant Images"
        },
        {
            "type": "paragraph",
            "content": "Subscribe our channel [Shinetech ChangChun](https:\/\/youtube.com\/@ShinetechShopidev?sub_confirmation=1)"
        },
        {
          "type": "checkbox",
          "id": "enable_group_media",
          "label": "Enable Selected Variant Images",
          "default": false
        }
  3. Update snippets/product-media-gallery.liquid: Locate and open the snippets/product-media-gallery.liquid file. Replace the existing code with code below.

    {% comment %}
      Renders a product media gallery. Should be used with 'media-gallery.js'
      Also see 'product-media-modal'
    
      Accepts:
      - product: {Object} Product liquid object
      - variant_images: {Array} Product images associated with a variant
      - limit: {Number} (optional) When passed, limits the number of media items to render
    
      Usage:
      {% render 'product-media-gallery' %}
    {% endcomment %}
    
    {%- liquid
      if section.settings.hide_variants and variant_images.size == product.media.size
        assign single_media_visible = true
      endif
    
      if limit == 1
        assign single_media_visible = true
      endif
    
      assign media_count = product.media.size
      if section.settings.hide_variants and media_count > 1 and variant_images.size > 0
        assign media_count = media_count | minus: variant_images.size | plus: 1
      endif
    
      if media_count == 1 or single_media_visible
        assign single_media_visible_mobile = true
      endif
    
      if media_count == 0 or single_media_visible_mobile or section.settings.mobile_thumbnails == 'show' or section.settings.mobile_thumbnails == 'columns' and media_count < 3
        assign hide_mobile_slider = true
      endif
    
      if section.settings.media_size == 'large'
        assign media_width = 0.65
      elsif section.settings.media_size == 'medium'
        assign media_width = 0.55
      elsif section.settings.media_size == 'small'
        assign media_width = 0.45
      endif
    -%}
    
    <media-gallery
      id="MediaGallery-{{ section.id }}"
      role="region"
      {% if section.settings.enable_sticky_info %}
        class="product__column-sticky"
      {% endif %}
      aria-label="{{ 'products.product.media.gallery_viewer' | t }}"
      data-desktop-layout="{{ section.settings.gallery_layout }}"
    >
      <div id="GalleryStatus-{{ section.id }}" class="visually-hidden" role="status"></div>
      <slider-component id="GalleryViewer-{{ section.id }}" class="slider-mobile-gutter">
        <a class="skip-to-content-link button visually-hidden quick-add-hidden" href="#ProductInfo-{{ section.id }}">
          {{ 'accessibility.skip_to_product_info' | t }}
        </a>
        <ul
          id="Slider-Gallery-{{ section.id }}"
          class="product__media-list contains-media grid grid--peek list-unstyled slider slider--mobile"
          role="list"
        >
          {%- if product.selected_or_first_available_variant.featured_media != null -%}
            {%- assign featured_media = product.selected_or_first_available_variant.featured_media -%}
            <li
              id="Slide-{{ section.id }}-{{ featured_media.id }}"
              class="product__media-item grid__item slider__slide is-active{% if single_media_visible %} product__media-item--single{% endif %}{% if featured_media.media_type != 'image' %} product__media-item--full{% endif %}{% if settings.animations_reveal_on_scroll %} scroll-trigger animate--fade-in{% endif %}"
              data-media-id="{{ section.id }}-{{ featured_media.id }}"
            >
              {%- assign media_position = 1 -%}
              {% render 'product-thumbnail',
                media: featured_media,
                media_count: media_count,
                position: media_position,
                desktop_layout: section.settings.gallery_layout,
                mobile_layout: section.settings.mobile_thumbnails,
                loop: section.settings.enable_video_looping,
                modal_id: section.id,
                xr_button: true,
                media_width: media_width,
                media_fit: section.settings.media_fit,
                constrain_to_viewport: section.settings.constrain_to_viewport,
                lazy_load: false
              %}
            </li>
          {%- endif -%}
          {%- for media in product.media -%}
            {% if media_position >= limit
              or media_position >= 1
              and section.settings.hide_variants
              and variant_images contains media.src
            %}
              {% continue %}
            {% endif %}
            {%- unless media.id == product.selected_or_first_available_variant.featured_media.id -%}
                {%- liquid
                  assign media_position = media_position | default: 0 | plus: 1
                  assign lazy_load = false
                  if media_position > 1
                    assign lazy_load = true
                  endif
                  assign current_color = product.selected_or_first_available_variant.option1              
                -%}
                {% if section.settings.enable_group_media == false or media_count == 1 or current_color == media.alt and section.settings.enable_group_media == true  %}
              <li
                id="Slide-{{ section.id }}-{{ media.id }}"
                class="product__media-item grid__item slider__slide{% if single_media_visible %} product__media-item--single{% endif %}{% if product.selected_or_first_available_variant.featured_media == nil and forloop.index == 1 %} is-active{% endif %}{% if media.media_type != 'image' %} product__media-item--full{% endif %}{% if settings.animations_reveal_on_scroll %} scroll-trigger animate--fade-in{% endif %}"
                data-media-id="{{ section.id }}-{{ media.id }}"
              >
                {% render 'product-thumbnail',
                  media: media,
                  media_count: media_count,
                  position: media_position,
                  desktop_layout: section.settings.gallery_layout,
                  mobile_layout: section.settings.mobile_thumbnails,
                  loop: section.settings.enable_video_looping,
                  modal_id: section.id,
                  xr_button: true,
                  media_width: media_width,
                  media_fit: section.settings.media_fit,
                  constrain_to_viewport: section.settings.constrain_to_viewport,
                  lazy_load: lazy_load
                %}
               
              </li>
                   {% endif %}
            {%- endunless -%}
          {%- endfor -%}
        </ul>
        <div class="slider-buttons quick-add-hidden{% if hide_mobile_slider %} small-hide{% endif %}">
          <button
            type="button"
            class="slider-button slider-button--prev"
            name="previous"
            aria-label="{{ 'general.slider.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">{{ 'general.slider.of' | t }}</span>
            <span class="slider-counter--total">{{ media_count }}</span>
          </div>
          <button
            type="button"
            class="slider-button slider-button--next"
            name="next"
            aria-label="{{ 'general.slider.next_slide' | t }}"
          >
            {% render 'icon-caret' %}
          </button>
        </div>
      </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 -%}
      {%- liquid
        assign is_not_limited_to_single_item = false
        if limit == null or limit > 1
          assign is_not_limited_to_single_item = true
        endif
      -%}
      {%- if is_not_limited_to_single_item
        and media_count > 1
        and section.settings.gallery_layout contains 'thumbnail'
        or section.settings.mobile_thumbnails == 'show'
      -%}
        <slider-component
          id="GalleryThumbnails-{{ section.id }}"
          class="thumbnail-slider slider-mobile-gutter quick-add-hidden{% unless section.settings.gallery_layout contains 'thumbnail' %} medium-hide large-up-hide{% endunless %}{% if section.settings.mobile_thumbnails != 'show' %} small-hide{% endif %}{% if media_count <= 3 %} thumbnail-slider--no-slide{% endif %}"
        >
          <button
            type="button"
            class="slider-button slider-button--prev{% if media_count <= 3 %} small-hide{% endif %}{% if media_count <= 4 %} medium-hide large-up-hide{% endif %}"
            name="previous"
            aria-label="{{ 'general.slider.previous_slide' | t }}"
            aria-controls="GalleryThumbnails-{{ section.id }}"
            data-step="3"
          >
            {% render 'icon-caret' %}
          </button>
          <ul
            id="Slider-Thumbnails-{{ section.id }}"
            class="thumbnail-list list-unstyled slider slider--mobile{% if section.settings.gallery_layout == 'thumbnail_slider' %} slider--tablet-up{% endif %}"
          >
            {%- capture sizes -%}
              (min-width: {{ settings.page_width }}px) calc(({{ settings.page_width | minus: 100 | times: media_width | round }} - 4rem) / 4),
              (min-width: 990px) calc(({{ media_width | times: 100 }}vw - 4rem) / 4),
              (min-width: 750px) calc((100vw - 15rem) / 8),
              calc((100vw - 8rem) / 3)
            {%- endcapture -%}
    
            {%- if featured_media != null -%}
              {%- liquid
                capture media_index
                  if featured_media.media_type == 'model'
                    increment model_index
                  elsif featured_media.media_type == 'video' or featured_media.media_type == 'external_video'
                    increment video_index
                  elsif featured_media.media_type == 'image'
                    increment image_index
                  endif
                endcapture
                assign media_index = media_index | plus: 1            
              -%}          
              <li
                id="Slide-Thumbnails-{{ section.id }}-0"
                class="thumbnail-list__item slider__slide{% if section.settings.hide_variants and variant_images contains featured_media.src %} thumbnail-list_item--variant{% endif %}"
                data-target="{{ section.id }}-{{ featured_media.id }}"
                data-media-position="{{ media_index }}"
                data-alt="{{ media.alt }}"
              >
                {%- capture thumbnail_id -%}
                  Thumbnail-{{ section.id }}-0
                {%- endcapture -%}
                
                <button
                  class="thumbnail global-media-settings global-media-settings--no-shadow"
                  aria-label="{%- if featured_media.media_type == 'image' -%}{{ 'products.product.media.load_image' | t: index: media_index }}{%- elsif featured_media.media_type == 'model' -%}{{ 'products.product.media.load_model' | t: index: media_index }}{%- elsif featured_media.media_type == 'video' or featured_media.media_type == 'external_video' -%}{{ 'products.product.media.load_video' | t: index: media_index }}{%- endif -%}"
                  aria-current="true"
                  aria-controls="GalleryViewer-{{ section.id }}"
                  aria-describedby="{{ thumbnail_id }}"
                >
                  {{
                    featured_media.preview_image
                    | image_url: width: 416
                    | image_tag:
                      loading: 'lazy',
                      sizes: sizes,
                      widths: '54, 74, 104, 162, 208, 324, 416',
                      id: thumbnail_id,
                      alt: featured_media.alt
                    | escape
                  }}
                </button>
              </li>          
            {%- endif -%}
            {%- for media in product.media -%}
              {%- unless media.id == product.selected_or_first_available_variant.featured_media.id -%}
                {%- liquid
                  capture media_index
                    if media.media_type == 'model'
                      increment model_index
                    elsif media.media_type == 'video' or media.media_type == 'external_video'
                      increment video_index
                    elsif media.media_type == 'image'
                      increment image_index
                    endif
                  endcapture
                  assign media_index = media_index | plus: 1
                -%}
                <li
                  id="Slide-Thumbnails-{{ section.id }}-{{ forloop.index }}"
                  class="thumbnail-list__item slider__slide{% if section.settings.hide_variants and variant_images contains media.src %} thumbnail-list_item--variant{% endif %}"
                  data-target="{{ section.id }}-{{ media.id }}"
                  data-media-position="{{ media_index }}"
                  data-alt="{{ media.alt }}"
                  data-enable-group-media="{{ section.settings.enable_group_media }}"
                >
                  {%- if media.media_type == 'model' -%}
                    <span class="thumbnail__badge" aria-hidden="true">
                      {%- render 'icon-3d-model' -%}
                    </span>
                  {%- elsif media.media_type == 'video' or media.media_type == 'external_video' -%}
                    <span class="thumbnail__badge" aria-hidden="true">
                      {%- render 'icon-play' -%}
                    </span>
                  {%- endif -%}
                  {%- capture thumbnail_id -%}
                    Thumbnail-{{ section.id }}-{{ forloop.index }}
                  {%- endcapture -%}
                  <button
                    class="thumbnail global-media-settings global-media-settings--no-shadow"
                    aria-label="{%- if media.media_type == 'image' -%}{{ 'products.product.media.load_image' | t: index: media_index }}{%- elsif media.media_type == 'model' -%}{{ 'products.product.media.load_model' | t: index: media_index }}{%- elsif media.media_type == 'video' or media.media_type == 'external_video' -%}{{ 'products.product.media.load_video' | t: index: media_index }}{%- endif -%}"
                    {% if media == product.selected_or_first_available_variant.featured_media
                      or product.selected_or_first_available_variant.featured_media == null
                      and forloop.index == 1
                    %}
                      aria-current="true"
                    {% endif %}
                    aria-controls="GalleryViewer-{{ section.id }}"
                    aria-describedby="{{ thumbnail_id }}"
                  >
                    {{
                      media.preview_image
                      | image_url: width: 416
                      | image_tag:
                        loading: 'lazy',
                        sizes: sizes,
                        widths: '54, 74, 104, 162, 208, 324, 416',
                        id: thumbnail_id,
                        alt: media.alt
                      | escape
                    }}
                  </button>
                 
                </li>
              {%- endunless -%}
            {%- endfor -%}
          </ul>
          <button
            type="button"
            class="slider-button slider-button--next{% if media_count <= 3 %} small-hide{% endif %}{% if media_count <= 4 %} medium-hide large-up-hide{% endif %}"
            name="next"
            aria-label="{{ 'general.slider.next_slide' | t }}"
            aria-controls="GalleryThumbnails-{{ section.id }}"
            data-step="3"
          >
            {% render 'icon-caret' %}
          </button>
        </slider-component>
      {%- endif -%}
    </media-gallery>
    
  4. Edit snippets/product-media-modal.liquid: Open this file and replace the code with below code.

    {% comment %}
      Renders a product media modal. Also see 'product-media-gallery'
    
      Accepts:
      - product: {Object} Product liquid object
      - variant_images: {Array} Product images associated with a variant
    
      Usage:
      {% render 'product-media-modal' %}
    {% endcomment %}
    
    <product-modal id="ProductModal-{{ section.id }}" class="product-media-modal media-modal">
      <div
        class="product-media-modal__dialog color-{{ section.settings.color_scheme }} gradient"
        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 color-{{ section.settings.color_scheme }} gradient"
          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 or variant_images contains media.id
                assign variant_image = true
              else
                assign variant_image = false
              endif
            assign current_color = product.selected_or_first_available_variant.option1 
    
              unless media.id == product.selected_or_first_available_variant.featured_media.id
                 if section.settings.enable_group_media == false or media_count ==1 or current_color == media.alt and section.settings.enable_group_media == true
                    render 'product-media', media: media, loop: section.settings.enable_video_looping, variant_image: variant_image
                 endif
              endunless
            -%}
          {%- endfor -%}
        </div>
      </div>
    </product-modal>
    
  5. Update snippets/product-variant-picker.liquid. Open this file and replace its contents with the code provided below.

    {% comment %}
      Renders product variant-picker
    
      Accepts:
      - product: {Object} product object.
      - block: {Object} passing the block information.
      - product_form_id: {String} Id of the product form to which the variant picker is associated.
      Usage:
      {% render 'product-variant-picker', product: product, block: block, product_form_id: product_form_id %}
    {% endcomment %}
    {%- unless product.has_only_default_variant -%}
      <variant-selects
        id="variant-selects-{{ section.id }}"
        data-section="{{ section.id }}"
        {{ block.shopify_attributes }}
      >
        {%- for option in product.options_with_values -%}
          {%- liquid
            assign swatch_count = option.values | map: 'swatch' | compact | size
            assign picker_type = block.settings.picker_type
    
            if swatch_count > 0 and block.settings.swatch_shape != 'none'
              if block.settings.picker_type == 'dropdown'
                assign picker_type = 'swatch_dropdown'
              else
                assign picker_type = 'swatch'
              endif
            endif
          -%}
          {%- if picker_type == 'swatch' -%}
            <fieldset class="js product-form__input product-form__input--swatch" data-option-name="{{ option.name }}">
              <legend class="form__label">
                {{ option.name }}:
                <span data-selected-value>
                  {{- option.selected_value -}}
                </span>
              </legend>
              {% render 'product-variant-options',
                product: product,
                option: option,
                block: block,
                picker_type: picker_type
              %}
            </fieldset>
          {%- elsif picker_type == 'button' -%}
            <fieldset class="js product-form__input product-form__input--pill" data-option-name="{{ option.name }}">
              <legend class="form__label">{{ option.name }}</legend>
              {% render 'product-variant-options',
                product: product,
                option: option,
                block: block,
                picker_type: picker_type
              %}
            </fieldset>
          {%- else -%}
            <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">
                {%- if picker_type == 'swatch_dropdown' -%}
                  <span
                    data-selected-value
                    class="dropdown-swatch"
                  >
                    {% render 'swatch', swatch: option.selected_value.swatch, shape: block.settings.swatch_shape %}
                  </span>
                {%- endif -%}
                <select
                  id="Option-{{ section.id }}-{{ forloop.index0 }}"
                  class="select__select"
                  name="options[{{ option.name | escape }}]"
                  form="{{ product_form_id }}"
                  data-option-name="{{ option.name }}"
                >
                  {% render 'product-variant-options',
                    product: product,
                    option: option,
                    block: block,
                    picker_type: picker_type
                  %}
                </select>
                <span class="svg-wrapper">
                  {{- 'icon-caret.svg' | inline_asset_content -}}
                </span>
              </div>
            </div>
          {%- endif -%}
        {%- endfor -%}
    
        <script type="application/json" data-selected-variant>
          {{ product.selected_or_first_available_variant | json }}
        </script>
      </variant-selects>
    {%- endunless -%}
    
  6. Update assets/product-info.js: In this file, add a new function called setVariantImages. Update the file Update the file with reference to the screenshots below.

    Update line 17 :

    this.setVariantImages()

    Update line 308 and 315:

    this.setVariantImages()
          setVariantImages() {
           // set variant images in the media gallery
            const galleryImages = Array.from(document.querySelectorAll('[data-alt]'));
            const variantOptionNames = Array.from(document.querySelectorAll('[data-option-name]'));
            let setVariantImages = false;
            let value;
            variantOptionNames.forEach((variantOption)=> {
              if(variantOption.getAttribute('data-option-name') == 'Color') {
                value = Array.from(variantOption.querySelectorAll('input')).find((radio) => radio.checked).value;
                setVariantImages = true;
              }
            })
            if(setVariantImages) {
              galleryImages.forEach((galleryImage) => {
                if(galleryImage.getAttribute('data-alt') == value && galleryImage.getAttribute('data-enable-group-media') == 'true') {
                  galleryImage.classList.remove("thumbnail-list_item--variant");            
                } else {
                  galleryImage.classList.add("thumbnail-list_item--variant");
                }          
              })
           }
          }
  7. Set Up Variant Image Alt Text: In your Shopify admin, ensure that each variant image has alt text that exactly matches its corresponding variant option name. Also, make sure that the the variant name is 'color'

By following these steps, you can effectively display only the images of the selected variant in your product gallery. This enhancement will not only improve user experience but also elevate the overall visual appeal of your Shopify store.

ByShinetech Changchun

Tue Dec 24 2024
the blog image
Optimize Your Shopify Footer: Integrate Newsletter Subscription with Footer in Dawn 15

Business Scenario

A common request from Shopify store owners is to enhance the footer area for better user engagement and improved aesthetic. One effective way to achieve this is by placing the newsletter subscription box in the same column as the menu links. This streamlined approach not only saves space but also encourages visitors to subscribe while browsing the footer menu.

Benefits of a Unified Footer Layout

Implementing this feature offers several advantages:

  • Space Optimization: Combining the newsletter subscription box with the menu saves valuable space in the footer area, making it less cluttered and more user-friendly.

  • Enhanced User Engagement: A well-organized footer can draw more attention to the newsletter subscription box, increasing the chances of visitors subscribing.

  • Improved Visual Appeal: A unified layout creates a clean and professional look, aligning with modern web design standards.

  • Increased Conversion Rates: By making it easier for customers to find and subscribe to your newsletter, you can potentially increase your mailing list and conversion rates.

Footer Newsletter subscription section in Dawn 15 by default.

Cutomized Footer Newsletter subscription section.

Steps to Implement

Before making any code modifications, always ensure you create a copy of the theme first. Test all your changes on this copy to confirm everything works perfectly before publishing it to the live theme. This precaution helps prevent any issues from affecting your main theme and keeps your work safe.

To display the newsletter subscription box component in the same column as the menu in the footer, follow these steps:

  1. Open Your Theme Code Editor: Access your Shopify theme's code editor.

  2. Edit the CSS File: Locate and open either the section-footer.css or base.css file.

  3. Insert the Custom CSS Code: Add the following code to position the newsletter subscription box in the same column as the menu:

    .footer__content-top {
      display: flex !important;
      align-items: baseline;
      justify-content: space-around;
    }
    
    .footer__blocks-wrapper {
      width: 70%; // Adjust based on the requirement
    }
    
    .footer-block__newsletter .footer-block__heading {
      text-align: left;
    }
    
    @media screen and (max-width: 749px) {
      .footer__content-top {
        flex-direction: column;
      }
    }
  1. Save Your Changes: Save the modifications to your CSS file.

  2. Preview the Changes: Check your website's footer to ensure that the newsletter subscription box is displayed correctly alongside the menu.

By following these steps, you can effectively display the newsletter subscription box in the same column as the menu in your Shopify store's footer. This not only enhances the visual appeal of your site but also improves user engagement and optimizes the footer layout.

ByShinetech Changchun

Thu Dec 19 2024
the blog image
Enhance Your Shopify Homepage with Autoplay Videos

Implementing autoplay videos on your Shopify homepage can be a game-changer for engaging customers as soon as they land on your site. This feature grabs attention immediately, providing a dynamic and visually appealing introduction to your store.

Benefits of Autoplay Videos

Implementing autoplay videos offers several advantages:

  • Increased Engagement: Videos capture attention more effectively than static images or text, keeping visitors on your site longer.

  • Enhanced User Experience: Provides a seamless and interactive experience, making your site feel more lively and modern.

  • Better Storytelling: Allows you to showcase your products or brand story in a compelling way, helping to build a stronger connection with your audience.

  • Boosted Conversions: Engaging video content can lead to higher conversion rates as customers are more likely to make a purchase after watching a video.

Steps to Implement

Before making any code modifications, always ensure you create a copy of the theme first. Test all your changes on this copy to confirm everything works perfectly before publishing it to the live theme. This precaution helps prevent any issues from affecting your main theme and keeps your work safe.

After uploading the video to Shopify, there are two options to achieve video autoplay feature.

First Option: Add Custom Liquid Code

If you’re using the Dawn theme version 13.0.0 or newer, follow these steps to add a custom liquid section for autoplay and loop features:

  1. Customize Your Theme: Click on “Customize” for your theme.

  2. Add a Section: In the customization panel, click on “Add section” and choose “Custom liquid.”

  3. Insert Custom Liquid Code: Paste the provided custom liquid code into the editor.

    <style>
    video {
    width: 100%;
    height: auto;
    display: block;
    margin: 0 auto;
    }
    </style>
    <video muted autoplay playsinline loop>
        <source src="/media/cc0-videos/websensepro.mp4"
                      type="video/mp4">
        <source src="/media/cc0-videos/websensepro.mp4"
                      type="video/mp4">
    </video>
    

Second Option: Auto Play Self-Hosted Video with Loop in Shopify

Create a new section named autoplay-video.liquid and paste the code. This will create a new section where you can select a video from your Shopify library.

<div 
  class="page-width"
  {% if section.settings.fullwidth %}
    style="max-width: 100%!important;margin: 0;padding:0px"
  {% endif %}
>
  <div class="video-section">
    <h2>{{ section.settings.heading }}</h2>
  </div>
  {% if section.settings.video_url.type == 'youtube' %}
    <div
      class="video-container"
      {% if section.settings.fullwidth %}
        style="width: 100%;"
      {% endif %}
    >
      <iframe
        src="https://www.youtube.com/embed/{{ section.settings.video_url.id }}{% if section.settings.autoplay %}?autoplay=1&mute=1{% endif %}{% if section.settings.controller == false %}&controls=0{% endif %}&modestbranding=1"
        class="youtube"
        width="100%"
        height="600px"
      ></iframe>
    </div>
  {% elsif section.settings.video_url.type == 'vimeo' %}
    <div class="video-container">
      <video
        src="https://player.vimeo.com/video/{{ section.settings.video_url.id }}{% if section.settings.autoplay %}?autoplay=1&mute=1{% endif %}"
        class="vimeo"
        {% if section.settings.fullwidth %}
          style="width: 100%;"
        {% endif %}
      ></video>
    </div>
  {% elsif section.settings.self_hosted_video %}
    <div
      class="video-container"
      {% if section.settings.fullwidth %}
        style="width: 100%;"
      {% endif %}
    >
      {{
        section.settings.self_hosted_video
        | video_tag:
          controls: section.settings.controller,
          autoplay: section.settings.autoplay,
          loop: true,
          width: '100%'
      }}
    </div>
  {% endif %}
</div>

{% if section.settings.fullwidth %}
  <style>
    /* Your full-height styles go here */
    .video-container {
      position: relative;
      width: 100%;
      padding-bottom: 56.25%; /* Adjust this value as needed for the aspect ratio */
      height: 0;
      overflow: hidden;
    }
    .video-container iframe,
    .video-container video {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
    }
  </style>
{% endif %}

<script>
  document.addEventListener('DOMContentLoaded', function () {
    var videos = document.querySelectorAll('.youtube, .vimeo');
    function playPauseVideos() {
      videos.forEach(function (video) {
        if (section.settings.autoplay) {
          video.contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}', '*');
        } else {
          video.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}', '*');
        }
      });
    }
    // Initial play/pause based on the 'Autoplay video' setting
    playPauseVideos();
    // Listen for changes in the 'Autoplay video' setting and play/pause accordingly
    document.getElementById('section_autoplay').addEventListener('change', function () {
      section.settings.autoplay = this.checked;
      playPauseVideos();
    });
  });
</script>
{% schema %}
{
  "name": "Video WebSensePro",
  "settings": [
    {
      "type": "text",
      "id": "heading",
      "label": "Video section heading",
      "default": "Video Autoplay"
    },
    {
      "type": "checkbox",
      "id": "fullwidth",
      "label": "Make Full Width"
    },
    {
      "type": "checkbox",
      "id": "autoplay",
      "label": "Autoplay video",
      "default": false
    },
    {
      "type": "checkbox",
      "id": "controller",
      "label": "Enable Controls",
      "default": false
    },
    {
      "type": "video",
      "id": "self_hosted_video",
      "label":"Select Video"
    }
  ],
  "presets": [
    {
      "name": "Video WebSensePro"
    }
  ]
}
{% endschema %}

If you wish to play a video hosted on YouTube or Vimeo, add the code below next to row 126 of above schema, then you’ll be able to paste a YouTube/Vimeo video link in the new section.

,
     {
      "type": "video_url",
      "id": "video_url",
      "accept": ["youtube", "vimeo"],
      "default": "https://www.youtube.com/watch?v=_9VUPq3SxOc",
      "label": "URL",
      "info": "Video plays on the page."
    }

ByShinetech Changchun

Fri Dec 13 2024
the blog image
Maximize Sales and Retention: A Guide to Shopify Store Credit

Store credit is a trendy new feature recently released by Shopify in July 2024, designed to enhance customer engagement. This innovative functionality allows customers to hold a balance within their accounts that can be applied to future purchases, rewarding loyal customers, eliminating the need for cash refunds or issuing gift cards on certain occasions. In this blog, we will explore the benefits of store credit, how it works, and how you can implement it effectively in your Shopify store.

What is Store Credit?

Store credit is a monetary value assigned to a customer's account that can only be used within the issuing store. Unlike gift cards or discount codes, store credit is non-transferable and linked directly to the customer, making it a unique tool for fostering loyalty and repeat business. With the introduction of this feature, Shopify merchants can now manually issue store credits directly from their admin panel.

Benefits of Store Credit

Implementing store credit in your Shopify store offers several advantages:

  • Enhanced Customer Loyalty: By providing store credit to the customers’ account instead of gift cards or coupons, which can be give away, businesses encourage customers to return and make new purchases, ultimately boosting retention rates.

  • Operational Efficiency: Store credit streamlines the loyalty rewards process, enabling merchants to manage credits directly from customer profiles without the hassle of issuing gift cards.

  • Increased Sales Potential: Customers using store credit often feel less constrained by their spending, leading them to purchase additional items beyond the value of their credits.

  • Improve Customer Experience: Customers no longer need to memorize complex gift card codes. With store credit, they can effortlessly enjoy the rewards of their loyalty.

  • Flexibility in Customer Interactions: Store credit can be issued for various reasons—whether compensating for an unsatisfactory experience or rewarding loyalty—making it a versatile tool for customer engagement.

 

Innovative Uses of Store Credit in Shopify

Currently, store credit in Shopify Admin can only be manually edited within the specific customer’s account. However, many merchants have expressed interest in automating the crediting process, particularly for loyalty rewards.

Based on the feedback we’ve gathered, our team has developed an effective solution for automating this process. Here’s how it works:

  1. Accrual of Store Credit: Customers earn store credit based on their purchases. For example, our model allows customers to accumulate 10% of their order total as store credit. If a customer makes a purchase of $1000, they will earn $100 in store credit. This is all based on the rules written in the code, and the rules can be changed based on the specific business requirement.

  2. Using Store Credit at Checkout: When customers log into their accounts at checkout, they will see an option to apply any available store credit. If they choose to use their $100 credit on a new purchase, this amount will be deducted from their order total.

  3. Tracking and Management: Store credits are easily managed through the Shopify admin interface. Merchants can view each customer's balance and transaction history, ensuring transparency and accuracy in managing credits In our custom-developed app interface, merchants can also track the source of the credit—specifying which order contributed to the credit amount—providing valuable insights for monitoring the credit process.

If you’re interested in implementing or customizing this store credit solution for your Shopify store, please reach out to us. We’re here to collaborate and tailor a solution that aligns perfectly with your business needs.

Shopify's new store credit feature is more than just another form of gift card; it’s a strategic tool that enhances customer loyalty and amplifies sales potential. By offering store credit, you can transform sales into opportunities for future sales, providing customers with flexibility and incentives to shop again.

ByShinetech Changchun

Thu Nov 28 2024
the blog image
Optimize Your Shopify Store: Prevent Overselling with Quantity Selector Limits

Current Issue

Many Shopify store owners face a common challenge: customers can select more items in the quantity selector than are available in stock. For example, if you have a product with 11 pieces in stock, customers may still choose a quantity exceeding this limit.

When they click "Add to Cart," an error message appears stating, "You can't add more PRODUCT NAME to the cart." Unfortunately, this message does not inform customers of the actual quantity available, leaving them unsure of how many pieces they can add to their cart. This is particularly problematic for customers needing to purchase in large volumes and indicates a clear need for improvement in the customer experience.

Before:

After modification:

Requirement

To enhance user experience and avoid overselling, we need to:

  1. Prevent customers from adding more items to their cart than the available stock.

  2. Display the exact quantity of each item on hand to the customer.

Solution

To set a limit on your quantity picker, follow these simple steps:

Before making any code modifications, always ensure you create a copy of the theme first. Test all your changes on this copy to confirm everything works perfectly before publishing it to the live theme. This precaution helps prevent any issues from affecting your main theme and keeps your work safe.

Step 1: Open Your Theme Code Editor

  • Navigate to your Shopify admin.

  • Go to Online Store and then Themes.

  • Click on Actions and select Edit Code.

Step 2: Find the File main-product.liquid

  • In the left-hand sidebar, locate the Sections folder.

  • Open the file main-product.liquid.

Step 3: Locate the Quantity Input Code

  • Search within this file for the quantity_input code.

tep 4: Add Necessary Attributes

Copy and paste the following code into the quantity_input box to set the limit:

max="{{ product.selected_or_first_available_variant.inventory_quantity }}"
                        data-max="{{ product.selected_or_first_available_variant.inventory_quantity }}"

Step 5: Find and open the product-info.js file

In the asset folder, locate the updateQuantityRules function.

Step 6: Replace the code with the following:

const attributes = ['data-cart-quantity', 'data-min', 'data-max', 'step', 'max'];

This ensures that customers cannot select more items than what is available in stock and informs them of the exact quantity on hand.

By implementing these steps, you can effectively manage stock levels and enhance your customers' shopping experience. No more disappointed customers—just a smooth, efficient checkout process.

ByShinetech Changchun

Wed Nov 27 2024
the blog image
Syncing Shopify with Microsoft Dynamics GP: A Case Study

In today’s digital world, businesses increasingly turn to e-commerce platforms like Shopify to expand their reach and boost online sales. At the same time, many rely on powerful ERP systems like Microsoft Dynamics GP to manage core operations. However, the lack of integration between these systems often results in manual data entry for orders, customer details, and inventory updates—leading to inefficiencies, errors, and added operational overhead.

This case study highlights how we developed a custom integration solution to synchronize data between Shopify and Microsoft Dynamics GP, helping businesses streamline operations and achieve a unified view of their online and offline activities.

Project Goals: Enabling a Smooth Data Flow

Our objective was to develop an application that automates data exchange between Shopify and Microsoft Dynamics GP, eliminating manual processes and ensuring data consistency. Specifically, we aimed to:

  • Automate Data Synchronization – Enable real-time or scheduled data transfers, ensuring both systems remain up-to-date.

  • Implement Flexible Synchronization Strategies – Design adaptable data sync plans to optimize efficiency and accuracy across different data types.

Implementation: A Step-by-Step Approach

Requirement Analysis

We conducted an in-depth assessment of the client’s business processes, identifying critical data points for synchronization, required update frequency, and specific business rules. This was documented in a detailed requirements specification to align expectations.

Technology Selection

C# was chosen for its strong compatibility with Microsoft Dynamics GP. The integration leveraged:

  • Shopify REST API – To extract necessary data from Shopify.

  • Dynamics GP Web Service – To securely transmit data into Dynamics GP.

Development Process

Our solution centered around two key components:

  • Data Retrieval – Extracting relevant information from Shopify using its REST API.

  • Data Transmission – Pushing data securely into Dynamics GP via its Web Service.

Overcoming Challenges

During integration, we tackled several challenges:

  • Limited Documentation – Microsoft Dynamics GP’s lack of detailed documentation required extensive research and testing.

  • Logical Adjustments – Custom synchronization logic was necessary to align with the client’s business workflows and data structures.

  • Web Service Configuration Issues – Some parameters failed to sync due to GP Web Service limitations, requiring manual configuration adjustments to resolve.

The Solution: A Robust Synchronization Engine

Our integration solution featured:

  • Custom Synchronization Rules – Tailored data transformation and synchronization logic for accurate, consistent migration.

  • Error Handling Mechanism – A comprehensive error-logging and retry system to ensure data integrity.

Deliverables: Equipping the Client for Success

We provided:

  • Source Code Package – Complete source code and configuration files for future maintenance and scalability.

  • User Guide – A detailed guide covering installation, configuration, and troubleshooting.

Quality Assurance: Ensuring a Seamless Experience

To ensure a stable and high-performing solution, we maintained ongoing collaboration with the client to refine and validate requirements. Our team conducted thorough design reviews, involving both internal and external stakeholders, to guarantee a scalable and efficient architecture. Extensive unit testing was performed to verify the reliability of individual components, followed by integration testing in a dedicated environment mirroring the production setup to ensure seamless data flow. 

We also carried out performance testing, simulating high-load scenarios to assess system stability. Before deployment, we engaged the client in structured User Acceptance Testing (UAT), incorporating their feedback to fine-tune the application. Finally, we implemented continuous monitoring and established a maintenance schedule to track real-time performance and ensure long-term stability.

Conclusion: Driving Business Efficiency

By integrating Shopify with Microsoft Dynamics GP, we helped the client:

  • Eliminate Manual Data Entry – Automating synchronization freed up valuable time and resources.

  • Improve Data Accuracy – Reduced errors and ensured consistent data across platforms.

  • Gain Real-time Visibility – A unified view of online and offline operations enabled better decision-making.

ByShinetech Changchun

Mon Nov 25 2024
the blog image
Automate Gift Card Issuance for Orders Over Certain Value in Shopify: A Shopify Flow Tutorial

In this guide, we will explore how to automatically send gift cards to customers who successfully place an order exceeding a specified amount. This process can enhance customer satisfaction and encourage repeat business.

Business Scenario

The client wants to implement an automated system that sends a gift card to customers after they complete an order that meets a predetermined price threshold.

Solution Overview

The solution involves creating a logical flow that checks the order details and issues a gift card based on specific conditions. Here’s how it works:

  1. Order Marking: When a gift card is sent, the system marks the associated order as paid.

  2. Order Amount Check: The system verifies if the total order amount exceeds a specified limit.

  3. Gift Card Issuance Decision: If the order total is above the set amount, the system prepares to issue a gift card.

  4. Additional Name Condition: To prevent unintended gift card issuance, an additional check ensures that the item names in the order do not match the gift card name.

  5. Flow Termination: If the order fails this name condition, the process halts, and no gift card is issued.

By implementing this dual-condition check (amount and name), the system ensures that gift cards are only issued when both criteria are satisfied.

Implementation Steps

This automation can be achieved using Shopify Flow. Below are the detailed steps:

Step 1: Create a Gift Card Product

Set up a gift card product with the desired value for distribution.

Step 2: Access Shopify Flow

Navigate to Shopify Flow and create a new workflow.

Step 3: Set Workflow Trigger

Start with the trigger labeled "Order Paid."

Step 4: Add Conditions

Insert a condition that checks if the order amount is greater than or equal to $199.

Ensure that none of the item names in the order match the name of the gift card (e.g., "Free Gift Card - £1,000.00").

Step 5: Select Gift Card Product

Add an action to select the gift card product as an "Order line item."

Make sure to uncheck the box for "Send email to notify customer." This prevents sending a bill for the gift card, which could confuse customers.

Step 6: Mark as Paid

Finally, add an action labeled "Mark as Paid." This will automatically mark the added gift card as paid, ensuring that customers receive a separate email with their gift card details.

Customer Notification

Once this workflow is activated, customers will receive an email containing their gift card information. Additionally, they can view the gift card added to their order in Shopify Admin.

Considerations

One limitation of this approach is that while the gift card is marked as paid manually, its value may still appear in the total order amount. This could lead to confusion regarding the actual total due from customers. By following these steps, you can effectively automate the issuance of gift cards based on customer orders, enhancing your customer engagement strategy while minimizing manual intervention.

Conclusion

In summary, automating gift card issuance for orders over $199 can significantly improve customer satisfaction and loyalty. By implementing this straightforward workflow in Shopify Flow, you can streamline your operations and enhance your marketing efforts.

ByShinetech Changchun

Mon Nov 04 2024
the blog image
How to Implement a Transparent Header in Your Shopify Theme

The Benefits of a Transparent Header

In today's digital landscape, a transparent header is a popular design trend that offers a visually striking effect when customers visit your site. Unlike traditional headers with a solid color, 

a transparent header enhances your brand image and offers several key benefits:

  • Aesthetic Appeal: Transparent headers provide a clean and modern look that can elevate your site's overall design.

  • Improved User Experience: They enable a smoother visual transition as users scroll, making navigation more intuitive.

  • Seamless Integration: A transparent header blends seamlessly with your page content, creating a cohesive visual experience.

  • Prominent Branding: With no distracting background, your brand elements become more prominent and impactful.

Visual Comparison

Here’s a comparison to illustrate the difference:

Traditional Header:

Transparent Header:

Step-by-Step Guide to Implementing a Transparent Header

To integrate a transparent header into your Shopify theme, follow these steps:

Before making any code modifications, always ensure you create a copy of the theme first. Test all your changes on this copy to confirm everything works perfectly before publishing it to the live theme. This precaution helps prevent any issues from affecting your main theme and keeps your work safe.

  1. Modify the Header.liquid File: Access the header.liquid file within the Sections folder of your theme. Insert the following code to achieve the transparent effect:

{% if template == 'index' %}
  <style>
    .section-header{margin-bottom:0}.header-wrapper--border-bottom{border-bottom:.1rem solid rgba(255,255,255,.08)}.overflow-hidden-desktop .header-wrapper,.overflow-hidden-tablet .header-wrapper{position:initial;width:initial;background:initial;color:initial;background:var(--gradient-background)}.header-wrapper summary.header__menu-item+ul span,.header-wrapper summary.header__menu-item+ul svg,.overflow-hidden-desktop .header-wrapper .disclosure__button svg,.overflow-hidden-desktop .header-wrapper .disclosure__button>span,.overflow-hidden-desktop .header-wrapper .list-menu span,.overflow-hidden-desktop .header-wrapper a svg,.overflow-hidden-desktop .header-wrapper details>span,.overflow-hidden-desktop .header-wrapper h1.header__heading span.h2,.overflow-hidden-desktop .header-wrapper summary svg,.overflow-hidden-desktop details[open]>.header__menu-item,.overflow-hidden-tablet .header-wrapper .disclosure__button svg,.overflow-hidden-tablet .header-wrapper .disclosure__button>span,.overflow-hidden-tablet .header-wrapper .list-menu span,.overflow-hidden-tablet .header-wrapper a svg,.overflow-hidden-tablet .header-wrapper details>span,.overflow-hidden-tablet .header-wrapper h1.header__heading span.h2,.overflow-hidden-tablet .header-wrapper summary svg,.overflow-hidden-tablet details[open]>.header__menu-item,.scrolled-past-header .header-wrapper .disclosure__button svg,.scrolled-past-header .header-wrapper .disclosure__button>span,.scrolled-past-header .header-wrapper .list-menu span,.scrolled-past-header .header-wrapper a svg,.scrolled-past-header .header-wrapper details>span,.scrolled-past-header .header-wrapper h1.header__heading span.h2,.scrolled-past-header .header-wrapper summary svg,.scrolled-past-header details[open]>.header__menu-item{color:rgba(var(--color-foreground),.75)!important}.header-wrapper{position:absolute;width:100%;background:0 0;color:#fff}.header-wrapper .disclosure__button svg,.header-wrapper .disclosure__button>span,.header-wrapper .list-menu span,.header-wrapper a svg,.header-wrapper details>span,.header-wrapper h1.header__heading span.h2,.header-wrapper summary svg,details[open]>.header__menu-item{color:#fff!important}.scrolled-past-header .header-wrapper{position:absolute;width:100%;background:0 0;color:rgb(var(--color-foreground))!important}.header-localization .disclosure .localization-form__select:hover{text-decoration-color:white}
    .scrolled-past-header .header-wrapper {
      background: #fff;
    }
  </style>
{% endif %}
  1. Save and Review: Save your changes and review the header on different devices to ensure it displays correctly across all platforms.

By incorporating a transparent header, you can enhance the visual appeal and user experience of your Shopify store. This small adjustment can make a significant impact on how customers 

interact with your site, ultimately benefiting your brand's image and sales.

ByShinetech Changchun

Fri Oct 18 2024
the blog image
How to Add Breadcrumbs in Shopify's Dawn 15 Theme

In this tutorial, we will address a frequent question from the Shopify community: how to add breadcrumbs to the Dawn 15 theme. Breadcrumbs are an essential feature that helps users navigate your site more efficiently, improving customer experience and potentially increasing sales revenue.

Understanding Breadcrumbs

Breadcrumbs are navigation aids that show users the path they have taken to arrive at a particular page. They enable users to easily find the collection they are viewing while on a product detail page by clicking on the breadcrumb path.

To add breadcrumbs to the Shopify Dawn 15 theme, follow these steps:

Before making any code modifications, always ensure you create a copy of the theme first. Test all your changes on this copy to confirm everything works perfectly before publishing it to the live theme. This precaution helps prevent any issues from affecting your main theme and keeps your work safe.

Creating a Breadcrumb Section for Shopify's Dawn 15 Theme

Add a New File

Begin by adding a new file in the sections folder of your theme. Name this file breadcrumb.liquid. Open the file and replace the content with the code provided. Save your changes.

{%- style -%}
  {% if template == 'product' and section.settings.enable_product_page %}
  .breadcrumbs{
  display: block;
  }
  {% else %}
  .breadcrumbs{
  display: none;
  }
  {% endif %}

  {% if section.settings.enable_complete_site %}
  .breadcrumbs{
  display: block;
  }
  {% endif %}

  {% if template == 'collection' and section.settings.enable_collection_page %}
  .breadcrumbs{
  display: block;
  }
  {% endif %}


  :root {
  --svg_fill: {{ section.settings.breadcrumb_text_color}}
  ;
  }

  .home-icon-container {
  display: inline-block;
  margin-right: 10px;
  margin-left: 10px;
  margin-top: 10px;
  vertical-align: sub;
  }

  .breadcrumbs {
  padding: 1rem 5rem;
  color: {{ section.settings.breadcrumb_text_color }}
  ;
  background-color: {{ section.settings.breadcrumb_bg_color }};
  text-transform: lowercase !important;
  }

  .breadcrumbs li {
  display: inline-block;
  }

  .breadcrumbs a {
  text-decoration: none;
  /*Will put liquid customization here*/
  font-size: 15px;
  color: inherit;
  }


  {%- if section.settings.breadcrumb_accent_color_bool -%}
  .breadcrumbs a:last-of-type {
  color: {{ section.settings.breadcrumb_accent_color }}
  !important;
  }
  {%- endif -%}


  .breadcrumb-delimeter:not(:last-child):after {
  {%- case section.settings.breadcrumb_delimeter -%}
  {%- when "angle_right" -%}
  content: "›";
  font-size: 18px;

  {%- when "slash" -%}
  content: "/";
  font-size: 16px;

  {%- when "arrow_right" -%}
  content: "➤";
  font-size: 15px;

  {%- when "squiggle_arrow" -%}
  content: "\27FF";
  font-size: 25px;

  {%- when "right_long" -%}
  content: "\279E";
  font-size: 18px;

  {%- when "double_right" -%}
  content: "\00BB";
  font-size: 20px;
  {%- when "diamond_arrow_head" -%}
  content: "⤞";
  font-size: 25px;

  {%- when "heavy_angle_right" -%}
  content: "\276F";
  font-size: 18px;

  {%- else -%}

  {%- endcase -%}
  display: inline-block;
  margin-left: 0.75rem;
  margin-right: 0.50rem;
  speak: none;
  }

  .breadcrumbs [aria-current="page"] {
  color: inherit;
  font-weight: normal;
  text-decoration: none;
  }
{%- endstyle -%}

<div class="page-width breadcrumbs" aria-label="breadcrumbs">
  {%- unless template == 'index' or template == 'cart' -%}
    {%- if section.settings.breadcrumbs_home_icon_bool -%}
      <div class="home-icon-container">
        <svg
          xmlns="http://www.w3.org/2000/svg"
          width="20"
          height="20"
          fill="currentColor"
          class="bi bi-house-door-fill"
          viewBox="0 0 16 16"
        >
          <path d="M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z" />
        </svg>
      </div>
    {%- endif -%}

    <a href="/" title="Home">Home</a>
    {%- case template.name -%}
      {%- when 'article' -%}
        {%- for link in linklists['main-menu'].links -%}
          {%- if link.url == blog.url -%}
            <span class="breadcrumb-delimeter" aria-hidden="true"></span>
            <!-- Breadcrumb Delimeter -->
            {{ link.title | link_to: link.url }}
            {% break %}
          {%- endif -%}
        {%- endfor -%}
        <span class="breadcrumb-delimeter" aria-hidden="true"></span>
        <a href="{{ article.url }}" aria-current="page">{{ article.title }}</a>
      {%- when 'product' -%}
        {%- capture product_url_string -%}
{%- for collection in product.collections -%}
{{ collection.url }}|
{%- endfor -%}
{%- endcapture -%}

        {%- assign object_url_string = product_url_string | append: product.url -%}

        {%- assign object_urls = object_url_string | split: '|' -%}
        {%- capture linklist_titles_str -%}
{%- for linklist in linklists -%}
{{ linklist.title | handleize }}|{%- endfor -%}
{%- endcapture -%}
        {%- assign str_size = linklist_titles_str | size | minus: 1 -%}
        {%- assign linklist_titles_str = linklist_titles_str | slice: 0, str_size -%}
        {%- assign linklist_titles = linklist_titles_str | split: '|' -%}

        {%- assign level = 1 -%}
        {%- for link in linklists['main-menu'].links -%}
          {%- assign link_handle = link.title | handle -%}
          {%- assign link_titles = link_titles | append: link.title | append: '|' -%}
          {%- assign link_urls = link_urls | append: link.url | append: '|' -%}                                                                                                                                    
          {%- assign link_levels = link_levels | append: level | append: '|' -%}
          {%- assign link_parents = link_parents | append: 'main-menu' | append: '|' -%}
          {%- assign link_handles = link_handles | append: link_handle | append: '|' -%}

          {%- if linklist_titles contains link_handle -%}
            {%- for clink in linklists[link_handle].links -%}
              {%- if forloop.first == true -%}
                {%- assign level = level | plus: 1 -%}
              {%- endif -%}
              {% assign clink_handle = clink.title | handle %}
              {%- assign link_titles = link_titles | append: clink.title | append: '|' -%}
              {%- assign link_urls = link_urls | append: clink.url | append: '|' -%}
              {%- assign link_levels = link_levels | append: level | append: '|' -%}
              {%- assign link_parents = link_parents | append: link_handle | append: '|' -%}
              {%- assign handle = link.title | handleize -%}
              {%- assign link_handles = link_handles | append: clink_handle | append: '|' -%}

              {%- if linklist_titles contains clink_handle -%}
                {%- for gclink in linklists[clink_handle].links -%}
                  {%- if forloop.first == true -%}
                    {%- assign level = level | plus: 1 -%}
                  {%- endif -%}

                  {% assign gclink_handle = gclink.title | handle %}
                  {%- assign link_titles = link_titles | append: gclink.title | append: '|' -%}
                  {%- assign link_urls = link_urls | append: gclink.url | append: '|' -%}
                  {%- assign link_levels = link_levels | append: level | append: '|' -%}
                  {%- assign link_parents = link_parents | append: gclink_handle | append: '|' -%}
                  {%- assign link_handles = link_handles | append: gclink_handle | append: '|' -%}

                  {%- if forloop.last == true -%}
                    {%- assign level = level | minus: 1 -%}
                  {%- endif -%}
                {%- endfor -%}
              {%- endif -%}
              {%- if forloop.last == true -%}
                {%- assign level = level | minus: 1 -%}
              {%- endif -%}
            {%- endfor -%}
          {%- endif -%}
        {%- endfor -%}

        {%- comment -%} CONVERT TO ARRAYS {%- endcomment -%}
        {%- assign str_size = link_levels | size | minus: 1 -%}
        {%- assign llevels = link_levels | slice: 0, str_size | split: '|' -%}

        {%- assign str_size = link_titles | size | minus: 1 -%}
        {%- assign ltitles = link_titles | slice: 0, str_size | split: '|' -%}

        {%- assign str_size = link_handles | size | minus: 1 -%}
        {%- assign lhandles = link_handles | slice: 0, str_size | split: '|' -%}

        {%- assign str_size = link_parents | size | minus: 1 -%}
        {%- assign lparents = link_parents | slice: 0, str_size | split: '|' -%}

        {%- assign str_size = link_urls | size | minus: 1 -%}
        {%- assign lurls = link_urls | slice: 0, str_size | split: '|' -%}

        {%- assign depth = '3' -%}
        {%- assign bc3_parent_list_handle = '' %}

        {%- for url in lurls -%}
          {%- if object_urls contains url and llevels[forloop.index0] == depth -%}
            {%- unless url == product.url or url == collection.url -%}
              {%- capture bc3 -%}
                 {{ ltitles[forloop.index0] | link_to: url, ltitles[forloop.index0] }}
              {%- endcapture -%}
            {%- endunless -%}
            {%- assign bc3_parent_list_handle = lparents[forloop.index0] -%}
            {%- assign bc3_list_handle = lhandles[forloop.index0] -%}
            {% break %}
          {%- endif -%}
        {%- endfor -%}

        {%- assign depth = '2' -%}
        {%- assign bc2_parent_list_handle = '' %}

        {%- if bc3_parent_list_handle == '' -%}
          {%- for url in lurls -%}
            {%- if llevels[forloop.index0] == depth -%}
              {%- if object_urls contains url -%}
                {%- unless url == product.url or url == collection.url -%}
                  {%- capture bc2 -%}
{{ ltitles[forloop.index0] | link_to: url, ltitles[forloop.index0] }}{%- endcapture -%}
                {% endunless %}
                {%- assign bc2_parent_list_handle = lparents[forloop.index0] -%}
                {%- break -%}
              {%- endif -%}
            {%- endif -%}
          {%- endfor -%}
        {%- else -%}
          {%- for list_handle in lhandles -%}
            {%- if list_handle == bc3_parent_list_handle -%}
              {% assign bc2_list_handle = list_handle %}
              {%- assign bc2_parent_list_handle = lparents[forloop.index0] -%}
              {%- assign bc2_list_title = ltitles[forloop.index0] -%}
              {%- for bc2_sibling_link in linklists[bc2_parent_list_handle].links -%}
                {%- assign bc2_sibling_title_handleized = bc2_sibling_link.title | handle -%}
                {% if bc2_sibling_title_handleized == bc2_list_handle %}
                  {%- capture bc2 -%}
{{ bc2_sibling_link.title | link_to: bc2_sibling_link.url, bc2_sibling_link.title }}{%- endcapture -%}
                  {% break %}
                {%- endif -%}
              {%- endfor -%}
              {% break %}
            {%- endif -%}
          {%- endfor -%}
        {%- endif -%}

        {%- assign depth = depth | minus: 1 | append: '' -%}
        {%- assign bc1_parent_list_handle = '' %}

        {%- if bc2_parent_list_handle == '' -%}
          {% for url in lurls %}
            {%- if object_urls contains url and llevels[forloop.index0] == depth -%}
              {%- unless url == product.url or url == collection.url -%}
                {%- capture bc1 -%}
{{ ltitles[forloop.index0] | link_to: url, ltitles[forloop.index0] }}{%- endcapture -%}
              {% endunless %}
              {%- assign bc1_parent_list_handle = lparents[forloop.index0] -%}
              {%- break -%}
            {%- endif -%}
          {%- endfor -%}

        {%- else -%}
          {%- for list_handle in lhandles -%}
            {%- if bc2_parent_list_handle == list_handle -%}
              {% assign bc1_list_handle = list_handle %}
              {%- assign bc1_parent_list_handle = lparents[forloop.index0] -%}
              {%- assign bc1_title = ltitles[forloop.index0] -%}
              {%- for bc1_sibling_link in linklists[bc1_parent_list_handle].links -%}
                {%- assign bc1_sibling_title_handleized = bc1_sibling_link.title | handle -%}
                {% if bc1_sibling_title_handleized == bc1_list_handle %}
                  {%- capture bc1 -%}
{{ bc1_sibling_link.title | link_to: bc1_sibling_link.url, bc1_sibling_link.title }}{%- endcapture -%}
                  {% break %}
                {%- endif -%}
              {%- endfor -%}
            {%- endif -%}
          {%- endfor -%}
        {%- endif -%}

        {%- if bc1 -%}
          <span class="breadcrumb-delimeter" aria-hidden="true"></span>
          <!-- Breadcrumb Delimeter -->
          {{ bc1 }}
        {%- endif -%}
        {%- if bc2 -%}
          <span class="breadcrumb-delimeter" aria-hidden="true"></span>
          {{ bc2 }}
        {%- endif -%}
        {%- if bc3 -%}
          <span class="breadcrumb-delimeter" aria-hidden="true"></span>
          {{ bc3 }}
        {%- endif -%}

        <span class="breadcrumb-delimeter" aria-hidden="true"></span>
        <a href="{{ product.url }}">{{ product.title }}</a>
      {%- else -%}
        {% for link in linklists['main-menu'].links %}
          {% if link.child_active or link.active %}
            <span class="breadcrumb-delimeter" aria-hidden="true"></span>
            <!-- Breadcrumb delimeter -->
            <a href="{{ link.url }}">
              {{ link.title | escape }}
            </a>
            {% for clink in link.links %}
              {% if clink.child_active or clink.active %}
                <span class="breadcrumb-delimeter" aria-hidden="true"></span>
                <a href="{{ clink.url }}">
                  {{ clink.title | escape }}
                </a>
                {% for gclink in clink.links %}
                  {% if gclink.child_active or gclink.active %}
                    <span class="breadcrumb-delimeter" aria-hidden="true"></span>
                    <a href="{{ gclink.url }}">
                      {{ gclink.title | escape }}
                    </a>
                  {% endif %}
                {%- endfor -%}
              {% endif %}
            {%- endfor -%}
          {% endif %}
        {%- endfor -%}
    {%- endcase -%}
  {%- endunless -%}
</div>

<script defer>
  const breadCrumbLinks = document.querySelectorAll('.breadcrumbs a');
  const lastLink = breadCrumbLinks[breadCrumbLinks.length - 1];
  lastLink.href = 'javascript&colon;void(0)';
</script>

{% schema %}
{
  "name": "Breadcrumb Navigation",
  "settings": [
    {
      "type": "header",
      "content": "Subscribe to our [Shinetech Changchun](https://www.youtube.com/@ShinetechShopidev)"
    },
    {
      "type": "checkbox",
      "id": "breadcrumbs_home_icon_bool",
      "label": "Use the home icon next to the Home link in the breadcrumb",
      "default": true
    },
    {
      "type": "select",
      "id": "breadcrumb_delimeter",
      "label": "Breadcrumb Delimeter Icon",
      "options": [
        {
          "value": "angle_right",
          "label": "Angle Right"
        },
        {
          "value": "slash",
          "label": "Slash"
        },
        {
          "value": "arrow_right",
          "label": "Arrow Right"
        },
        {
          "value": "squiggle_arrow",
          "label": "Squiggle Arrow"
        },
        {
          "value": "right_long",
          "label": "Right Long"
        },
        {
          "value": "double_right",
          "label": "Double Right"
        },
        {
          "value": "diamond_arrow_head",
          "label": "Diamond Arrow Head"
        },
        {
          "value": "heavy_angle_right",
          "label": "Heavy Angle Right"
        }
      ],
      "default": "angle_right"
    },
    {
      "type": "color",
      "id": "breadcrumb_bg_color",
      "label": "Background Color",
      "default": "#fff"
    },
    {
      "type": "color",
      "id": "breadcrumb_text_color",
      "label": "Breadcrumb Color",
      "default": "#465076"
    },
    {
      "type": "checkbox",
      "id": "breadcrumb_accent_color_bool",
      "label": "Enable visited page color link in breadcrumb",
      "default": false
    },
    {
      "type": "color",
      "id": "breadcrumb_accent_color",
      "label": "Visited Page Link Color",
      "default": "#4770db"
    },
    {
      "type": "checkbox",
      "id": "enable_product_page",
      "label": "Enable in Product Page"
    },
    {
      "type": "checkbox",
      "id": "enable_collection_page",
      "label": "Enable in Collection Page"
    },
    {
      "type": "checkbox",
      "id": "enable_complete_site",
      "label": "Enable on Complete Website",
      "default": true
    }
  ],
  "presets": [
    {
      "name": "Breadcrumb Navigation"
    }
  ]
}
{% endschema %}


Adding and Customizing the Breadcrumb Section in Dawn 15 Theme

  1. Add Breadcrumb Section in the Theme Editor: Access the theme editor and add the newly created section to the page templates where you want the breadcrumbs to appear, such as the collection listing page, product listing page, and product detail page. Repeat the same process for any other pages where you wish to add breadcrumbs.

  2. Customize the Appearance: Adjust the color to match your brand. Position the newly added breadcrumbs section where desired.

By following these steps, your site will now feature functional breadcrumb navigation. 

Important:

This solution is tailored to the Dawn 15 theme. For other themes, custom adjustments may be necessary.

ByShinetech Changchun

Fri Oct 18 2024