Custom Sections Are What Separate Template Stores From Professional Ones
Every Shopify store starts with the same themes. The stores that stand out — the ones that feel like a brand instead of a template — are the ones with custom sections built specifically for their content and goals. Learning how to build custom Shopify sections is the single highest-leverage skill for any merchant or developer working with Shopify themes.
Sections are the modular building blocks of every page in Online Store 2.0. They're Liquid files with a visual editor interface, meaning you build them once with code and merchants customize them forever without touching a line of code. A well-built section library turns the Shopify theme editor from a limited tool into a full page builder.
This tutorial walks through building real sections from scratch — not theoretical concepts, but production-ready code you can paste into your theme today. If you're just getting started with Shopify development, sections are the best place to begin because results are immediate and visual.
How Sections Work in Online Store 2.0

Before writing code, understand how sections fit into the theme architecture.
The Section Lifecycle
- You create a
.liquidfile in thesections/folder of your theme - The file contains HTML/Liquid markup at the top and a
{% schema %}block at the bottom - The schema defines what settings and blocks appear in the theme editor
- Adding a
presetsarray makes the section available in the "Add section" picker - Merchants drag the section onto any page, configure settings, and publish
Section vs Block vs Snippet
These three concepts confuse beginners. Here's the distinction:
| Concept | What It Is | Where It Lives | Merchant-Editable? |
|---|---|---|---|
| Section | A self-contained page module with its own settings UI | sections/ folder | Yes — full theme editor panel |
| Block | A repeatable content unit inside a section | Defined in section schema or blocks/ folder | Yes — add, remove, reorder |
| Snippet | A reusable code fragment included by sections | snippets/ folder | No — code only, no editor UI |
Sections are the building blocks merchants interact with. Blocks are the building blocks inside sections. Snippets are developer utilities that keep code DRY.
According to Shopify's official sections documentation, each JSON template can render up to 25 sections, and each section supports up to 50 blocks — more than enough for any page layout.
Building a Hero Banner Section
Let's start with the most common section: a hero banner with a background image, headline, subheadline, and CTA button. This pattern appears on virtually every Shopify store's homepage.
The Complete Code
Create sections/hero-banner.liquid:
{{ 'section-hero-banner.css' | asset_url | stylesheet_tag }}
<section
class="hero-banner"
style="--hero-min-height: {{ section.settings.min_height }}px;"
>
{%- if section.settings.image -%}
<div class="hero-banner__media">
<img
src="{{ section.settings.image | image_url: width: 1920 }}"
srcset="
{{ section.settings.image | image_url: width: 768 }} 768w,
{{ section.settings.image | image_url: width: 1200 }} 1200w,
{{ section.settings.image | image_url: width: 1920 }} 1920w
"
sizes="100vw"
alt="{{ section.settings.image.alt | escape }}"
loading="eager"
fetchpriority="high"
width="1920"
height="{{ 1920 | divided_by: section.settings.image.aspect_ratio | round }}"
>
</div>
{%- endif -%}
<div class="hero-banner__overlay" style="opacity: {{ section.settings.overlay_opacity | divided_by: 100.0 }};"></div>
<div class="hero-banner__content hero-banner__content--{{ section.settings.text_alignment }}">
{%- if section.settings.subheading != blank -%}
<p class="hero-banner__subheading">{{ section.settings.subheading }}</p>
{%- endif -%}
{%- if section.settings.heading != blank -%}
<h1 class="hero-banner__heading">{{ section.settings.heading }}</h1>
{%- endif -%}
{%- if section.settings.text != blank -%}
<p class="hero-banner__text">{{ section.settings.text }}</p>
{%- endif -%}
{%- if section.settings.button_label != blank -%}
<a href="{{ section.settings.button_link }}" class="hero-banner__button">
{{ section.settings.button_label }}
</a>
{%- endif -%}
</div>
</section>
{% schema %}
{
"name": "Hero Banner",
"tag": "section",
"class": "hero-banner-section",
"settings": [
{
"type": "image_picker",
"id": "image",
"label": "Background image"
},
{
"type": "range",
"id": "overlay_opacity",
"min": 0,
"max": 90,
"step": 5,
"default": 40,
"unit": "%",
"label": "Overlay opacity"
},
{
"type": "range",
"id": "min_height",
"min": 300,
"max": 800,
"step": 50,
"default": 500,
"unit": "px",
"label": "Minimum height"
},
{
"type": "text",
"id": "subheading",
"label": "Subheading",
"default": "New Collection"
},
{
"type": "text",
"id": "heading",
"label": "Heading",
"default": "Your headline here"
},
{
"type": "textarea",
"id": "text",
"label": "Description"
},
{
"type": "text",
"id": "button_label",
"label": "Button label",
"default": "Shop Now"
},
{
"type": "url",
"id": "button_link",
"label": "Button link"
},
{
"type": "select",
"id": "text_alignment",
"label": "Text alignment",
"options": [
{ "value": "left", "label": "Left" },
{ "value": "center", "label": "Center" },
{ "value": "right", "label": "Right" }
],
"default": "center"
}
],
"presets": [
{
"name": "Hero Banner"
}
]
}
{% endschema %}The CSS
Create assets/section-hero-banner.css:
.hero-banner {
position: relative;
display: flex;
align-items: center;
justify-content: center;
min-height: var(--hero-min-height, 500px);
overflow: hidden;
}
.hero-banner__media {
position: absolute;
inset: 0;
}
.hero-banner__media img {
width: 100%;
height: 100%;
object-fit: cover;
}
.hero-banner__overlay {
position: absolute;
inset: 0;
background: #000;
}
.hero-banner__content {
position: relative;
z-index: 2;
max-width: 640px;
padding: 2rem;
}
.hero-banner__content--center { text-align: center; }
.hero-banner__content--left { text-align: left; margin-right: auto; }
.hero-banner__content--right { text-align: right; margin-left: auto; }
.hero-banner__subheading {
font-size: 0.75rem;
letter-spacing: 0.15em;
text-transform: uppercase;
margin-bottom: 1rem;
opacity: 0.8;
}
.hero-banner__heading {
font-size: clamp(2rem, 5vw, 3.5rem);
line-height: 1.1;
margin-bottom: 1rem;
}
.hero-banner__text {
font-size: 1.1rem;
margin-bottom: 2rem;
opacity: 0.9;
}
.hero-banner__button {
display: inline-block;
padding: 0.875rem 2rem;
background: var(--color-button, #fff);
color: var(--color-button-text, #000);
text-decoration: none;
font-weight: 600;
border-radius: 4px;
transition: opacity 0.2s;
}
.hero-banner__button:hover { opacity: 0.85; }What This Section Teaches
- `image_picker` setting type for background images
- `srcset` and `sizes` for responsive image loading
- `range` settings for numeric sliders (height, opacity)
- `select` setting for dropdown choices
- CSS custom properties for passing Liquid values to CSS
- `presets` to make the section available in the editor
Building a Testimonials Section With Blocks
Blocks are what make sections truly dynamic. Instead of hardcoding three testimonials, blocks let merchants add, remove, and reorder an unlimited number of testimonials through the editor.
The Complete Code
Create sections/testimonials.liquid:
<section class="testimonials">
{%- if section.settings.heading != blank -%}
<h2 class="testimonials__heading">{{ section.settings.heading }}</h2>
{%- endif -%}
<div class="testimonials__grid">
{%- for block in section.blocks -%}
<div class="testimonials__card" {{ block.shopify_attributes }}>
<div class="testimonials__stars">
{%- for i in (1..block.settings.rating) -%}
<span class="testimonials__star">★</span>
{%- endfor -%}
</div>
{%- if block.settings.quote != blank -%}
<blockquote class="testimonials__quote">
{{ block.settings.quote }}
</blockquote>
{%- endif -%}
<div class="testimonials__author">
{%- if block.settings.avatar -%}
<img
src="{{ block.settings.avatar | image_url: width: 80 }}"
alt="{{ block.settings.name }}"
width="40"
height="40"
class="testimonials__avatar"
loading="lazy"
>
{%- endif -%}
<div>
<strong>{{ block.settings.name }}</strong>
{%- if block.settings.title != blank -%}
<span class="testimonials__title">{{ block.settings.title }}</span>
{%- endif -%}
</div>
</div>
</div>
{%- endfor -%}
</div>
</section>
{% schema %}
{
"name": "Testimonials",
"settings": [
{
"type": "text",
"id": "heading",
"label": "Heading",
"default": "What Our Customers Say"
}
],
"blocks": [
{
"type": "testimonial",
"name": "Testimonial",
"settings": [
{
"type": "range",
"id": "rating",
"min": 1,
"max": 5,
"default": 5,
"label": "Star rating"
},
{
"type": "textarea",
"id": "quote",
"label": "Quote",
"default": "This product changed how I run my business."
},
{
"type": "text",
"id": "name",
"label": "Customer name",
"default": "Jane D."
},
{
"type": "text",
"id": "title",
"label": "Customer title",
"default": "Store Owner"
},
{
"type": "image_picker",
"id": "avatar",
"label": "Photo"
}
]
}
],
"presets": [
{
"name": "Testimonials",
"blocks": [
{ "type": "testimonial" },
{ "type": "testimonial" },
{ "type": "testimonial" }
]
}
]
}
{% endschema %}Key Block Concepts
- `block.shopify_attributes` — adds data attributes that connect the rendered block to the theme editor. Always include this on block wrapper elements.
- Block presets — the
blocksarray inpresetsdefines default blocks when the section is first added. Three testimonials by default gives merchants a starting point. - `for block in section.blocks` — iterates over all blocks the merchant has added. They control how many through the editor.
FirstPier's guide to mastering section schema covers advanced block patterns including nested blocks and conditional rendering.
Building an FAQ Accordion Section

FAQ sections are SEO gold — they answer questions Google shows in "People Also Ask" boxes and qualify for FAQ rich snippets with proper schema markup. For merchants focused on SEO performance, a well-built FAQ section does double duty.
The Complete Code
Create sections/faq-accordion.liquid:
{{ 'section-faq-accordion.css' | asset_url | stylesheet_tag }}
<section class="faq">
{%- if section.settings.heading != blank -%}
<h2 class="faq__heading">{{ section.settings.heading }}</h2>
{%- endif -%}
<div class="faq__list">
{%- for block in section.blocks -%}
<details class="faq__item" {{ block.shopify_attributes }}>
<summary class="faq__question">
{{ block.settings.question }}
<span class="faq__icon" aria-hidden="true">+</span>
</summary>
<div class="faq__answer">
{{ block.settings.answer }}
</div>
</details>
{%- endfor -%}
</div>
</section>
{%- comment -%} FAQ Schema for Rich Snippets {%- endcomment -%}
{%- if section.blocks.size > 0 -%}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{%- for block in section.blocks -%}
{
"@type": "Question",
"name": {{ block.settings.question | json }},
"acceptedAnswer": {
"@type": "Answer",
"text": {{ block.settings.answer | strip_html | json }}
}
}{% unless forloop.last %},{% endunless %}
{%- endfor -%}
]
}
</script>
{%- endif -%}
{% schema %}
{
"name": "FAQ Accordion",
"settings": [
{
"type": "text",
"id": "heading",
"label": "Heading",
"default": "Frequently Asked Questions"
}
],
"blocks": [
{
"type": "faq_item",
"name": "Question",
"settings": [
{
"type": "text",
"id": "question",
"label": "Question"
},
{
"type": "richtext",
"id": "answer",
"label": "Answer"
}
]
}
],
"presets": [
{
"name": "FAQ Accordion",
"blocks": [
{ "type": "faq_item" },
{ "type": "faq_item" },
{ "type": "faq_item" }
]
}
]
}
{% endschema %}What Makes This Section Special
- Native `<details>`/`<summary>` — accordion behavior without JavaScript. Works everywhere, accessible by default, keyboard navigable.
- Built-in FAQ schema — the JSON-LD block at the bottom generates structured data for Google's FAQ rich snippets automatically. Every question the merchant adds becomes a potential rich result.
- `richtext` setting type — allows formatted answers with bold, italic, links, and lists through the theme editor.
Building a Product Feature Grid With Theme Blocks
Shopify's blocks documentation introduces Theme Blocks — reusable block types that live in the /blocks folder and can be shared across multiple sections.
When to Use Theme Blocks vs Section Blocks
| Section Blocks | Theme Blocks | |
|---|---|---|
| Defined in | Section's {% schema %} | Separate file in /blocks/ |
| Reusable across sections | No — locked to one section | Yes — any section can use them |
| Best for | Section-specific content | Shared patterns (buttons, images, text) |
For this feature grid, we'll use section blocks since the content is specific to this layout:
<section class="feature-grid">
{%- if section.settings.heading != blank -%}
<h2 class="feature-grid__heading">{{ section.settings.heading }}</h2>
{%- endif -%}
<div class="feature-grid__items" style="--columns: {{ section.settings.columns }};">
{%- for block in section.blocks -%}
<div class="feature-grid__item" {{ block.shopify_attributes }}>
{%- if block.settings.icon != blank -%}
<div class="feature-grid__icon">
<img
src="{{ block.settings.icon | image_url: width: 96 }}"
alt=""
width="48"
height="48"
loading="lazy"
>
</div>
{%- endif -%}
<h3 class="feature-grid__title">{{ block.settings.title }}</h3>
<p class="feature-grid__text">{{ block.settings.text }}</p>
</div>
{%- endfor -%}
</div>
</section>
{% schema %}
{
"name": "Feature Grid",
"settings": [
{
"type": "text",
"id": "heading",
"label": "Heading",
"default": "Why Choose Us"
},
{
"type": "select",
"id": "columns",
"label": "Columns",
"options": [
{ "value": "2", "label": "2" },
{ "value": "3", "label": "3" },
{ "value": "4", "label": "4" }
],
"default": "3"
}
],
"blocks": [
{
"type": "feature",
"name": "Feature",
"settings": [
{
"type": "image_picker",
"id": "icon",
"label": "Icon"
},
{
"type": "text",
"id": "title",
"label": "Title",
"default": "Feature Name"
},
{
"type": "textarea",
"id": "text",
"label": "Description",
"default": "Describe this feature and its benefit."
}
]
}
],
"presets": [
{
"name": "Feature Grid",
"blocks": [
{ "type": "feature" },
{ "type": "feature" },
{ "type": "feature" }
]
}
]
}
{% endschema %}Advanced Section Patterns

