Create Customizable Sections in Shopify

When you peel back the glossy storefront of a Shopify site, what you’ll find underneath isn’t a tangle of random code. It’s a clean system of building blocks called sections.

Every banner, every product grid, every testimonial carousel, all of it lives inside these modular, self-contained units.

Sections are what make Shopify themes so powerful (and friendly for non-developers). They let merchants drag, drop, reorder, and customize entire parts of a page without touching a single line of code. And for developers, sections are the perfect mix of structure and flexibility. Each one is its own micro-template, with its own settings, content blocks, and styling logic.

A Shopify theme isn’t one giant page. It’s a collection of reusable sections stitched together by JSON templates. Once you understand how sections work, you understand the heart of Shopify theme development.

Start with the Schema

Before you write a single line of Liquid or start wrapping divs around things, start with the schema.

Why?

Because the schema is the blueprint of your section. It tells Shopify what kind of content the section can hold, what options the merchant can tweak, and how those inputs appear inside the Theme Editor.

Think of it like sketching the wireframe before painting the wall. Once your schema is ready, you know exactly what variables you’ll have access to in your Liquid code (section.settings, block.settings, and so on). That means fewer surprises and a much cleaner development flow.

Each schema lives inside the {% schema %} and {% endschema %} tags at the bottom of your section file. It’s written in JSON and usually defines:

  • Settings: the editable fields for the whole section (like heading text or layout style).
  • Blocks: repeatable sub-components (like features, testimonials, or slides).
  • Presets: pre-filled demo content that appears when you add the section in the Theme Editor.

A good schema answers three questions upfront:

  1. What will this section display?
  2. What should the merchant be able to change?
  3. What defaults should make it look good out of the box?

By starting with the schema, you’re building from the top down — defining the data structure first, then writing the markup and CSS to bring it to life. It’s a small shift in workflow that makes a huge difference in speed, stability, and sanity.

A Testimonial Section Example

Let’s say you want to build a simple Testimonials section.

Before worrying about layout or styling, you start by defining what this section is and what data it needs. That’s your schema.

Here’s what that looks like:

{% schema %}
{
  "name": "Testimonials",
  "tag": "section",
  "settings": [
    {
      "type": "text",
      "id": "heading",
      "label": "Section Heading",
      "default": "What our customers say"
    },
    {
      "type": "select",
      "id": "layout",
      "label": "Layout Style",
      "options": [
        { "value": "grid", "label": "Grid" },
        { "value": "carousel", "label": "Carousel" }
      ],
      "default": "grid"
    }
  ],
  "blocks": [
    {
      "type": "testimonial",
      "tag": null,
      "name": "Testimonial",
      "settings": [
        { "type": "text", "id": "author", "label": "Customer Name" },
        { "type": "textarea", "id": "quote", "label": "Customer Quote" },
        { "type": "image_picker", "id": "photo", "label": "Customer Photo" }
      ]
    }
  ],
  "max_blocks": 6,
  "presets": [
    {
      "name": "Testimonials",
      "blocks": [
        {
          "type": "testimonial",
          "settings": {
            "author": "Jane D.",
            "quote": "This product changed my workflow completely!"
          }
        },
        {
          "type": "testimonial",
          "settings": {
            "author": "Rahul K.",
            "quote": "Incredible experience and top-notch support."
          }
        }
      ]
    }
  ]
}
{% endschema %}

What You’ve Defined

  • The section will appear in the Theme Editor as “Testimonials” and wrap itself in a <section> tag.
  • It has two customizable settings: a heading and a layout style.
  • It supports up to six testimonial blocks, each with a name, quote, and optional photo.
  • It includes a preset. This means when someone adds it to a page, they’ll already see two ready-made testimonials to start with.

You can read more about the available attributes and settings in the Shopify section schema documentation.

With this schema alone, the Theme Editor is already aware of your section.

You can add it, rearrange it, and play with its settings even before you’ve written a single <div>.

That’s the beauty of schema-first development: you define what’s possible before defining how it looks.

Build the Markup

With your schema defined, Shopify already knows what data the section can hold. Now it’s your job to decide how to display it. Every setting you created in the schema is automatically available through Liquid objects like section.settings and block.settings.

Let’s take the Testimonials schema we just built and give it a simple but elegant layout.

<div class="testimonials-section testimonials--{{ section.settings.layout }}">
  {% if section.settings.heading != blank %}
    <h2 class="testimonials-heading">{{ section.settings.heading }}</h2>
  {% endif %}

  <div class="testimonials-list">
    {% for block in section.blocks %}
      {% if block.type == 'testimonial' %}
        <div class="testimonial-item" {{ block.shopify_attributes }}>
          {% if block.settings.photo %}
            <div class="testimonial-photo">
              <img
                src="{{ block.settings.photo | image_url: width: 160 }}"
                alt="{{ block.settings.author | escape }}"
                loading="lazy"
              >
            </div>
          {% endif %}
          <blockquote class="testimonial-quote">
            “{{ block.settings.quote }}”
          </blockquote>
          {% if block.settings.author %}
            <cite class="testimonial-author">— {{ block.settings.author }}</cite>
          {% endif %}
        </div>
      {% endif %}
    {% endfor %}
  </div>
