Go to Online Store → Themes → Edit Code → Sections and create a new file. Name it exactly:
Then paste the full section code below and save:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css" />
{%- 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;
}
}
.custom-video-feed { position: relative; }
.custom-video-feed__header {
display: flex; align-items: center;
justify-content: flex-end; margin-bottom: 24px;
}
.custom-video-feed__nav { display: flex; gap: 8px; }
.custom-video-feed__nav-btn {
width: 40px; height: 40px; border-radius: 50%;
border: 1.5px solid rgba(0,0,0,0.35); background: transparent;
cursor: pointer; display: flex; align-items: center;
justify-content: center; transition: all 0.25s ease;
color: #282928; flex-shrink: 0;
}
.custom-video-feed__nav-btn:hover:not(.swiper-button-disabled) {
background: #121212; color: #fff; border-color: #121212;
}
.custom-video-feed__nav-btn.swiper-button-disabled { opacity: 0.3; cursor: not-allowed; }
.custom-video-feed__nav-btn svg { width: 16px; height: 16px; }
.custom-video-feed__nav-btn--prev svg { transform: rotate(180deg); }
.custom-video-feed .swiper-pagination {
position: static; margin-top: 16px;
display: flex; justify-content: center; gap: 6px;
}
.custom-video-feed .swiper-pagination-bullet {
width: 6px; height: 6px; border-radius: 50%;
background: rgba(0,0,0,0.2); opacity: 1; margin: 0 !important;
transition: all 0.3s ease;
}
.custom-video-feed .swiper-pagination-bullet-active {
background: #121212; width: 20px; border-radius: 3px;
}
.custom-video-feed__card {
display: flex; flex-direction: column;
overflow: hidden; cursor: pointer;
}
.custom-video-feed__video-wrap {
position: relative; width: 100%; aspect-ratio: 9 / 16;
overflow: hidden; background: #000; border-radius: 10px;
}
.custom-video-feed__video {
width: 100%; height: 100%; object-fit: cover;
display: block; cursor: pointer;
}
.custom-video-feed__controls {
position: absolute; bottom: 10px; right: 10px; z-index: 2;
}
.custom-video-feed__ctrl-btn {
width: 35px; height: 35px; border-radius: 10px; border: none;
background: rgba(255,255,255,0.2); color: white;
display: flex; align-items: center; justify-content: center;
cursor: pointer; backdrop-filter: blur(4px);
transition: background 0.2s ease;
}
.custom-video-feed__ctrl-btn:hover { background: rgba(255,255,255,0.3); }
.custom-video-feed__ctrl-btn svg { width: 18px; height: 18px; }
.custom-video-feed__play-overlay {
position: absolute; inset: 0; display: flex;
align-items: center; justify-content: center;
background: rgba(0,0,0,0.2); opacity: 1;
transition: opacity 0.3s ease; pointer-events: none;
}
.custom-video-feed__play-overlay.is-playing { opacity: 0; }
.custom-video-feed__play-icon {
width: 48px; height: 48px; border-radius: 50%;
background: rgba(255,255,255,0.9);
display: flex; align-items: center; justify-content: center;
}
.custom-video-feed__play-icon svg { width: 18px; height: 18px; margin-left: 3px; color: #000; }
.custom-video-feed__product {
display: flex; align-items: center; gap: 10px;
padding: 10px 12px; border: 1px solid #e1e1e1;
text-decoration: none; color: inherit;
transition: background 0.2s ease; border-radius: 10px;
margin-top: 4px; width: 100%; box-sizing: border-box;
margin-bottom: 24px;
}
.custom-video-feed__product:hover { background: rgba(0,0,0,0.03); }
.custom-video-feed__product-img {
width: 55px; height: 55px; object-fit: cover;
border-radius: 10px; flex-shrink: 0;
border: 1px solid rgba(0,0,0,0.08);
}
.custom-video-feed__product-img-placeholder {
width: 55px; height: 55px; background: rgba(0,0,0,0.06);
border-radius: 10px; flex-shrink: 0;
}
.custom-video-feed__product-info { flex: 1; min-width: 0; text-align: left; }
.custom-video-feed__product-title {
font-size: 16px; font-weight: 400; margin: 0 0 8px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
line-height: 1.2; color: #282928;
}
.custom-video-feed__product-price {
font-size: 18px; font-weight: 600; line-height: 1.2; color: #282928; margin: 0;
}
.custom-video-feed__product-btn {
width: 32px; height: 32px; border-radius: 10px;
background: #000; color: #fff; border: none;
display: flex; align-items: center; justify-content: center;
cursor: pointer; flex-shrink: 0; transition: opacity 0.2s ease;
}
.custom-video-feed__product-btn:hover { opacity: 0.8; }
.custom-video-feed__product-btn svg { width: 14px; height: 14px; }
{%- endstyle -%}
<div class="page-width section-{{ section.id }}-padding">
<div class="custom-video-feed__header">
<div class="custom-video-feed__nav">
<button class="custom-video-feed__nav-btn custom-video-feed__nav-btn--prev"
id="VideoFeedPrev-{{ section.id }}" aria-label="Previous">
<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">
<path d="M9 18l6-6-6-6"/>
</svg>
</button>
<button class="custom-video-feed__nav-btn custom-video-feed__nav-btn--next"
id="VideoFeedNext-{{ section.id }}" aria-label="Next">
<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">
<path d="M9 18l6-6-6-6"/>
</svg>
</button>
</div>
</div>
<div class="custom-video-feed">
<div class="swiper" id="VideoFeedSwiper-{{ section.id }}">
<div class="swiper-wrapper">
{%- for block in section.blocks -%}
{%- if block.type == 'video_card' -%}
{%- assign linked_product = all_products[block.settings.product] -%}
<div class="swiper-slide">
<div class="custom-video-feed__card">
<div class="custom-video-feed__video-wrap">
{%- if block.settings.video_url != blank -%}
<video class="custom-video-feed__video"
src="{{ block.settings.video_url }}"
loop muted playsinline preload="metadata"></video>
{%- else -%}
<div style="width:100%;height:100%;background:rgba(0,0,0,0.06);
display:flex;align-items:center;justify-content:center;">
<svg width="40" height="40" viewBox="0 0 24 24" fill="none"
stroke="rgba(0,0,0,0.2)" stroke-width="1.5">
<polygon points="5 3 19 12 5 21 5 3"/>
</svg>
</div>
{%- endif -%}
<div class="custom-video-feed__play-overlay">
<div class="custom-video-feed__play-icon">
<svg viewBox="0 0 24 24" fill="currentColor">
<polygon points="5 3 19 12 5 21 5 3"/>
</svg>
</div>
</div>
<div class="custom-video-feed__controls">
<button class="custom-video-feed__ctrl-btn" aria-label="Toggle mute" data-muted="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/>
<line x1="23" y1="9" x2="17" y2="15"/>
<line x1="17" y1="9" x2="23" y2="15"/>
</svg>
</button>
</div>
</div>
{%- if block.settings.product != blank and linked_product != blank -%}
<a href="{{ linked_product.url }}" class="custom-video-feed__product">
{%- if linked_product.featured_image -%}
<img class="custom-video-feed__product-img"
src="{{ linked_product.featured_image | image_url: width: 120 }}"
alt="{{ linked_product.featured_image.alt | escape }}"
width="55" height="55" loading="lazy">
{%- else -%}
<div class="custom-video-feed__product-img-placeholder"></div>
{%- endif -%}
<div class="custom-video-feed__product-info">
<p class="custom-video-feed__product-title">{{ linked_product.title }}</p>
<p class="custom-video-feed__product-price">{{ linked_product.price | money }}</p>
</div>
<span class="custom-video-feed__product-btn" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12h14M12 5l7 7-7 7"/>
</svg>
</span>
</a>
{%- endif -%}
</div>
</div>
{%- endif -%}
{%- endfor -%}
</div>
<div class="swiper-pagination" id="VideoFeedPagination-{{ section.id }}"></div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js" defer></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
const sectionId = '{{ section.id }}';
const swiper = new Swiper('#VideoFeedSwiper-' + sectionId, {
grabCursor: true,
navigation: {
prevEl: '#VideoFeedPrev-' + sectionId,
nextEl: '#VideoFeedNext-' + sectionId,
},
pagination: {
el: '#VideoFeedPagination-' + sectionId,
clickable: true,
},
breakpoints: {
0: { slidesPerView: 2, spaceBetween: 10 },
600: { slidesPerView: 2, spaceBetween: 12 },
768: { slidesPerView: 3, spaceBetween: 12 },
1024: { slidesPerView: 5, spaceBetween: 12 },
},
});
const allVideos = document.querySelectorAll(
'#VideoFeedSwiper-' + sectionId + ' .custom-video-feed__video'
);
function pauseAll(except) {
allVideos.forEach(function (v) {
if (v !== except) {
v.pause();
const overlay = v.closest('.custom-video-feed__video-wrap')
.querySelector('.custom-video-feed__play-overlay');
if (overlay) overlay.classList.remove('is-playing');
}
});
}
allVideos.forEach(function (video) {
const wrap = video.closest('.custom-video-feed__video-wrap');
const overlay = wrap.querySelector('.custom-video-feed__play-overlay');
const muteBtn = wrap.querySelector('.custom-video-feed__ctrl-btn');
wrap.addEventListener('click', function (e) {
if (muteBtn && (e.target === muteBtn || muteBtn.contains(e.target))) return;
if (video.paused) {
pauseAll(video);
video.play().catch(function () {});
if (overlay) overlay.classList.add('is-playing');
} else {
video.pause();
if (overlay) overlay.classList.remove('is-playing');
}
});
if (muteBtn) {
muteBtn.addEventListener('click', function (e) {
e.stopPropagation();
video.muted = !video.muted;
muteBtn.setAttribute('data-muted', video.muted ? 'true' : 'false');
muteBtn.innerHTML = video.muted
? '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><line x1="23" y1="9" x2="17" y2="15"/><line x1="17" y1="9" x2="23" y2="15"/></svg>'
: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/></svg>';
});
}
});
if ('IntersectionObserver' in window) {
const observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
const video = entry.target.querySelector('.custom-video-feed__video');
const overlay = entry.target.querySelector('.custom-video-feed__play-overlay');
if (!video) return;
if (entry.isIntersecting) {
video.play().catch(function () {});
if (overlay) overlay.classList.add('is-playing');
} else {
video.pause();
if (overlay) overlay.classList.remove('is-playing');
}
});
}, { threshold: 0.5 });
document.querySelectorAll(
'#VideoFeedSwiper-' + sectionId + ' .swiper-slide'
).forEach(function (slide) { observer.observe(slide); });
}
});
</script>
{% schema %}
{
"name": "Video Feed",
"tag": "section",
"class": "section",
"disabled_on": { "groups": ["header", "footer"] },
"settings": [
{ "type": "header", "content": "Padding" },
{ "type": "range", "id": "padding_top", "min": 0, "max": 100,
"step": 4, "unit": "px", "label": "Padding top", "default": 36 },
{ "type": "range", "id": "padding_bottom", "min": 0, "max": 100,
"step": 4, "unit": "px", "label": "Padding bottom", "default": 36 }
],
"blocks": [
{
"type": "video_card",
"name": "Video Card",
"settings": [
{ "type": "header", "content": "Video" },
{ "type": "url", "id": "video_url", "label": "Paste video URL (.mp4)",
"info": "Use if video is hosted externally" },
{ "type": "header", "content": "Product" },
{ "type": "product", "id": "product", "label": "Select product" }
]
}
],
"presets": [{ "name": "Video Feed", "blocks": [] }]
}
{% endschema %}
Go to Online Store → Themes → Customize. On the Homepage (or any page), click Add section and search for Video Feed. Add it and drag it into position.
Inside the Video Feed section in the theme editor, click Add block → Video Card. Each block has two fields:
· Video URL (.mp4) — paste a direct link to your hosted video
· Select product — choose any product from your store
Add as many blocks as you need. Each block = one video card in the slider.
Click Save in the theme editor, then open your storefront. Your video feed appears with the linked product tiles below each card.
Videos auto-play as they enter the viewport and pause when they scroll out. Only one video plays at a time — clicking a new card pauses the previous one automatically.
slidesPerView values inside the breakpoints object in the script. The defaults are 2 on mobile, 3 on tablet, 5 on desktop.
Open your storefront, add items to the feed, and watch it come to life. Tap any card to play, use the mute button for audio, and the nav arrows and pagination dots work automatically. Adjust slidesPerView in the Swiper config to fit your theme's column width.
I implement this kind of custom Shopify development daily — no apps, clean code, done fast. Tell me what you need and I'll get it done right.