The Easiest in Horizon theme is add it a block. Here is the Block code, simply copy and paste it as a block.
-
Go to Online Store > Themes → Edit code (on your duplicate)
-
In the Blocks folder, click Add a new Block
-
Name it breadcrumbs and paste this code:
{%- liquid
assign block_id = block.id
-%}
{% style %}
#breadcrumbs-{{ block_id }} {
display: flex;
align-items: center;
flex-wrap: wrap;
row-gap: {{ block.settings.row_gap }}px;
column-gap: {{ block.settings.item_gap }}px;
padding: {{ block.settings.padding_v }}px {{ block.settings.padding_h }}px;
margin: {{ block.settings.margin_top }}px 0 {{ block.settings.margin_bottom }}px 0;
background-color: {{ block.settings.bg_color }};
border-radius: {{ block.settings.border_radius }}px;
list-style: none;
opacity: 0;
animation: bcFadeIn-{{ block_id }} 0.5s ease forwards;
animation-delay: 0.1s;
{%- if block.settings.show_border -%}
border: {{ block.settings.border_width }}px solid {{ block.settings.border_color }};
{%- endif -%}
}
@keyframes bcFadeIn-{{ block_id }} {
to { opacity: 1; }
}
/* ── Crumb item wrapper ── */
#breadcrumbs-{{ block_id }} .bc__item {
display: inline-flex;
align-items: center;
gap: {{ block.settings.item_gap }}px;
opacity: 0;
animation: bcSlideIn-{{ block_id }} 0.4s ease forwards;
}
#breadcrumbs-{{ block_id }} .bc__item:nth-child(1) { animation-delay: 0.15s; }
#breadcrumbs-{{ block_id }} .bc__item:nth-child(2) { animation-delay: 0.25s; }
#breadcrumbs-{{ block_id }} .bc__item:nth-child(3) { animation-delay: 0.35s; }
#breadcrumbs-{{ block_id }} .bc__item:nth-child(4) { animation-delay: 0.45s; }
#breadcrumbs-{{ block_id }} .bc__item:nth-child(5) { animation-delay: 0.55s; }
#breadcrumbs-{{ block_id }} .bc__item:nth-child(6) { animation-delay: 0.65s; }
#breadcrumbs-{{ block_id }} .bc__item:nth-child(7) { animation-delay: 0.75s; }
#breadcrumbs-{{ block_id }} .bc__item:nth-child(8) { animation-delay: 0.85s; }
@keyframes bcSlideIn-{{ block_id }} {
from { opacity: 0; transform: translateX(-6px); }
to { opacity: 1; transform: translateX(0); }
}
/* ── Links ── */
#breadcrumbs-{{ block_id }} .bc__link {
font-size: {{ block.settings.text_size }}px;
font-weight: {{ block.settings.link_weight }};
color: {{ block.settings.link_color }};
text-decoration: {{ block.settings.link_decoration }};
line-height: 1.4;
transition: color 0.2s ease, opacity 0.2s ease;
white-space: nowrap;
}
#breadcrumbs-{{ block_id }} .bc__link:hover {
color: {{ block.settings.link_hover_color }};
text-decoration: underline;
}
/* ── Home icon ── */
#breadcrumbs-{{ block_id }} .bc__home-icon {
width: {{ block.settings.home_icon_size }}px;
height: {{ block.settings.home_icon_size }}px;
display: inline-flex;
align-items: center;
justify-content: center;
color: {{ block.settings.link_color }};
vertical-align: middle;
flex-shrink: 0;
transition: color 0.2s ease;
}
#breadcrumbs-{{ block_id }} .bc__home-icon:hover {
color: {{ block.settings.link_hover_color }};
}
#breadcrumbs-{{ block_id }} .bc__home-icon svg {
width: 100%;
height: 100%;
}
/* ── Separator ── */
#breadcrumbs-{{ block_id }} .bc__sep {
display: inline-flex;
align-items: center;
flex-shrink: 0;
color: {{ block.settings.sep_color }};
font-size: {{ block.settings.sep_size }}px;
line-height: 1;
user-select: none;
opacity: 0.{{ block.settings.sep_opacity }};
pointer-events: none;
}
#breadcrumbs-{{ block_id }} .bc__sep svg {
width: {{ block.settings.sep_size }}px;
height: {{ block.settings.sep_size }}px;
flex-shrink: 0;
}
/* ── Current (last) crumb ── */
#breadcrumbs-{{ block_id }} .bc__current {
font-size: {{ block.settings.text_size }}px;
font-weight: {{ block.settings.current_weight }};
color: {{ block.settings.current_color }};
line-height: 1.4;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: {{ block.settings.current_max_width }}px;
position: relative;
display: inline-block;
}
{%- if block.settings.show_current_underline -%}
#breadcrumbs-{{ block_id }} .bc__current::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
width: 100%;
height: {{ block.settings.underline_height }}px;
background-color: {{ block.settings.underline_color }};
border-radius: 2px;
transform: scaleX(0);
transform-origin: left center;
animation: bcUnderline-{{ block_id }} 0.5s ease forwards;
animation-delay: 0.8s;
}
@keyframes bcUnderline-{{ block_id }} {
to { transform: scaleX(1); }
}
{%- endif -%}
/* ── Ellipsis button (mobile collapsed mode) ── */
#breadcrumbs-{{ block_id }} .bc__ellipsis-btn {
background: none;
border: 1px solid {{ block.settings.sep_color }};
border-radius: 4px;
cursor: pointer;
font-size: {{ block.settings.text_size }}px;
color: {{ block.settings.link_color }};
padding: 0 5px;
line-height: 1.4;
transition: background 0.2s ease;
}
#breadcrumbs-{{ block_id }} .bc__ellipsis-btn:hover {
background-color: {{ block.settings.sep_color }};
color: #fff;
}
/* ── Mobile collapsed state ── */
@media screen and (max-width: 749px) {
#breadcrumbs-{{ block_id }} {
padding: {{ block.settings.padding_v | times: 0.75 | round }}px
{{ block.settings.padding_h | times: 0.75 | round }}px;
}
#breadcrumbs-{{ block_id }} .bc__link,
#breadcrumbs-{{ block_id }} .bc__current {
font-size: {{ block.settings.text_size | minus: 1 | at_least: 10 }}px;
}
{%- if block.settings.mobile_truncate -%}
/* Hide all middle items by default on mobile */
#breadcrumbs-{{ block_id }} .bc__item--middle {
display: none;
}
/* Show ellipsis toggle */
#breadcrumbs-{{ block_id }} .bc__item--ellipsis {
display: inline-flex !important;
}
/* When expanded, show middles and hide ellipsis */
#breadcrumbs-{{ block_id }}.bc--expanded .bc__item--middle {
display: inline-flex;
}
#breadcrumbs-{{ block_id }}.bc--expanded .bc__item--ellipsis {
display: none !important;
}
{%- endif -%}
}
@media screen and (min-width: 750px) {
#breadcrumbs-{{ block_id }} .bc__item--ellipsis {
display: none !important;
}
#breadcrumbs-{{ block_id }} .bc__item--middle {
display: inline-flex !important;
}
}
{% endstyle %}
{%- liquid
-%}
{%- comment -%} ── Build separator HTML inline ── {%- endcomment -%}
{%- liquid
assign sep_style = block.settings.separator_style
case sep_style
when 'slash'
assign sep_char = '/'
when 'dot'
assign sep_char = '·'
when 'arrow'
assign sep_char = '→'
when 'dash'
assign sep_char = '–'
else
assign sep_char = nil
endcase
-%}
{%- capture sep_html -%}
<span class="bc__sep" aria-hidden="true">
{%- if sep_style == 'chevron' -%}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2.5"
stroke-linecap="round" stroke-linejoin="round">
<polyline points="9 18 15 12 9 6"/>
</svg>
{%- elsif sep_style == 'arrow' -%}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<line x1="5" y1="12" x2="19" y2="12"/>
<polyline points="12 5 19 12 12 19"/>
</svg>
{%- else -%}
{{ sep_char }}
{%- endif -%}
</span>
{%- endcapture -%}
{%- comment -%}
── Build the full collection ancestry ──
Horizon exposes product.collections — we find the deepest
collection that has a parent chain by walking handle-based
ancestors if show_full_path is on.
{%- endcomment -%}
{%- liquid
assign show_full = block.settings.show_full_path
assign show_home = block.settings.show_home
assign home_style = block.settings.home_style
comment
Pick the "best" collection: prefer one whose handle is in the URL,
otherwise fall back to the first collection.
endcomment
assign current_col = nil
if product.collections.size > 0
for col in product.collections
if request.path contains col.handle
assign current_col = col
break
endif
endfor
if current_col == nil
assign current_col = product.collections.first
endif
endif
comment
Build ancestor chain.
Shopify doesn't expose parent collections directly, but we can
walk url_redirects. The safest cross-theme approach is to split
the collection's URL by '/' and attempt to resolve each segment
as a collection handle — giving us a breadcrumb trail from the URL path.
endcomment
assign col_segments = nil
if current_col != nil
assign col_url_clean = current_col.url | remove: '/collections/'
assign col_segments = col_url_clean | split: '/'
endif
-%}
<nav
id="breadcrumbs-{{ block_id }}"
aria-label="{{ block.settings.nav_aria_label }}"
{{ block.shopify_attributes }}
>
{%- comment -%} ── HOME ── {%- endcomment -%}
{%- if show_home -%}
<span class="bc__item">
<a
class="bc__link"
href="{{ routes.root_url }}"
aria-label="{{ block.settings.home_label }}"
>
{%- if home_style == 'icon' or home_style == 'both' -%}
<span class="bc__home-icon" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="1.8"
stroke-linecap="round" stroke-linejoin="round">
<path d="M3 9.5L12 3l9 6.5V20a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9.5z"/>
<polyline points="9 21 9 13 15 13 15 21"/>
</svg>
</span>
{%- endif -%}
{%- if home_style == 'text' or home_style == 'both' -%}
{{- block.settings.home_label -}}
{%- endif -%}
</a>
{{ sep_html }}
</span>
{%- endif -%}
{%- comment -%}
── MIDDLE CRUMBS ──
Two modes:
show_full_path = true → try to resolve every segment of the
collection URL as its own collection,
rendering each as a linked crumb.
show_full_path = false → show only the direct (single) collection.
{%- endcomment -%}
{%- if current_col != nil -%}
{%- if show_full and col_segments != nil and col_segments.size > 1 -%}
{%- comment -%}
Walk each URL segment. For each segment we try to find a
matching collection in product.collections; if found we link it,
otherwise we still render the segment title as plain text
(handles nested catalogue structures).
The LAST segment is the direct parent — it becomes one item
before the product crumb.
{%- endcomment -%}
{%- assign built_path = '/collections' -%}
{%- for segment in col_segments -%}
{%- assign built_path = built_path | append: '/' | append: segment -%}
{%- assign is_last_segment = false -%}
{%- assign seg_index = forloop.index0 -%}
{%- assign last_index = col_segments.size | minus: 1 -%}
{%- if seg_index == last_index -%}
{%- assign is_last_segment = true -%}
{%- endif -%}
{%- comment -%} Try to match this segment to a known collection {%- endcomment -%}
{%- assign matched_col = nil -%}
{%- for pc in product.collections -%}
{%- if pc.handle == segment -%}
{%- assign matched_col = pc -%}
{%- break -%}
{%- endif -%}
{%- endfor -%}
{%- assign seg_title = segment | replace: '-', ' ' | capitalize -%}
{%- if matched_col != nil -%}
{%- assign seg_title = matched_col.title -%}
{%- endif -%}
{%- assign seg_url = built_path -%}
{%- if is_last_segment -%}
{%- comment -%} Direct parent collection — NOT a middle item (always shown) {%- endcomment -%}
<span class="bc__item">
<a class="bc__link" href="{{ seg_url }}">{{ seg_title }}</a>
{{ sep_html }}
</span>
{%- else -%}
{%- comment -%} Ancestor — collapsible on mobile {%- endcomment -%}
<span class="bc__item bc__item--middle">
<a class="bc__link" href="{{ seg_url }}">{{ seg_title }}</a>
{{ sep_html }}
</span>
{%- endif -%}
{%- endfor -%}
{%- else -%}
{%- comment -%} Simple single-collection mode {%- endcomment -%}
<span class="bc__item bc__item--middle">
<a class="bc__link" href="{{ current_col.url }}">
{{ current_col.title }}
</a>
{{ sep_html }}
</span>
{%- endif -%}
{%- comment -%} Ellipsis toggle — only visible on mobile when truncating {%- endcomment -%}
{%- if block.settings.mobile_truncate -%}
<span class="bc__item bc__item--ellipsis" aria-hidden="true">
<button
class="bc__ellipsis-btn"
onclick="document.getElementById('breadcrumbs-{{ block_id }}').classList.add('bc--expanded');this.closest('.bc__item').style.display='none';"
aria-label="Show full path"
>···</button>
{{ sep_html }}
</span>
{%- endif -%}
{%- endif -%}
{%- comment -%} ── PRODUCT (current page) ── {%- endcomment -%}
<span class="bc__item">
<span class="bc__current" aria-current="page">
{%- if block.settings.current_show_title -%}
{{- product.title -}}
{%- else -%}
{{- block.settings.current_custom_text -}}
{%- endif -%}
</span>
</span>
</nav>
{% schema %}
{
"name": "Breadcrumbs",
"settings": [
{
"type": "header",
"content": "🗂️ Trail Options"
},
{
"type": "checkbox",
"id": "show_full_path",
"label": "Show full collection path",
"info": "When enabled, every ancestor in the collection URL is shown as a separate crumb (e.g. Home › Clothing › Tops › T-Shirts › Product). When disabled, only the immediate parent collection is shown.",
"default": true
},
{
"type": "header",
"content": "🏠 Home Crumb"
},
{
"type": "checkbox",
"id": "show_home",
"label": "Show home link",
"default": true
},
{
"type": "select",
"id": "home_style",
"label": "Home display style",
"options": [
{ "value": "icon", "label": "Icon only" },
{ "value": "text", "label": "Text only" },
{ "value": "both", "label": "Icon + Text" }
],
"default": "icon"
},
{
"type": "text",
"id": "home_label",
"label": "Home label / aria-label",
"default": "Home"
},
{
"type": "range",
"id": "home_icon_size",
"label": "Home icon size",
"min": 12,
"max": 24,
"step": 1,
"unit": "px",
"default": 15
},
{
"type": "header",
"content": "📄 Current Page Crumb"
},
{
"type": "checkbox",
"id": "current_show_title",
"label": "Use product title",
"info": "Disable to set a custom label below",
"default": true
},
{
"type": "text",
"id": "current_custom_text",
"label": "Custom current page text",
"default": "Product"
},
{
"type": "range",
"id": "current_max_width",
"label": "Current crumb max-width (ellipsis)",
"min": 80,
"max": 500,
"step": 10,
"unit": "px",
"default": 220
},
{
"type": "checkbox",
"id": "show_current_underline",
"label": "Animate underline on current crumb",
"default": true
},
{
"type": "range",
"id": "underline_height",
"label": "Underline thickness",
"min": 1,
"max": 4,
"step": 1,
"unit": "px",
"default": 2
},
{
"type": "color",
"id": "underline_color",
"label": "Underline color",
"default": "#111111"
},
{
"type": "header",
"content": "➤ Separator"
},
{
"type": "select",
"id": "separator_style",
"label": "Separator style",
"options": [
{ "value": "chevron", "label": "› Chevron (SVG)" },
{ "value": "slash", "label": "/ Slash" },
{ "value": "dot", "label": "· Dot" },
{ "value": "arrow", "label": "→ Arrow (SVG)" },
{ "value": "dash", "label": "– Dash" }
],
"default": "chevron"
},
{
"type": "range",
"id": "sep_size",
"label": "Separator size",
"min": 8,
"max": 20,
"step": 1,
"unit": "px",
"default": 12
},
{
"type": "color",
"id": "sep_color",
"label": "Separator color",
"default": "#aaaaaa"
},
{
"type": "range",
"id": "sep_opacity",
"label": "Separator opacity",
"info": "1 = 10% · 10 = 100%",
"min": 1,
"max": 10,
"step": 1,
"default": 6
},
{
"type": "header",
"content": "📱 Mobile"
},
{
"type": "checkbox",
"id": "mobile_truncate",
"label": "Collapse ancestor crumbs on mobile",
"info": "Replaces hidden ancestors with a ··· button that expands on tap",
"default": true
},
{
"type": "header",
"content": "🎨 Colors"
},
{
"type": "color",
"id": "bg_color",
"label": "Background",
"default": "#ffffff"
},
{
"type": "color",
"id": "link_color",
"label": "Link / ancestor color",
"default": "#6b7280"
},
{
"type": "color",
"id": "link_hover_color",
"label": "Link hover color",
"default": "#111111"
},
{
"type": "color",
"id": "current_color",
"label": "Current page color",
"default": "#111111"
},
{
"type": "header",
"content": "🔤 Typography"
},
{
"type": "range",
"id": "text_size",
"label": "Text size",
"min": 10,
"max": 18,
"step": 1,
"unit": "px",
"default": 13
},
{
"type": "select",
"id": "link_weight",
"label": "Ancestor link weight",
"options": [
{ "value": "300", "label": "Light" },
{ "value": "400", "label": "Normal" },
{ "value": "500", "label": "Medium" },
{ "value": "600", "label": "Semi-bold" }
],
"default": "400"
},
{
"type": "select",
"id": "current_weight",
"label": "Current page weight",
"options": [
{ "value": "400", "label": "Normal" },
{ "value": "500", "label": "Medium" },
{ "value": "600", "label": "Semi-bold" },
{ "value": "700", "label": "Bold" }
],
"default": "600"
},
{
"type": "select",
"id": "link_decoration",
"label": "Link underline",
"options": [
{ "value": "none", "label": "None" },
{ "value": "underline", "label": "Underline" }
],
"default": "none"
},
{
"type": "header",
"content": "♿ Accessibility"
},
{
"type": "text",
"id": "nav_aria_label",
"label": "Nav aria-label",
"default": "Breadcrumb"
},
{
"type": "header",
"content": "📐 Layout"
},
{
"type": "range",
"id": "padding_v",
"label": "Padding top / bottom",
"min": 0,
"max": 24,
"step": 2,
"unit": "px",
"default": 8
},
{
"type": "range",
"id": "padding_h",
"label": "Padding left / right",
"min": 0,
"max": 32,
"step": 2,
"unit": "px",
"default": 0
},
{
"type": "range",
"id": "item_gap",
"label": "Horizontal gap between items",
"min": 2,
"max": 20,
"step": 1,
"unit": "px",
"default": 6
},
{
"type": "range",
"id": "row_gap",
"label": "Row gap (when wrapping)",
"min": 0,
"max": 12,
"step": 1,
"unit": "px",
"default": 4
},
{
"type": "range",
"id": "border_radius",
"label": "Border radius",
"min": 0,
"max": 16,
"step": 2,
"unit": "px",
"default": 0
},
{
"type": "checkbox",
"id": "show_border",
"label": "Show border",
"default": false
},
{
"type": "range",
"id": "border_width",
"label": "Border width",
"min": 1,
"max": 3,
"step": 1,
"unit": "px",
"default": 1
},
{
"type": "color",
"id": "border_color",
"label": "Border color",
"default": "#e5e7eb"
},
{
"type": "range",
"id": "margin_top",
"label": "Margin top",
"min": 0,
"max": 40,
"step": 4,
"unit": "px",
"default": 0
},
{
"type": "range",
"id": "margin_bottom",
"label": "Margin bottom",
"min": 0,
"max": 40,
"step": 4,
"unit": "px",
"default": 8
}
],
"presets": [
{
"name": "Breadcrumbs"
}
]
}
{% endschema %}