← All sections
Collection Accordion
Big expanding doors into your collections, each one opening onto its bestsellers.
Install
One file: sections/mf-collection-accordion.liquid. It appears in the theme editor as EX · Collection accordion.
- Connect EXPERAISE Sections to Claude (once). In Claude Code:In the Claude app: Settings → Connectors → Add custom connector →
claude mcp add --transport http experaise-sections https://sections.experaise.com/api/mcphttps://sections.experaise.com/api/mcp. Full setup - Ask Claude. It fetches
sections/mf-collection-accordion.liquid, pushes only that file to your theme with Shopify CLI, and tells you where to find it.Install the EXPERAISE Sections section "Collection Accordion" (collection-accordion) on my Shopify store your-store.myshopify.com. Put it on an unpublished copy of my live theme, then tell me how to add it in the theme editor. - Customize. Online Store → Themes → Customize → Add section → EX · Collection accordion. Every text, color and product is a setting.
{%- liquid
assign s = section.settings
assign heading_tag = s.heading_tag | default: 'h2'
assign panel_count = section.blocks.size
assign shade = s.overlay | divided_by: 100.0
-%}
<mf-collection-accordion
class="mf-acc mf-acc--accent-{{ s.accent_style }}"
data-autoplay="{{ s.autoplay }}"
data-hover="{{ s.hover_open }}"
style="--mf-acc-bg: {{ s.color_bg }}; --mf-acc-fg: {{ s.color_text }}; --mf-acc-accent: {{ s.color_accent }}; --mf-acc-pt: {{ s.padding_top }}px; --mf-acc-pb: {{ s.padding_bottom }}px; --mf-acc-h: {{ s.panel_height }}px; --mf-acc-radius: {{ s.radius }}px; --mf-acc-gap: {{ s.gap }}px; --mf-acc-grow: {{ s.open_width }}; --mf-acc-cycle: {{ s.cycle }}s; --mf-acc-shade: {{ shade }}; --mf-acc-count: {{ panel_count }};"
>
<div class="mf-acc__glow" aria-hidden="true"></div>
<div class="mf-acc__grain" aria-hidden="true"></div>
<div class="mf-acc__inner">
{%- if s.eyebrow != blank or s.heading != blank or s.text != blank or s.link_label != blank -%}
<div class="mf-acc__head">
<div class="mf-acc__intro">
{%- if s.eyebrow != blank -%}
<p class="mf-acc__eyebrow"><i aria-hidden="true"></i>{{ s.eyebrow | escape }}</p>
{%- endif -%}
{%- if s.heading != blank -%}
<{{ heading_tag }} class="mf-acc__heading">
{{- s.heading -}}
{%- if s.heading_accent != blank %} <span class="mf-acc__accent">{{ s.heading_accent }}</span>{% endif -%}
</{{ heading_tag }}>
{%- endif -%}
</div>
{%- if s.text != blank or s.link_label != blank -%}
<div class="mf-acc__aside">
{%- if s.text != blank -%}
<div class="mf-acc__lede">{{ s.text }}</div>
{%- endif -%}
{%- if s.link_label != blank -%}
<a class="mf-acc__all" href="{{ s.link_url | default: routes.collections_url }}">
{{- s.link_label | escape -}}
<svg viewBox="0 0 20 20" width="16" height="16" aria-hidden="true" focusable="false"><path d="M4 10h11m-4-5 5 5-5 5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
{%- endif -%}
</div>
{%- endif -%}
</div>
{%- endif -%}
{%- if panel_count > 0 -%}
<div class="mf-acc__panels">
{%- for block in section.blocks -%}
{%- liquid
assign b = block.settings
assign c = b.collection
assign label = b.label | default: c.title | default: 'Collection'
assign url = c.url | default: routes.collections_url
assign img = b.image | default: c.featured_image
assign art = forloop.index0 | modulo: 5 | plus: 1
assign body_id = 'MfAcc-' | append: section.id | append: '-' | append: forloop.index
if forloop.index < 10
assign num = forloop.index | prepend: '0'
else
assign num = forloop.index
endif
if forloop.first
assign loading = 'eager'
else
assign loading = 'lazy'
endif
-%}
<div
class="mf-acc__panel{% if forloop.first %} is-active{% endif %}"
style="--i: {{ forloop.index0 }};"
{{ block.shopify_attributes }}
>
<div class="mf-acc__media" aria-hidden="true">
{%- if img != blank -%}
{{
img
| image_url: width: 1800
| image_tag:
loading: loading,
alt: '',
sizes: '(min-width: 750px) 72vw, 100vw',
widths: '480, 720, 960, 1280, 1600, 1800',
class: 'mf-acc__img'
}}
{%- else -%}
<span class="mf-acc__art mf-acc__art--{{ art }}"><span></span></span>
{%- endif -%}
</div>
<span class="mf-acc__shade" aria-hidden="true"></span>
<span class="mf-acc__dim" aria-hidden="true"></span>
<a class="mf-acc__cover" href="{{ url }}" tabindex="-1" aria-hidden="true"></a>
<button
type="button"
class="mf-acc__trigger"
aria-expanded="{% if forloop.first %}true{% else %}false{% endif %}"
aria-controls="{{ body_id }}"
>
<span class="mf-acc__num">{{ num }}<span class="mf-acc__bar" aria-hidden="true"><span class="mf-acc__fill"></span></span></span>
<span class="mf-acc__label">{{ label | escape }}</span>
<span class="mf-acc__plus" aria-hidden="true"></span>
</button>
<div class="mf-acc__body" id="{{ body_id }}">
{%- if s.show_count and c != blank -%}
<span class="mf-acc__count">{{ c.products_count }} {{ s.count_label | escape }}</span>
{%- endif -%}
<div class="mf-acc__foot">
<div class="mf-acc__copy">
<h3 class="mf-acc__title">{{ label | escape }}</h3>
{%- if b.text != blank -%}
<p class="mf-acc__text">{{ b.text | escape }}</p>
{%- endif -%}
{%- if b.button_label != blank -%}
<a class="mf-acc__cta" href="{{ url }}">
{{- b.button_label | escape -}}
<span class="mf-acc__cta-ico" aria-hidden="true">
<svg viewBox="0 0 20 20" width="14" height="14" focusable="false"><path d="M4 10h11m-4-5 5 5-5 5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
</span>
</a>
{%- endif -%}
</div>
{%- if s.peek_count > 0 and c != blank and c.products.size > 0 -%}
<ul class="mf-acc__peeks" role="list">
{%- for item in c.products limit: s.peek_count -%}
<li class="mf-acc__peek-item" style="--p: {{ forloop.index0 }};">
<a class="mf-acc__peek" href="{{ item.url }}" aria-label="{{ item.title | escape }}, {{ item.price | money | strip_html | escape }}">
{%- if item.featured_media != blank -%}
{{
item.featured_media
| image_url: width: 300
| image_tag:
loading: 'lazy',
alt: '',
sizes: '(min-width: 750px) 104px, 64px',
widths: '120, 200, 300',
class: 'mf-acc__peek-img'
}}
{%- else -%}
<span class="mf-acc__peek-art mf-acc__art--{{ forloop.index | plus: art | modulo: 5 | plus: 1 }}"></span>
{%- endif -%}
<span class="mf-acc__price">{{ item.price | money }}</span>
</a>
</li>
{%- endfor -%}
</ul>
{%- endif -%}
</div>
</div>
</div>
{%- endfor -%}
</div>
{%- endif -%}
</div>
</mf-collection-accordion>
{% stylesheet %}
/* Collection accordion: a row of tall panels, one open at a time. The open
panel grows (flex-grow) while every image keeps a fixed width, so opening
a panel reveals more of its picture instead of rescaling it. Text and
product previews ride in with transform/opacity only. Below 750px the row
becomes a vertical accordion. */
.mf-acc {
position: relative;
isolation: isolate;
display: block;
overflow: hidden;
background: var(--mf-acc-bg, #0d0c0b);
color: var(--mf-acc-fg, #f4f1ea);
font-family: inherit;
}
.mf-acc *,
.mf-acc *::before,
.mf-acc *::after {
box-sizing: border-box;
}
.mf-acc__glow {
position: absolute;
z-index: -1;
inset: 0;
pointer-events: none;
background:
radial-gradient(38% 46% at 8% 0%, color-mix(in srgb, var(--mf-acc-accent) 16%, transparent), transparent 70%),
radial-gradient(50% 40% at 92% 100%, color-mix(in srgb, var(--mf-acc-accent) 9%, transparent), transparent 75%),
radial-gradient(70% 55% at 50% 72%, color-mix(in srgb, var(--mf-acc-fg) 5%, transparent), transparent 80%);
}
.mf-acc__grain {
position: absolute;
z-index: -1;
inset: 0;
pointer-events: none;
opacity: 0.55;
background-image:
radial-gradient(color-mix(in srgb, var(--mf-acc-fg) 7%, transparent) 0.6px, transparent 0.9px),
radial-gradient(color-mix(in srgb, var(--mf-acc-fg) 5%, transparent) 0.6px, transparent 0.9px);
background-size: 3px 3px, 5px 5px;
background-position: 0 0, 1px 2px;
}
.mf-acc__inner {
width: min(100% - 40px, 1320px);
margin: 0 auto;
padding: var(--mf-acc-pt, 88px) 0 var(--mf-acc-pb, 88px);
}
/* ---------- heading ---------- */
.mf-acc__head {
display: grid;
grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
align-items: end;
gap: 24px 64px;
margin: 0 0 clamp(32px, 4vw, 52px);
}
.mf-acc__eyebrow {
display: inline-flex;
align-items: center;
gap: 10px;
margin: 0 0 20px;
color: color-mix(in srgb, var(--mf-acc-fg) 66%, transparent);
font-size: 12px;
font-weight: 600;
line-height: 1.2;
letter-spacing: 0.16em;
text-transform: uppercase;
}
.mf-acc__eyebrow i {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--mf-acc-accent);
box-shadow: 0 0 0 4px color-mix(in srgb, var(--mf-acc-accent) 20%, transparent), 0 0 18px var(--mf-acc-accent);
}
.mf-acc__heading {
margin: 0;
color: var(--mf-acc-fg);
font-family: inherit;
font-size: clamp(38px, 4.9vw, 72px);
font-weight: 600;
line-height: 0.98;
letter-spacing: -0.04em;
text-transform: none;
text-wrap: balance;
}
.mf-acc__accent {
display: inline;
}
.mf-acc--accent-serif .mf-acc__accent {
font-family: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
font-style: italic;
font-weight: 400;
letter-spacing: -0.025em;
color: color-mix(in srgb, var(--mf-acc-fg) 78%, var(--mf-acc-accent));
}
.mf-acc--accent-muted .mf-acc__accent {
color: color-mix(in srgb, var(--mf-acc-fg) 46%, transparent);
}
.mf-acc__aside {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 18px;
max-width: 420px;
justify-self: end;
padding-bottom: 6px;
}
.mf-acc__lede {
color: color-mix(in srgb, var(--mf-acc-fg) 66%, transparent);
font-size: 16px;
line-height: 1.6;
letter-spacing: 0;
}
.mf-acc__lede p {
margin: 0;
color: inherit;
font-size: inherit;
line-height: inherit;
}
.mf-acc__all {
display: inline-flex;
align-items: center;
gap: 8px;
min-height: 40px;
padding: 0 18px;
border: 1px solid color-mix(in srgb, var(--mf-acc-fg) 22%, transparent);
border-radius: 999px;
color: var(--mf-acc-fg);
font-size: 14px;
font-weight: 500;
line-height: 1;
letter-spacing: 0;
text-decoration: none;
text-transform: none;
transition: border-color 0.3s ease, background-color 0.3s ease;
}
.mf-acc__all svg {
transition: transform 0.3s ease;
}
.mf-acc__all:hover {
border-color: color-mix(in srgb, var(--mf-acc-fg) 55%, transparent);
background: color-mix(in srgb, var(--mf-acc-fg) 6%, transparent);
}
.mf-acc__all:hover svg {
transform: translateX(3px);
}
/* ---------- panels ---------- */
.mf-acc__panels {
position: relative;
display: flex;
gap: var(--mf-acc-gap, 10px);
height: var(--mf-acc-h, 540px);
container-type: inline-size;
/* The open panel's final width, solved like flexbox does (closed strips
never go below their 64px minimum). Images and the content layer are
laid out at this width from the start, so nothing rescales or reflows
while a panel opens: the panel simply uncovers them. */
--mf-acc-room: calc(100cqw - (var(--mf-acc-count, 4) - 1) * var(--mf-acc-gap, 10px));
--mf-acc-closed: max(64px, calc(var(--mf-acc-room) / (var(--mf-acc-grow, 6) + var(--mf-acc-count, 4) - 1)));
--mf-acc-open-w: calc(var(--mf-acc-room) - (var(--mf-acc-count, 4) - 1) * var(--mf-acc-closed));
}
.mf-acc__panel {
--mf-acc-ease: cubic-bezier(0.65, 0, 0.2, 1);
position: relative;
flex: 1 1 0;
min-width: 64px;
overflow: hidden;
border-radius: var(--mf-acc-radius, 22px);
background: color-mix(in srgb, var(--mf-acc-fg) 8%, var(--mf-acc-bg));
box-shadow: 0 30px 60px -36px rgba(0, 0, 0, 0.7);
transition: flex-grow 0.85s var(--mf-acc-ease);
}
.mf-acc__panel.is-active {
flex-grow: var(--mf-acc-grow, 6);
}
.mf-acc__panel::after {
content: "";
position: absolute;
z-index: 6;
inset: 0;
border-radius: inherit;
pointer-events: none;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.09);
}
.mf-acc__media {
position: absolute;
z-index: 0;
inset: 0;
overflow: hidden;
}
.mf-acc__img,
.mf-acc__art {
position: absolute;
top: 0;
left: 50%;
width: max(100%, var(--mf-acc-open-w, 74cqw));
max-width: none;
height: 100%;
margin: 0;
object-fit: cover;
transform: translateX(-50%) scale(1.12);
transition: transform 1.4s var(--mf-acc-ease);
}
.mf-acc__panel.is-active .mf-acc__img,
.mf-acc__panel.is-active .mf-acc__art {
transform: translateX(-50%) scale(1);
}
/* designed fallback art (no image, no collection image) */
.mf-acc__art {
display: block;
overflow: hidden;
}
.mf-acc__art > span {
position: absolute;
left: 50%;
bottom: 12%;
width: 15%;
min-width: 70px;
aspect-ratio: 5 / 11;
border-radius: 40px 40px 14px 14px;
transform: translateX(10%);
background: linear-gradient(90deg, rgba(0, 0, 0, 0.18), rgba(255, 255, 255, 0.28) 30%, rgba(255, 255, 255, 0.05) 60%, rgba(0, 0, 0, 0.2));
box-shadow: 0 40px 60px -20px rgba(0, 0, 0, 0.45), inset 0 0 0 1px rgba(255, 255, 255, 0.25);
}
.mf-acc__art::before {
content: "";
position: absolute;
top: 14%;
left: 38%;
width: 46%;
aspect-ratio: 1;
border-radius: 50%;
background: radial-gradient(circle at 35% 30%, rgba(255, 255, 255, 0.75), rgba(255, 255, 255, 0) 62%);
opacity: 0.7;
}
.mf-acc__art::after {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 24%;
background: linear-gradient(180deg, rgba(0, 0, 0, 0.08), rgba(0, 0, 0, 0.28));
}
.mf-acc__art--1 {
background: radial-gradient(60% 70% at 70% 30%, #ffd2a8 0%, transparent 70%), linear-gradient(160deg, #f6e2cc 0%, #e0a16e 55%, #8a4a25 100%);
}
.mf-acc__art--2 {
background: radial-gradient(60% 70% at 70% 30%, #eef4e6 0%, transparent 70%), linear-gradient(160deg, #dfe8d6 0%, #93a882 55%, #3d4c35 100%);
}
.mf-acc__art--3 {
background: radial-gradient(60% 70% at 70% 30%, #f2ecff 0%, transparent 70%), linear-gradient(160deg, #e6defa 0%, #a08ad6 55%, #4a3a7d 100%);
}
.mf-acc__art--4 {
background: radial-gradient(60% 70% at 70% 30%, #fff1df 0%, transparent 70%), linear-gradient(160deg, #f3e7d8 0%, #c9a77c 55%, #5f4529 100%);
}
.mf-acc__art--5 {
background: radial-gradient(60% 70% at 70% 30%, #dceaff 0%, transparent 70%), linear-gradient(160deg, #d9e4f2 0%, #6f8fb8 55%, #1f2f4a 100%);
}
.mf-acc__shade {
position: absolute;
z-index: 1;
inset: 0;
pointer-events: none;
background:
linear-gradient(180deg, rgb(8 7 6 / calc(var(--mf-acc-shade, 0.6) * 0.55)) 0%, rgb(8 7 6 / 0) 26%),
linear-gradient(0deg, rgb(8 7 6 / var(--mf-acc-shade, 0.6)) 0%, rgb(8 7 6 / calc(var(--mf-acc-shade, 0.6) * 0.5)) 30%, rgb(8 7 6 / 0) 62%);
}
.mf-acc__dim {
position: absolute;
z-index: 1;
inset: 0;
pointer-events: none;
background: linear-gradient(0deg, rgba(8, 7, 6, 0.74), rgba(8, 7, 6, 0.3) 55%, rgba(8, 7, 6, 0.14));
opacity: 1;
transition: opacity 0.6s ease;
}
.mf-acc__panel.is-active .mf-acc__dim {
opacity: 0;
}
.mf-acc__panel:not(.is-active):hover .mf-acc__dim {
opacity: 0.55;
}
@media screen and (min-width: 750px) and (hover: hover) {
.mf-acc__panel:not(.is-active):hover .mf-acc__img,
.mf-acc__panel:not(.is-active):hover .mf-acc__art {
transform: translateX(-50%) scale(1.06);
}
}
.mf-acc__cover {
position: absolute;
z-index: 2;
inset: 0;
display: none;
}
.mf-acc__panel.is-active .mf-acc__cover {
display: block;
}
/* Without JavaScript the triggers cannot open panels, so every panel
becomes a direct link to its collection instead. */
mf-collection-accordion:not(:defined) .mf-acc__cover {
display: block;
z-index: 6;
}
.mf-acc__trigger {
-webkit-appearance: none;
appearance: none;
position: absolute;
z-index: 3;
inset: 0;
display: block;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
border: 0;
border-radius: inherit;
background: transparent;
color: #fff;
font: inherit;
font-size: 15px;
line-height: 1.2;
letter-spacing: 0;
text-align: left;
text-transform: none;
cursor: pointer;
}
.mf-acc__panel.is-active .mf-acc__trigger {
pointer-events: none;
cursor: default;
}
.mf-acc__trigger:focus-visible {
outline: 2px solid var(--mf-acc-accent);
outline-offset: -4px;
}
.mf-acc__num {
position: absolute;
top: 16px;
left: 14px;
display: inline-flex;
align-items: center;
gap: 10px;
height: 30px;
padding: 0 10px;
border-radius: 999px;
color: rgba(255, 255, 255, 0.8);
font-size: 13px;
font-weight: 600;
line-height: 1;
letter-spacing: 0.06em;
font-variant-numeric: tabular-nums;
transition: color 0.4s ease, background-color 0.4s ease, box-shadow 0.4s ease;
}
.mf-acc__panel.is-active .mf-acc__num {
padding: 0 12px;
background: rgba(12, 11, 10, 0.42);
-webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(12px);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.16);
color: #fff;
}
.mf-acc__panel.is-active .mf-acc__num::before {
content: "";
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--mf-acc-accent);
box-shadow: 0 0 10px var(--mf-acc-accent);
order: -1;
margin-right: -2px;
}
.mf-acc__label {
position: absolute;
left: 20px;
bottom: 66px;
max-height: calc(100% - 132px);
overflow: hidden;
color: #fff;
font-size: 17px;
font-weight: 600;
line-height: 1.2;
letter-spacing: -0.01em;
white-space: nowrap;
text-overflow: ellipsis;
writing-mode: vertical-rl;
transform: rotate(180deg);
transition: opacity 0.35s ease 0.25s, transform 0.5s var(--mf-acc-ease) 0.25s;
}
.mf-acc__panel.is-active .mf-acc__label {
opacity: 0;
transform: rotate(180deg) translateY(-14px);
transition-delay: 0s;
}
.mf-acc__plus {
position: absolute;
left: 14px;
bottom: 16px;
display: block;
width: 34px;
height: 34px;
border: 1px solid rgba(255, 255, 255, 0.34);
border-radius: 50%;
background: rgba(12, 11, 10, 0.28);
-webkit-backdrop-filter: blur(8px);
backdrop-filter: blur(8px);
transition: opacity 0.3s ease, transform 0.5s var(--mf-acc-ease), background-color 0.3s ease, border-color 0.3s ease;
}
.mf-acc__plus::before,
.mf-acc__plus::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 12px;
height: 1.5px;
margin: -0.75px 0 0 -6px;
border-radius: 2px;
background: #fff;
transition: background-color 0.3s ease;
}
.mf-acc__plus::after {
transform: rotate(90deg);
}
.mf-acc__panel:not(.is-active):hover .mf-acc__plus {
border-color: var(--mf-acc-accent);
background: var(--mf-acc-accent);
transform: rotate(90deg);
}
.mf-acc__panel:not(.is-active):hover .mf-acc__plus::before,
.mf-acc__panel:not(.is-active):hover .mf-acc__plus::after {
background: #111;
}
.mf-acc__panel.is-active .mf-acc__plus {
opacity: 0;
transform: rotate(90deg) scale(0.6);
}
/* ---------- open panel content ---------- */
.mf-acc__body {
position: absolute;
z-index: 4;
inset: 0;
pointer-events: none;
color: #fff;
}
.mf-acc__panel.is-active .mf-acc__body a {
pointer-events: auto;
}
.mf-acc__bar {
display: none;
width: 48px;
height: 2px;
overflow: hidden;
border-radius: 2px;
background: rgba(255, 255, 255, 0.24);
}
.mf-acc.is-playing .mf-acc__panel.is-active .mf-acc__bar {
display: block;
}
.mf-acc__fill {
display: block;
width: 100%;
height: 100%;
background: var(--mf-acc-accent);
box-shadow: 0 0 12px var(--mf-acc-accent);
transform: scaleX(0);
transform-origin: left center;
}
.mf-acc.is-playing .mf-acc__panel.is-active .mf-acc__fill {
animation: mf-acc-progress var(--mf-acc-cycle, 7s) linear forwards;
}
.mf-acc.is-hovering .mf-acc__fill,
.mf-acc.is-offscreen .mf-acc__fill {
animation-play-state: paused;
}
.mf-acc__count {
position: absolute;
top: 16px;
right: 16px;
display: inline-flex;
align-items: center;
min-height: 30px;
padding: 0 12px;
border: 1px solid rgba(255, 255, 255, 0.24);
border-radius: 999px;
background: rgba(12, 11, 10, 0.42);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
color: rgba(255, 255, 255, 0.92);
font-size: 12px;
font-weight: 500;
line-height: 1;
letter-spacing: 0.02em;
white-space: nowrap;
}
.mf-acc__foot {
position: absolute;
left: 0;
bottom: 0;
width: max(100%, var(--mf-acc-open-w, 100%));
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 32px;
padding: clamp(22px, 2.6vw, 36px);
}
.mf-acc__copy {
flex: 0 1 auto;
min-width: 0;
max-width: 520px;
}
.mf-acc__title {
margin: 0;
color: #fff;
font-family: inherit;
font-size: clamp(34px, 3.9vw, 58px);
font-weight: 600;
line-height: 1;
letter-spacing: -0.035em;
text-transform: none;
text-wrap: balance;
overflow-wrap: break-word;
}
.mf-acc__text {
max-width: 40ch;
margin: 14px 0 0;
color: rgba(255, 255, 255, 0.8);
font-size: 15px;
line-height: 1.55;
letter-spacing: 0;
text-transform: none;
}
.mf-acc__cta {
display: inline-flex;
align-items: center;
gap: 12px;
min-height: 46px;
margin-top: 22px;
padding: 0 6px 0 20px;
border-radius: 999px;
background: #fff;
color: #111;
font-size: 14px;
font-weight: 600;
line-height: 1;
letter-spacing: 0;
text-decoration: none;
text-transform: none;
white-space: nowrap;
box-shadow: 0 14px 34px -16px rgba(0, 0, 0, 0.6);
}
.mf-acc__cta-ico {
display: inline-flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
border-radius: 50%;
background: var(--mf-acc-accent);
color: #111;
transition: transform 0.35s var(--mf-acc-ease);
}
.mf-acc__cta:hover .mf-acc__cta-ico {
transform: rotate(-45deg);
}
.mf-acc__cta:focus-visible,
.mf-acc__peek:focus-visible,
.mf-acc__all:focus-visible {
outline: 2px solid var(--mf-acc-accent);
outline-offset: 3px;
}
/* staggered reveal of the open panel's copy */
.mf-acc__title,
.mf-acc__text,
.mf-acc__cta,
.mf-acc__count {
opacity: 0;
transform: translateY(16px);
transition: opacity 0.25s ease, transform 0.25s ease;
}
.mf-acc__panel.is-active .mf-acc__title,
.mf-acc__panel.is-active .mf-acc__text,
.mf-acc__panel.is-active .mf-acc__cta,
.mf-acc__panel.is-active .mf-acc__count {
opacity: 1;
transform: none;
transition: opacity 0.6s ease, transform 0.8s var(--mf-acc-ease);
}
.mf-acc__panel.is-active .mf-acc__count { transition-delay: 0.35s; }
.mf-acc__panel.is-active .mf-acc__title { transition-delay: 0.38s; }
.mf-acc__panel.is-active .mf-acc__text { transition-delay: 0.46s; }
.mf-acc__panel.is-active .mf-acc__cta { transition-delay: 0.54s; }
/* product previews */
.mf-acc__peeks {
display: flex;
flex: none;
align-items: flex-end;
margin: 0;
padding: 0;
list-style: none;
}
.mf-acc__peek-item {
margin: 0 0 0 -14px;
padding: 0;
opacity: 0;
transform: translateY(26px) rotate(calc((var(--p, 0) - 1) * 4deg));
transition: opacity 0.25s ease, transform 0.3s ease;
}
.mf-acc__peek-item:first-child {
margin-left: 0;
}
.mf-acc__panel.is-active .mf-acc__peek-item {
opacity: 1;
transform: translateY(calc(var(--p, 0) * -10px)) rotate(calc((var(--p, 0) - 1) * 3deg));
transition: opacity 0.6s ease, transform 0.9s var(--mf-acc-ease);
transition-delay: calc(0.5s + var(--p, 0) * 0.09s);
}
.mf-acc__peek {
position: relative;
display: block;
width: clamp(78px, 7.4vw, 104px);
aspect-ratio: 4 / 5;
overflow: hidden;
border-radius: 14px;
background: #ece7df;
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.5), 0 18px 34px -14px rgba(0, 0, 0, 0.6);
color: #111;
text-decoration: none;
transition: transform 0.4s var(--mf-acc-ease);
}
.mf-acc__peek:hover {
transform: translateY(-6px) rotate(0deg);
}
.mf-acc__peek-img,
.mf-acc__peek-art {
display: block;
width: 100%;
height: 100%;
max-width: none;
margin: 0;
object-fit: cover;
}
.mf-acc__price {
position: absolute;
left: 6px;
bottom: 6px;
padding: 4px 7px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.92);
color: #111;
font-size: 11px;
font-weight: 600;
line-height: 1;
letter-spacing: 0;
white-space: nowrap;
}
/* preview-only scripted cursor */
.mf-acc__ghost {
position: absolute;
z-index: 10;
top: 0;
left: 0;
width: 26px;
height: 26px;
pointer-events: none;
opacity: 0;
transition: transform 1.1s cubic-bezier(0.65, 0, 0.35, 1), opacity 0.4s ease;
}
.mf-acc__ghost.is-on {
opacity: 1;
}
.mf-acc__ghost svg {
display: block;
filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.45));
}
.mf-acc__ghost::after {
content: "";
position: absolute;
top: -12px;
left: -12px;
width: 26px;
height: 26px;
border: 2px solid var(--mf-acc-accent);
border-radius: 50%;
opacity: 0;
}
.mf-acc__ghost.is-click::after {
animation: mf-acc-click 0.6s ease-out;
}
@keyframes mf-acc-progress {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
@keyframes mf-acc-click {
0% { opacity: 0.9; transform: scale(0.4); }
100% { opacity: 0; transform: scale(1.8); }
}
@media screen and (max-width: 989px) {
.mf-acc__head {
grid-template-columns: minmax(0, 1fr);
gap: 20px;
}
.mf-acc__aside {
justify-self: start;
max-width: 560px;
}
.mf-acc__peek-item:nth-child(n + 3) {
display: none;
}
}
@media screen and (max-width: 749px) {
/* A vertical accordion: closed panels become slim rows with a horizontal
title, the open one grows tall and shows the same content. */
.mf-acc__inner {
width: min(100% - 32px, 1320px);
padding: calc(var(--mf-acc-pt, 88px) * 0.7) 0 calc(var(--mf-acc-pb, 88px) * 0.7);
}
.mf-acc__head {
margin-bottom: 28px;
}
.mf-acc__eyebrow {
margin-bottom: 16px;
font-size: 11px;
}
.mf-acc__heading {
font-size: clamp(36px, 10.4vw, 46px);
letter-spacing: -0.04em;
}
.mf-acc__lede {
font-size: 15px;
}
.mf-acc__aside {
gap: 16px;
}
.mf-acc__panels {
flex-direction: column;
height: auto;
gap: 8px;
}
.mf-acc__panel {
--mf-acc-open-h: clamp(380px, calc(var(--mf-acc-h, 540px) * 0.78), 460px);
flex: none;
min-width: 0;
height: 72px;
border-radius: min(var(--mf-acc-radius, 22px), 20px);
transition: height 0.75s var(--mf-acc-ease);
}
.mf-acc__panel.is-active {
height: var(--mf-acc-open-h);
}
.mf-acc__img,
.mf-acc__art {
top: 50%;
left: 0;
width: 100%;
height: var(--mf-acc-open-h);
transform: translateY(-50%) scale(1.12);
}
.mf-acc__panel.is-active .mf-acc__img,
.mf-acc__panel.is-active .mf-acc__art {
transform: translateY(-50%) scale(1);
}
.mf-acc__dim {
background: linear-gradient(90deg, rgba(8, 7, 6, 0.7), rgba(8, 7, 6, 0.35));
}
.mf-acc__num {
top: 50%;
left: 8px;
margin-top: -15px;
font-size: 12px;
}
.mf-acc__panel.is-active .mf-acc__num {
top: 14px;
left: 14px;
margin-top: 0;
}
.mf-acc__label {
top: 50%;
left: 54px;
right: 64px;
bottom: auto;
max-height: none;
font-size: 17px;
writing-mode: horizontal-tb;
transform: translateY(-50%);
}
.mf-acc__panel.is-active .mf-acc__label {
transform: translateY(-50%) translateX(12px);
}
.mf-acc__plus {
top: 50%;
right: 16px;
bottom: auto;
left: auto;
width: 32px;
height: 32px;
margin-top: -16px;
}
.mf-acc__count {
top: 14px;
right: 14px;
}
.mf-acc__foot {
right: 0;
width: auto;
flex-direction: column-reverse;
align-items: flex-start;
gap: 18px;
padding: 20px;
}
.mf-acc__copy {
max-width: none;
width: 100%;
}
.mf-acc__title {
font-size: clamp(30px, 9vw, 38px);
white-space: normal;
}
.mf-acc__text {
margin-top: 10px;
font-size: 14px;
}
.mf-acc__cta {
margin-top: 18px;
}
.mf-acc__peek-item:nth-child(n + 3) {
display: block;
}
.mf-acc__peek-item {
margin-left: 8px;
}
.mf-acc__panel.is-active .mf-acc__peek-item {
transform: rotate(calc((var(--p, 0) - 1) * 2deg));
}
.mf-acc__peek {
width: 58px;
border-radius: 10px;
}
.mf-acc__price {
display: none;
}
}
@media (prefers-reduced-motion: reduce) {
.mf-acc__panel,
.mf-acc__img,
.mf-acc__art,
.mf-acc__dim,
.mf-acc__label,
.mf-acc__plus,
.mf-acc__title,
.mf-acc__text,
.mf-acc__cta,
.mf-acc__count,
.mf-acc__peek-item,
.mf-acc__peek,
.mf-acc__cta-ico,
.mf-acc__all svg,
.mf-acc__ghost {
transition: none !important;
}
.mf-acc__fill,
.mf-acc__ghost::after {
animation: none !important;
}
.mf-acc__peek:hover,
.mf-acc__cta:hover .mf-acc__cta-ico,
.mf-acc__all:hover svg,
.mf-acc__panel:not(.is-active):hover .mf-acc__plus {
transform: none;
}
}
{% endstylesheet %}
{% javascript %}
if (!customElements.get('mf-collection-accordion')) {
customElements.define('mf-collection-accordion', class extends HTMLElement {
connectedCallback() {
this.panels = Array.from(this.querySelectorAll('.mf-acc__panel'));
if (!this.panels.length) return;
this.triggers = this.panels.map((p) => p.querySelector('.mf-acc__trigger'));
this.bodies = this.panels.map((p) => p.querySelector('.mf-acc__body'));
const first = this.panels.findIndex((p) => p.classList.contains('is-active'));
this.index = first < 0 ? 0 : first;
this.timers = [];
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
this.preview = window.__MF_PREVIEW__ === true;
this.fine = window.matchMedia('(hover: hover) and (pointer: fine)');
this.narrow = window.matchMedia('(max-width: 749px)');
this.playing = !reduce && this.panels.length > 1 && (this.dataset.autoplay === 'true' || this.preview);
this.onClick = (event) => {
const trigger = event.target.closest('.mf-acc__trigger');
if (!trigger || !this.contains(trigger)) return;
this.stop();
this.activate(this.triggers.indexOf(trigger));
};
this.onKey = (event) => {
const k = this.triggers.indexOf(event.target);
if (k < 0) return;
const map = { ArrowRight: k + 1, ArrowDown: k + 1, ArrowLeft: k - 1, ArrowUp: k - 1, Home: 0, End: this.panels.length - 1 };
if (!(event.key in map)) return;
event.preventDefault();
this.stop();
this.activate(map[event.key], true);
};
this.onOver = (event) => {
if (event.pointerType !== 'mouse' || this.dataset.hover !== 'true') return;
if (!this.fine.matches || this.narrow.matches) return;
const panel = event.target.closest('.mf-acc__panel');
if (!panel || panel.classList.contains('is-active')) return;
clearTimeout(this.hoverTimer);
this.hoverTimer = setTimeout(() => {
this.stop();
this.activate(this.panels.indexOf(panel));
}, 90);
};
this.onEnter = (event) => {
if (event.pointerType === 'mouse') this.classList.add('is-hovering');
};
this.onLeave = () => {
clearTimeout(this.hoverTimer);
this.classList.remove('is-hovering');
};
this.onFocus = (event) => {
if (!event.target.matches(':focus-visible')) return;
this.stop();
const panel = event.target.closest('.mf-acc__panel');
const k = this.panels.indexOf(panel);
if (k >= 0 && k !== this.index && !event.target.classList.contains('mf-acc__trigger')) this.activate(k);
};
this.onEnd = (event) => {
if (event.animationName !== 'mf-acc-progress' || !this.playing) return;
if (!this.panels[this.index].contains(event.target)) return;
if (this.ghost) this.ghost.classList.add('is-click');
this.activate(this.index + 1);
};
this.onSelect = (event) => {
const panel = event.target.closest && event.target.closest('.mf-acc__panel');
const k = this.panels.indexOf(panel);
if (k < 0) return;
this.stop();
this.activate(k);
};
this.addEventListener('click', this.onClick);
this.addEventListener('keydown', this.onKey);
this.addEventListener('pointerover', this.onOver);
this.addEventListener('pointerenter', this.onEnter);
this.addEventListener('pointerleave', this.onLeave);
this.addEventListener('focusin', this.onFocus);
this.addEventListener('animationend', this.onEnd);
this.addEventListener('shopify:block:select', this.onSelect);
if ('IntersectionObserver' in window) {
this.io = new IntersectionObserver((entries) => {
entries.forEach((entry) => this.classList.toggle('is-offscreen', !entry.isIntersecting));
}, { rootMargin: '80px 0px' });
this.io.observe(this);
}
this.activate(this.index);
if (this.playing) {
this.classList.add('is-playing');
if (this.preview && !reduce) this.buildGhost();
this.scheduleGhost();
}
}
disconnectedCallback() {
this.removeEventListener('click', this.onClick);
this.removeEventListener('keydown', this.onKey);
this.removeEventListener('pointerover', this.onOver);
this.removeEventListener('pointerenter', this.onEnter);
this.removeEventListener('pointerleave', this.onLeave);
this.removeEventListener('focusin', this.onFocus);
this.removeEventListener('animationend', this.onEnd);
this.removeEventListener('shopify:block:select', this.onSelect);
if (this.io) this.io.disconnect();
clearTimeout(this.hoverTimer);
clearTimeout(this.introTimer);
this.clearTimers();
if (this.ghost) this.ghost.remove();
this.ghost = null;
}
activate(i, focus) {
const n = this.panels.length;
const next = ((i % n) + n) % n;
this.panels.forEach((panel, k) => {
const on = k === next;
panel.classList.toggle('is-active', on);
if (this.triggers[k]) this.triggers[k].setAttribute('aria-expanded', on ? 'true' : 'false');
if (this.bodies[k]) this.bodies[k].inert = !on;
});
this.index = next;
if (focus && this.triggers[next]) this.triggers[next].focus();
this.scheduleGhost();
}
stop() {
if (!this.playing) return;
this.playing = false;
this.classList.remove('is-playing');
this.clearTimers();
if (this.ghost) this.ghost.classList.remove('is-on');
}
clearTimers() {
this.timers.forEach((t) => clearTimeout(t));
this.timers = [];
}
/* Library preview only: a scripted cursor glides to the next panel
just before the timer opens it, so the gallery card shows the
interaction without a visitor. */
buildGhost() {
const wrap = this.querySelector('.mf-acc__panels');
if (!wrap || this.narrow.matches) return;
const ghost = document.createElement('span');
ghost.className = 'mf-acc__ghost';
ghost.setAttribute('aria-hidden', 'true');
ghost.innerHTML = '<svg viewBox="0 0 24 24" width="26" height="26"><path d="M5 3l14 7.5-6.2 1.6L10 18.5z" fill="#fff" stroke="#111" stroke-width="1.4" stroke-linejoin="round"/></svg>';
wrap.appendChild(ghost);
this.ghost = ghost;
this.wrap = wrap;
/* First beat: the cursor rests on the open panel's button, then the
regular schedule glides it to the next strip. */
this.introTimer = setTimeout(() => {
if (!this.ghost || !this.playing) return;
const box = wrap.getBoundingClientRect();
const panel = this.panels[this.index];
const aim = panel.querySelector('.mf-acc__cta-ico') || panel;
const r = aim.getBoundingClientRect();
const x = r.left - box.left + Math.min(r.width * 0.55, 24);
const y = r.top - box.top + Math.min(r.height * 0.6, 26);
ghost.style.transition = 'none';
ghost.style.transform = 'translate(' + x.toFixed(1) + 'px, ' + y.toFixed(1) + 'px)';
void ghost.offsetWidth;
ghost.style.transition = '';
ghost.classList.add('is-on');
}, 900);
}
scheduleGhost() {
if (!this.ghost || !this.playing) return;
this.clearTimers();
const cycle = parseFloat(getComputedStyle(this).getPropertyValue('--mf-acc-cycle')) || 7;
const target = this.panels[(this.index + 1) % this.panels.length];
this.timers.push(setTimeout(() => this.ghost && this.ghost.classList.remove('is-click'), 700));
this.timers.push(setTimeout(() => {
if (!this.ghost || !this.playing) return;
const box = this.wrap.getBoundingClientRect();
const r = target.getBoundingClientRect();
const x = r.left - box.left + Math.min(r.width / 2, 70);
const y = r.top - box.top + r.height * 0.42;
this.ghost.classList.add('is-on');
this.ghost.style.transform = 'translate(' + x.toFixed(1) + 'px, ' + y.toFixed(1) + 'px)';
}, cycle * 720));
}
});
}
{% endjavascript %}
{% schema %}
{
"name": "EX · Collection accordion",
"tag": "section",
"class": "mf-section",
"disabled_on": {
"groups": ["header", "footer"]
},
"settings": [
{
"type": "header",
"content": "Heading"
},
{
"type": "text",
"id": "eyebrow",
"label": "Eyebrow",
"default": "Shop by category"
},
{
"type": "inline_richtext",
"id": "heading",
"label": "Heading",
"default": "Four doors into"
},
{
"type": "inline_richtext",
"id": "heading_accent",
"label": "Heading accent",
"default": "the whole shop."
},
{
"type": "select",
"id": "accent_style",
"label": "Accent style",
"options": [
{ "value": "plain", "label": "Plain" },
{ "value": "serif", "label": "Italic serif" },
{ "value": "muted", "label": "Muted" }
],
"default": "serif"
},
{
"type": "select",
"id": "heading_tag",
"label": "Heading HTML tag",
"info": "Use H1 only when this is the first section of the page.",
"options": [
{ "value": "h1", "label": "H1" },
{ "value": "h2", "label": "H2" }
],
"default": "h2"
},
{
"type": "richtext",
"id": "text",
"label": "Text",
"default": "<p>Pick a door, not a menu. Each panel opens onto a collection with its bestsellers already on show.</p>"
},
{
"type": "text",
"id": "link_label",
"label": "Link label",
"default": "All collections"
},
{
"type": "url",
"id": "link_url",
"label": "Link",
"default": "/collections"
},
{
"type": "header",
"content": "Panels"
},
{
"type": "range",
"id": "panel_height",
"label": "Panel height",
"min": 400,
"max": 720,
"step": 20,
"unit": "px",
"default": 520
},
{
"type": "range",
"id": "open_width",
"label": "Open panel width",
"info": "How many times wider the open panel is than a closed one.",
"min": 3,
"max": 9,
"step": 1,
"default": 6
},
{
"type": "range",
"id": "radius",
"label": "Corner radius",
"min": 0,
"max": 40,
"step": 2,
"unit": "px",
"default": 22
},
{
"type": "range",
"id": "gap",
"label": "Space between panels",
"min": 4,
"max": 24,
"step": 2,
"unit": "px",
"default": 10
},
{
"type": "range",
"id": "overlay",
"label": "Image shade for text",
"min": 20,
"max": 90,
"step": 5,
"unit": "%",
"default": 65
},
{
"type": "range",
"id": "peek_count",
"label": "Product previews per panel",
"info": "Small product tiles from the collection, linking to each product. 0 hides them.",
"min": 0,
"max": 4,
"step": 1,
"default": 3
},
{
"type": "checkbox",
"id": "show_count",
"label": "Show product count",
"default": true
},
{
"type": "text",
"id": "count_label",
"label": "Product count label",
"default": "products"
},
{
"type": "header",
"content": "Behaviour"
},
{
"type": "checkbox",
"id": "autoplay",
"label": "Cycle through panels until the visitor interacts",
"default": true
},
{
"type": "range",
"id": "cycle",
"label": "Seconds per panel",
"min": 3,
"max": 15,
"step": 1,
"unit": "s",
"default": 7
},
{
"type": "checkbox",
"id": "hover_open",
"label": "Open panels on hover (desktop)",
"info": "Panels always open on click, tap and keyboard.",
"default": true
},
{
"type": "header",
"content": "Layout"
},
{
"type": "range",
"id": "padding_top",
"label": "Top spacing",
"min": 0,
"max": 160,
"step": 4,
"unit": "px",
"default": 72
},
{
"type": "range",
"id": "padding_bottom",
"label": "Bottom spacing",
"min": 0,
"max": 160,
"step": 4,
"unit": "px",
"default": 72
},
{
"type": "header",
"content": "Colors"
},
{
"type": "color",
"id": "color_bg",
"label": "Background",
"default": "#0d0c0b"
},
{
"type": "color",
"id": "color_text",
"label": "Text",
"default": "#f4f1ea"
},
{
"type": "color",
"id": "color_accent",
"label": "Accent",
"default": "#ff8a4c"
}
],
"blocks": [
{
"type": "collection",
"name": "Collection panel",
"settings": [
{
"type": "collection",
"id": "collection",
"label": "Collection"
},
{
"type": "image_picker",
"id": "image",
"label": "Custom image",
"info": "Optional. Defaults to the collection image, then to gradient art. Keep the subject near the center: closed panels show a slim slice."
},
{
"type": "text",
"id": "label",
"label": "Title",
"info": "Leave empty to use the collection title."
},
{
"type": "text",
"id": "text",
"label": "Short text",
"default": "Our most-loved formulas, chosen by thousands of reorders."
},
{
"type": "text",
"id": "button_label",
"label": "Button label",
"default": "Shop the edit"
}
]
}
],
"max_blocks": 8,
"presets": [
{
"name": "EX · Collection accordion",
"blocks": [
{
"type": "collection",
"settings": {
"text": "The formulas our customers reorder most. If you only try one thing, start here.",
"button_label": "Shop bestsellers"
}
},
{
"type": "collection",
"settings": {
"text": "Serums, creams and SPF built around a calm, strong skin barrier.",
"button_label": "Shop skincare"
}
},
{
"type": "collection",
"settings": {
"text": "Body oils and candles for the slow part of the evening.",
"button_label": "Shop body & home"
}
},
{
"type": "collection",
"settings": {
"text": "Fresh from the lab this season, in small first batches.",
"button_label": "Shop new arrivals"
}
}
]
}
]
}
{% endschema %}
Why it converts
Category navigation as a visual choice: fewer, bigger doors into collections lift click-through from the homepage.
Specs
About this section
Collection Accordion is a Shopify Online Store 2.0 section that turns category navigation into a row of tall image panels. One panel is open at a time: it grows wide and reveals the collection title, a short line of text, a button into the collection, a product count and small product tiles with prices that link straight to products. The other panels fold into slim strips with vertical titles. Each block is a collection panel: pick a collection, and optionally a custom image, a title override, short text and a button label. The image falls back to the collection image, then to gradient art, so no panel is ever empty. Above the row sit an eyebrow, a two-part heading with a plain, italic serif or muted accent, rich text and a link to all collections. The theme editor also sets panel height, open panel width, corner radius, gaps, image shade, product previews per panel, spacing and the background, text and accent colors. Panels open on hover, click, tap and keyboard, and the row cycles through panels with a progress line until the visitor interacts. The conversion principle is choice made visual: a few large, picture-led doors are easier to choose between than a text menu, and showing real products inside each door gives the shopper a reason to click. On phones the row becomes a vertical accordion with full-width rows. With reduced motion, panels switch instantly and nothing cycles. One self-contained .liquid file with no external fonts, libraries or images.
Questions
How do panels open?
On desktop a panel opens when the cursor rests on it for a moment, or on click. On touch screens it opens on tap. Keyboard users tab to a panel button and use the arrow keys, Home and End. Until the visitor interacts, the row cycles through panels with a progress line; hovering pauses it.
Where do the panel images come from?
Each Collection panel block uses its custom image if you set one, otherwise the collection image from Shopify admin, otherwise gradient art. Images keep a fixed width while panels open, so keep the subject near the center: closed strips show a slim vertical slice.
How many collections can I add?
Up to eight Collection panel blocks. Four or five read best on desktop, because the open panel stays wide while the closed strips remain legible. Use Open panel width to balance them.
Is it accessible and theme-compatible?
Each panel title is a real button with aria-expanded, closed panels are inert so hidden links are skipped, and focus rings use the accent color. Styles are scoped to the section and use the theme font, so it works on Dawn, Horizon and premium Online Store 2.0 themes.
More sections
Pro