Theme Customization

Build Custom Shopify Sections Like a Pro

A complete development guide covering Liquid templates, JSON schema, JavaScript integration, and best practices for Shopify Online Store 2.0.

April 3, 202513 min read

What Are Shopify Sections?

Sections are modular, reusable content blocks that make up your Shopify store's pages. Since the introduction of Online Store 2.0, sections can be added to any page (not just the homepage), making them the fundamental building blocks of Shopify theme architecture. Each section is a self-contained Liquid file that includes its own HTML structure, optional CSS and JavaScript, and a JSON schema that defines what store owners can configure in the theme editor. The power of sections lies in their dual nature: they are code for developers but visual drag-and-drop blocks for store owners. A well-built section lets a non-technical merchant completely change their page content without touching a line of code.

Section Architecture

Every Shopify section lives in the sections/ directory of your theme and follows a consistent structure. The Liquid template is the HTML markup with dynamic data pulled from settings and blocks. It uses Liquid tags and filters to render content. The schema is a JSON object at the bottom of the file wrapped in {% schema %} tags. It defines the section name, settings (global section options), blocks (repeatable items within the section), and presets (default configurations for the theme editor). Optional CSS and JavaScript can be included inline via {% style %} and {% javascript %} tags, or referenced as external assets. Blocks are the secret weapon of section development. They allow store owners to add, remove, and reorder items within a section. Think of a testimonial carousel where each testimonial is a block: the store owner can add 3 testimonials or 30, reorder them, and edit each one individually.

Basic Section Template

liquid
{%- comment -%}
  sections/custom-testimonials.liquid
  A reusable testimonial section with configurable layout.
{%- endcomment -%}

<div
  class="testimonials-section"
  style="
    padding-top: {{ section.settings.padding_top }}px;
    padding-bottom: {{ section.settings.padding_bottom }}px;
    background-color: {{ section.settings.background_color }};
  "
>
  <div class="page-width">
    {%- if section.settings.heading != blank -%}
      <h2 class="testimonials-section__heading h2">
        {{ section.settings.heading | escape }}
      </h2>
    {%- endif -%}

    {%- if section.settings.subheading != blank -%}
      <p class="testimonials-section__subheading">
        {{ section.settings.subheading | escape }}
      </p>
    {%- endif -%}

    <div class="testimonials-grid testimonials-grid--{{ section.settings.columns }}">
      {%- for block in section.blocks -%}
        <div class="testimonial-card" {{ block.shopify_attributes }}>
          {%- if block.settings.rating > 0 -%}
            <div class="testimonial-card__stars" aria-label="{{ block.settings.rating }} out of 5 stars">
              {%- for i in (1..block.settings.rating) -%}
                <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
                  <path d="M10 1l2.39 6.41H19l-5.3 4.03 1.97 6.56L10 14.27 4.33 18l1.97-6.56L1 7.41h6.61z"/>
                </svg>
              {%- endfor -%}
            </div>
          {%- endif -%}

          <blockquote class="testimonial-card__quote">
            {{ block.settings.quote }}
          </blockquote>

          <div class="testimonial-card__author">
            {%- if block.settings.avatar != blank -%}
              {{
                block.settings.avatar
                | image_url: width: 80
                | image_tag: loading: 'lazy', class: 'testimonial-card__avatar', width: 40, height: 40
              }}
            {%- endif -%}
            <div>
              <cite class="testimonial-card__name">{{ block.settings.name | escape }}</cite>
              {%- if block.settings.title != blank -%}
                <span class="testimonial-card__title">{{ block.settings.title | escape }}</span>
              {%- endif -%}
            </div>
          </div>
        </div>
      {%- endfor -%}
    </div>
  </div>
</div>

Section Schema Definition