Once you've built the basics, these patterns take your sections to the next level.
Dynamic Collection References
Let merchants select a collection and display its products:
{%- assign collection = collections[section.settings.collection] -%}
{%- for product in collection.products limit: section.settings.limit -%}
<div class="product-card">
<a href="{{ product.url }}">
<img src="{{ product.featured_image | image_url: width: 600 }}" alt="{{ product.title }}" loading="lazy">
<h3>{{ product.title }}</h3>
<p>{{ product.price | money }}</p>
</a>
</div>
{%- endfor -%}Metafield-Powered Content
Pull dynamic content from metafields for truly custom data:
{%- assign sizing_chart = product.metafields.custom.sizing_chart -%}
{%- if sizing_chart != blank -%}
<div class="sizing-chart">
{{ sizing_chart | metafield_tag }}
</div>
{%- endif -%}Responsive Background Video
{%- if section.settings.video != blank -%}
<video
autoplay muted loop playsinline
class="hero__video"
poster="{{ section.settings.image | image_url: width: 1920 }}"
>
{%- for source in section.settings.video.sources -%}
<source src="{{ source.url }}" type="{{ source.mime_type }}">
{%- endfor -%}
</video>
{%- endif -%}Use the video setting type in your schema:
{
"type": "video",
"id": "video",
"label": "Background video"
}For deeper customization patterns, connect with the Shopify experts network — experienced developers can help with complex section architectures.
Performance Best Practices for Sections
Every section you build contributes to (or detracts from) page performance. These rules keep your sections fast:
Image Optimization
{%- comment -%} Always use srcset for responsive images {%- endcomment -%}
<img
src="{{ image | image_url: width: 800 }}"
srcset="
{{ image | image_url: width: 400 }} 400w,
{{ image | image_url: width: 800 }} 800w,
{{ image | image_url: width: 1200 }} 1200w
"
sizes="(max-width: 768px) 100vw, 50vw"
alt="{{ image.alt | escape }}"
loading="lazy"
width="800"
height="{{ 800 | divided_by: image.aspect_ratio | round }}"
>CSS Loading Strategy
Only load a section's CSS when the section is on the page:
{%- comment -%} At the top of your section {%- endcomment -%}
{{ 'section-my-section.css' | asset_url | stylesheet_tag }}JavaScript: Progressive Enhancement
{%- comment -%} Load JS only when needed, defer it {%- endcomment -%}
<script src="{{ 'section-carousel.js' | asset_url }}" defer></script>Avoiding Common Performance Killers
| Do This | Not This |
|---|---|
| loading="lazy" on below-fold images | Load all images eagerly |
| Load section CSS via stylesheet_tag | Put all CSS in theme.css |
| Use srcset with multiple sizes | Serve one huge image |
| Defer non-critical JavaScript | Use inline <script> tags |
| Set explicit width and height on images | Let browsers guess dimensions (CLS) |
Shogun's guide to custom Liquid sections covers additional performance patterns specific to high-traffic stores.
Testing and Debugging Your Sections