</div>

What’s Happening Here

  • section.settings.layout is pulled straight from the schema’s “Layout Style” dropdown, letting you output a class like testimonials--grid or testimonials--carousel.
  • Each block represents one testimonial added through the Theme Editor.
  • block.shopify_attributes is a built-in helper that adds the necessary attributes for in-editor highlighting (so merchants can click a testimonial directly in the preview to edit it).
  • The code checks for optional fields like photo or author before rendering them. This is a good habit to avoid empty tags.

Even without styling, this will already render a functioning, editable testimonials section.

Shopify automatically adds a wrapper div element around each block with a unique id attribute. Set the tag attribute to null if you don’t want it to wrap the block in anything. However, you should use block.shopify_attributes in that case to add the necessary data attributes for the block to be identified by the theme editor and make it compatible with the theme editor.

Blocks that make use of "tag": null should contain a single top level HTML tag within the same Liquid file. Only a single HTML element can be tagged with {{ block.shopify_attributes }}. This element should be the topmost HTML element in the file. This is important to allow the theme editor to move the entirety of the block’s markup to a new index when merchants re-order blocks without leaving orphaned HTML elements.

You can read more about the available block attributes and settings in the Shopify block schema documentation.

Add Some Style

Now wrap it up with minimal CSS right below your Liquid markup (or link to a stylesheet):

<style>
  .testimonials-section {
    max-width: 1000px;
    margin: 3rem auto;
    padding: 0 1rem;
    text-align: center;
  }

  .testimonials-list {
    display: grid;
    gap: 2rem;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  }

  .testimonial-item {
    background: #fff;
    border-radius: 10px;
    padding: 1.5rem;
    box-shadow: 0 2px 10px rgba(0,0,0,0.05);
  }

  .testimonial-photo img {
    border-radius: 50%;
    width: 80px;
    height: 80px;
    object-fit: cover;
  }

  .testimonial-quote {
    font-style: italic;
    margin: 1rem 0;
  }

  .testimonial-author {
    font-weight: bold;
    color: #333;
  }
</style>

Try It in the Theme Editor

  1. Add your new section file to sections/testimonials.liquid.
  2. Open the Theme Editor and click Add section → Testimonials.
  3. You’ll see your schema fields and prefilled blocks instantly.
  4. Edit text, change layout, or add more testimonials. No code required.

Inline Variables and Smart Class Names

If your section has user-defined settings like background color, column count, spacing, or even layout toggles, you might be tempted to create unique CSS for every instance using something like .section-{{ section.id }}. That works, but it’s not elegant. Every section instance ends up with its own style block, your CSS becomes uncacheable, and your HTML fills up with identifiers that don’t mean anything to humans.

There’s a cleaner, modern approach that Shopify’s own themes now use: inline CSS variables, optionally combined with modifier class names.

The Inline Variable Method

Instead of generating new CSS rules per section, you can pass dynamic values directly into your HTML as inline variables.

<div class="testimonials-section"
     style="--bg: {{ section.settings.bg_color }};
            --columns: {{ section.settings.columns }};
            --gap: {{ section.settings.spacing }}px;">
  ...
</div>

Then your CSS stays static and reusable:

.testimonials-section {
  background-color: var(--bg);
  display: grid;
  gap: var(--gap);
  grid-template-columns: repeat(var(--columns), 1fr);
}

Each section instance now carries its own styling values internally. No section-{{ section.id }} needed.

The variables are automatically scoped to that section, which means multiple “Testimonials” sections can appear on the same page without clashing.

This is the pattern Shopify’s Dawn and other modern themes rely on because:

  • CSS remains cacheable (no redundant <style> tags).
  • The markup stays clean and semantic.
  • Each section behaves like its own small “theme instance.”

The Class Modifier Method

For some settings like layout options that map to a few predictable scenarios (e.g., 2 columns, 3 columns, 4 columns) using modifier class names is even simpler.

<div class="testimonials-section testimonials--{{ section.settings.columns }}">
  ...
</div>

Your CSS defines those scenarios:

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

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

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

Now, the merchant’s choice in the Theme Editor (2, 3, or 4 columns) directly maps to a class, and your CSS remains fully external, fast, and cacheable.

This “class modifier” approach is perfect for discrete design choices i.e., fixed sets of options you can plan for ahead of time. On the other hand, inline variables shine when users need continuous or custom values (colors, spacing, numeric controls, etc.).

Leverage Metafields and Metaobjects