json
{% schema %}
{
  "name": "Testimonials",
  "tag": "section",
  "class": "section-testimonials",
  "settings": [
    {
      "type": "text",
      "id": "heading",
      "label": "Heading",
      "default": "What Our Customers Say"
    },
    {
      "type": "text",
      "id": "subheading",
      "label": "Subheading",
      "default": "Trusted by hundreds of Shopify store owners"
    },
    {
      "type": "select",
      "id": "columns",
      "label": "Columns",
      "options": [
        { "value": "2", "label": "2 columns" },
        { "value": "3", "label": "3 columns" },
        { "value": "4", "label": "4 columns" }
      ],
      "default": "3"
    },
    {
      "type": "color",
      "id": "background_color",
      "label": "Background color",
      "default": "#f8f9fa"
    },
    {
      "type": "range",
      "id": "padding_top",
      "min": 0,
      "max": 100,
      "step": 4,
      "unit": "px",
      "label": "Top padding",
      "default": 60
    },
    {
      "type": "range",
      "id": "padding_bottom",
      "min": 0,
      "max": 100,
      "step": 4,
      "unit": "px",
      "label": "Bottom padding",
      "default": 60
    }
  ],
  "blocks": [
    {
      "type": "testimonial",
      "name": "Testimonial",
      "settings": [
        {
          "type": "textarea",
          "id": "quote",
          "label": "Quote text",
          "default": "This service transformed our Shopify store."
        },
        {
          "type": "text",
          "id": "name",
          "label": "Author name",
          "default": "Jane Smith"
        },
        {
          "type": "text",
          "id": "title",
          "label": "Author title",
          "default": "Store Owner"
        },
        {
          "type": "image_picker",
          "id": "avatar",
          "label": "Author photo"
        },
        {
          "type": "range",
          "id": "rating",
          "min": 0,
          "max": 5,
          "step": 1,
          "label": "Star rating",
          "default": 5
        }
      ]
    }
  ],
  "presets": [
    {
      "name": "Testimonials",
      "blocks": [
        { "type": "testimonial" },
        { "type": "testimonial" },
        { "type": "testimonial" }
      ]
    }
  ]
}
{% endschema %}

Section Styles

css
/* Add to your section or to assets/custom-testimonials.css */
.testimonials-section__heading {
  text-align: center;
  margin-bottom: 0.5rem;
}

.testimonials-section__subheading {
  text-align: center;
  color: #6b7280;
  margin-bottom: 2.5rem;
  max-width: 600px;
  margin-left: auto;
  margin-right: auto;
}

.testimonials-grid {
  display: grid;
  gap: 1.5rem;
}

.testimonials-grid--2 { grid-template-columns: repeat(2, 1fr); }
.testimonials-grid--3 { grid-template-columns: repeat(3, 1fr); }
.testimonials-grid--4 { grid-template-columns: repeat(4, 1fr); }

@media (max-width: 749px) {
  .testimonials-grid--2,
  .testimonials-grid--3,
  .testimonials-grid--4 {
    grid-template-columns: 1fr;
  }
}

.testimonial-card {
  background: white;
  border-radius: 8px;
  padding: 1.5rem;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}

.testimonial-card__stars {
  display: flex;
  gap: 2px;
  color: #f59e0b;
  margin-bottom: 1rem;
}

.testimonial-card__quote {
  font-style: italic;
  line-height: 1.6;
  margin: 0 0 1.25rem;
  color: #374151;
}