The Theme Editor Test
After creating a section, verify:
- The section appears in the "Add section" picker
- All settings render correctly in the editor sidebar
- Changes save and preview in real-time
- Blocks can be added, removed, and reordered
- The section renders properly with empty/default values
Common Debugging Techniques
{%- comment -%} Dump all section settings for debugging {%- endcomment -%}
<pre>{{ section.settings | json }}</pre>
{%- comment -%} Check block count {%- endcomment -%}
<p>Blocks: {{ section.blocks.size }}</p>
{%- comment -%} Verify a collection reference {%- endcomment -%}
{% assign col = collections[section.settings.collection] %}
{% if col %}
Collection: {{ col.title }} ({{ col.products_count }} products)
{% else %}
No collection selected
{% endif %}Mobile Testing
Always test sections on real mobile devices. Common issues:
- Text overflows on small screens
- Touch targets too small (minimum 44x44px)
- Images not responsive (missing srcset)
- Horizontal scrolling from fixed widths
Instant.so's guide to custom Liquid covers mobile-first section patterns that prevent these issues.
Common Mistakes When Building Sections
Mistake #1: Missing Presets
Without a presets array in your schema, the section won't appear in the "Add section" picker. Merchants can't use a section they can't find.
Mistake #2: Hardcoding Content
Every piece of text, image, and link should come from settings or blocks. If a merchant can't change it through the editor, it shouldn't be in your section.
Mistake #3: Forgetting block.shopify_attributes
This attribute connects rendered blocks to the theme editor. Without it, clicking a block in the editor won't highlight it on the page — and merchants won't know what they're editing.
Mistake #4: Not Handling Empty States
Always check for blank values before rendering:
{%- comment -%} Safe: handles missing content gracefully {%- endcomment -%}
{%- if section.settings.heading != blank -%}
<h2>{{ section.settings.heading }}</h2>
{%- endif -%}Mistake #5: Ignoring Accessibility
- Add
alttext to every image (use| escapefilter) - Ensure color contrast meets WCAG standards
- Make interactive elements keyboard accessible
- Use semantic HTML (
<nav>,<main>,<section>,<article>)
For help building accessible, performant sections, explore Talk Shop's theme design resources and our analytics guides for measuring section performance.
Your Section Building Roadmap

Learning how to build custom Shopify sections follows a natural progression. Here's what to build at each skill level:
Beginner (Week 1-2):
- Hero banner with image, text, and CTA
- Simple text + image section (side by side)
- Announcement bar
Intermediate (Week 3-4):
- Testimonials with blocks
- FAQ accordion with schema markup
- Featured collection grid with product cards
Advanced (Month 2+):
- Custom product page sections with metafield integration
- Multi-column sections with responsive breakpoints
- Sections with JavaScript interactivity (carousels, tabs, modals)
- Theme blocks shared across multiple sections
Each section you build adds to a reusable library that accelerates every future project. The best developers don't rebuild from scratch — they compose from proven sections. Share what you're building with the Talk Shop community — we love reviewing section code and sharing patterns.

About Talk Shop
The Talk Shop team — insights from our community of Shopify developers, merchants, and experts.
Related Insights
The ecommerce newsletter that's actually useful.
Daily trends, teardowns, and tactics from the top 1% of ecommerce brands. Delivered every morning.