Everything we’ve discussed so far keeps section content fairly static. Sure, you could tweak the heading or description in each section, but that approach falls apart once you’re managing hundreds of products. Especially if many of them share the same type of content.

Metafields and metaobjects are Shopify’s way of turning themes into proper content systems. They let you connect reusable data to products, collections, or even entire pages, so you can display dynamic content without manually editing dozens of section instances.

I used this approach for a client who wanted to display ingredient information on each product page. Metafields and metaobjects excel here because every product has its own unique list of ingredients but the same ingredient might appear in multiple products. By defining ingredients as metaobjects and connecting them through a product metafield, I could dynamically output the right set of ingredients for each product while keeping the ingredient data centralized and reusable.

Address the Dreaded Liquid Overload

Your Liquid code for the testimonials section works beautifully now. However, as you add more block types, loops, and conditional logic, your section file can start looking like a miniature spaghetti factory. It happens to everyone but you should avoid it.

Instead of creating a section file that tries to handle every possible block variation directly inside the {% for block in section.blocks %} loop, move each block type’s code into its own snippet.

Example

Inside your sections/testimonials.liquid file:

{% for block in section.blocks %}
  {% case block.type %}
    {% when 'testimonial' %}
      {% render 'block-testimonial', block: block %}
    {% when 'quote' %}
      {% render 'block-quote', block: block %}
  {% endcase %}
{% endfor %}

And then in snippets/block-testimonial.liquid:

<div class="testimonial" {{ block.shopify_attributes }}>
  <blockquote>“{{ block.settings.quote }}”</blockquote>
  <cite>{{ block.settings.author }}</cite>
</div>

This keeps your main section clean, readable, and easy to extend later. If you ever add a new block type, you just drop a new snippet file. No need to scroll through 200 lines of Liquid.

Differentiate Blocks with Targeted Presets

Your current testimonial preset already does what a good default should. It gives the merchant a ready-to-use section with a couple of sample testimonials instead of a blank canvas. That’s exactly how presets are meant to work. They make your section feel functional the moment it’s added.

But you can take presets even further.

Right now, your section offers one default configuration. If a merchant wants a different layout such as a two-column grid instead of a single-column list, or a version with images instead of just text, they’d still need to manually change the settings after adding it.

To make the Theme Editor experience smoother, you can create multiple presets, each representing a variation of your section. Shopify will list each preset separately under “Add section,” so the merchant can choose the layout or style they want from the start.

For example:

"presets": [
  {
    "name": "Testimonials — Simple Grid",
    "settings": { "layout": "grid" },
    "blocks": [
      {
        "type": "testimonial",
        "settings": {
          "author": "Jane D.",
          "quote": "This product changed my workflow completely!"
        }
      },
      {
        "type": "testimonial",
        "settings": {
          "author": "Rahul K.",
          "quote": "Incredible experience and top-notch support."
        }
      }
    ]
  },
  {
    "name": "Testimonials — Carousel",
    "settings": { "layout": "carousel" },
    "blocks": [
      {
        "type": "testimonial",
        "settings": {
          "author": "Meena R.",
          "quote": "It’s so easy to manage now — our site feels alive!"
        }
      },
      {
        "type": "testimonial",
        "settings": {
          "author": "Carlos M.",
          "quote": "The smooth scrolling carousel layout looks fantastic on mobile."
        }
      },
      {
        "type": "testimonial",
        "settings": {
          "author": "Anna S.",
          "quote": "Our customers love how authentic these testimonials look."
        }
      }
    ]
  }
]

Now, when the merchant clicks Add section, they’ll see two separate options:

  • Testimonials — Simple Grid
  • Testimonials — Carousel

Each one already has the right layout and a few demo blocks to make the preview look real from the start.
This saves the merchant from adding and deleting blocks manually and makes your section feel much more intentional and user-friendly.

Wrapping It Up

Building sections in Shopify isn’t just about getting something to render. It’s about creating flexible, reusable components that make your theme easier to maintain and a joy to use for merchants.

  1. Starting with the schema gives your section structure.
  2. Smart styling with variables and classes keeps it clean and scalable.
  3. Metafields and metaobjects bring true dynamic content into the mix.
  4. And well-thought-out presets and snippets make the Theme Editor experience smooth and professional.

The magic happens when you treat each section like a self-contained system with structured data, thoughtful markup, and minimal Liquid noise. The less you repeat yourself, the better your theme performs and the easier it is for someone else (or future you) to extend it later.

So, whether you’re building a product benefits grid, testimonials, or dynamic ingredient lists, the mindset stays the same: define clearly, build cleanly, and connect smartly.

Once you start thinking of sections as modular, data-driven building blocks rather than static layouts, Shopify stops feeling like a page builder and starts behaving like a real content framework.

CategoriesHow-To Guides

Leave a Comment

Your email address will not be published.

0%