.testimonial-card__author {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.testimonial-card__avatar {
  border-radius: 50%;
  object-fit: cover;
}

.testimonial-card__name {
  font-style: normal;
  font-weight: 600;
  display: block;
}

.testimonial-card__title {
  font-size: 0.875rem;
  color: #6b7280;
}

Section Development Best Practices

  • Always provide default values in your schema so sections look good out of the box when a merchant first adds them
  • Use block.shopify_attributes on block elements to enable the theme editor's click-to-select functionality
  • Wrap text outputs in escape filters to prevent XSS vulnerabilities: {{ settings.heading | escape }}
  • Support responsive design with mobile-first CSS and use Shopify's page-width container class for consistent margins
  • Use Shopify's image_url and image_tag filters for automatic responsive images and CDN optimization
  • Include meaningful alt text on images using settings or product data for accessibility
  • Keep JavaScript minimal within sections and defer loading when possible
  • Use CSS custom properties for theme colors so sections inherit the merchant's color scheme
  • Test sections with zero blocks, one block, and many blocks to ensure graceful handling of edge cases
  • Use section.settings.heading != blank checks to hide empty elements instead of rendering blank HTML
Tip

When building JavaScript-heavy sections (carousels, accordions, tabs), use the Intersection Observer API to initialize JavaScript only when the section scrolls into view. This prevents unused sections from affecting page load performance.

Lazy JavaScript Initialization

javascript
// Initialize section JavaScript only when visible
class TestimonialCarousel extends HTMLElement {
  constructor() {
    super();
    this.initialized = false;
  }

  connectedCallback() {
    // Wait until the section is visible before initializing
    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting && !this.initialized) {
            this.init();
            this.initialized = true;
            observer.unobserve(this);
          }
        });
      },
      { rootMargin: '200px' } // Start loading slightly before visible
    );
    observer.observe(this);
  }

  init() {
    // Your carousel initialization code here
    this.slides = this.querySelectorAll('.carousel-slide');
    this.currentIndex = 0;
    this.setupNavigation();
    this.startAutoplay();
  }

  setupNavigation() {
    const prevBtn = this.querySelector('[data-prev]');
    const nextBtn = this.querySelector('[data-next]');
    prevBtn?.addEventListener('click', () => this.navigate(-1));
    nextBtn?.addEventListener('click', () => this.navigate(1));
  }

  navigate(direction) {
    this.currentIndex =
      (this.currentIndex + direction + this.slides.length) %
      this.slides.length;
    this.updateSlides();
  }

  updateSlides() {
    this.slides.forEach((slide, index) => {
      slide.classList.toggle('active', index === this.currentIndex);
    });
  }

  startAutoplay() {
    this.interval = setInterval(() => this.navigate(1), 5000);
    this.addEventListener('mouseenter', () => clearInterval(this.interval));
    this.addEventListener('mouseleave', () => this.startAutoplay());
  }
}

customElements.define('testimonial-carousel', TestimonialCarousel);

Need a Custom Section Built?

Describe the section you need and our AI consultant will scope the work and provide a quote. Custom sections typically take 1 -- 3 days to build.

Get a Section Quote

Working with Metafields in Sections

Metafields unlock dynamic content that goes beyond Shopify's default product and page fields. You can create custom fields (ingredients lists, size charts, care instructions, brand stories) and render them in your sections. To use metafields in a section, reference them through the resource they are attached to. For product metafields: product.metafields.custom.your_field_name. For page metafields: page.metafields.custom.your_field_name. You can also use dynamic sources in your schema to let merchants select metafield values in the theme editor. Add a setting with type: 'product' or type: 'page' and access its metafields in the template. Metaobjects take this further by creating entirely custom content types. A 'Team Member' metaobject with fields for name, photo, bio, and role can be rendered beautifully in a custom team section. Store owners manage the content through the Shopify admin, and your section renders it dynamically.

Debugging and Testing Sections

Section development has some unique debugging challenges. The theme editor preview can behave differently from the live storefront. Always test on both. Some JavaScript that works in preview may fail on the live site due to different loading order. Use the Shopify Theme Inspector Chrome extension to identify performance bottlenecks in your Liquid rendering. It shows exactly how long each section takes to render server-side. Test your sections across different states: with zero blocks, with one block, with maximum blocks. Test with very long text, very short text, and no text. Test with images of different aspect ratios. Edge cases are where sections break. For JavaScript debugging, use the browser console and the Shopify developer tools. The section rendering API (Section Rendering API) is particularly useful for debugging sections that update dynamically without full page reloads.

Complex Section? We Can Help.

From animated carousels to dynamic product showcases with metafield integration, our team builds custom Shopify sections at $40/hour with a clear scope and timeline.

Describe Your Section

Ready to Get Started?

Chat with our AI consultant to scope your project and receive a transparent quote in minutes.

Start a Consultation