Why reusability matters in Shopify theme work
When you're building themes at an agency, you don't build one store — you build many. Each client has different branding, content structure, and requirements. Copy-pasting sections between projects is a trap: one bug fix in project A needs to be replicated manually in B, C, and D.
OS 2.0 introduced the concept of Sections Everywhere — sections that can be placed on any page, not just the homepage. Combined with a well-designed block architecture, this gives you a component model that's genuinely reusable across projects.
These patterns come from real agency work at Lime, where I built and maintained sections across multiple client Shopify Plus stores. The goal was always: write once, adapt quickly.
Section anatomy: what a good OS 2.0 section looks like
A reusable section has three layers: the schema (what merchants configure), the Liquid template (what renders), and the styles (scoped to this section). Keep these concerns separate even inside a single .liquid file.
{%- liquid
assign heading = section.settings.heading
assign layout = section.settings.layout
-%}
<section id="shopify-section-{{ section.id }}"
class="feature-grid feature-grid--{{ layout }}">
{%- if heading != blank -%}
<h2 class="feature-grid__heading">
{{- heading -}}
</h2>
{%- endif -%}
<div class="feature-grid__blocks">
{%- for block in section.blocks -%}
{%- case block.type -%}
{%- when 'feature_card' -%}
{%- render 'feature-card',
block: block,
forloop: forloop -%}
{%- endcase -%}
{%- endfor -%}
</div>
</section>
Notice the {{ layout }} modifier class on the root element — this single setting lets merchants switch between grid layouts without touching code. The blocks are delegated to a snippet (feature-card), keeping the section file readable.
Block design: one type, many configurations
The mistake I see most often is creating too many block types for what is essentially the same component with different content. Instead, design one block type with enough settings to cover the variants:
"blocks": [
{
"type": "feature_card",
"name": "Feature card",
"settings": [
{ "type": "image_picker", "id": "image", "label": "Image" },
{ "type": "text", "id": "heading", "label": "Heading" },
{ "type": "textarea", "id": "body", "label": "Body text" },
{ "type": "url", "id": "link_url", "label": "Link URL" },
{ "type": "text", "id": "link_label","label": "Link label" },
{
"type": "select", "id": "style", "label": "Card style",
"default": "outlined",
"options": [
{ "value": "outlined", "label": "Outlined" },
{ "value": "filled", "label": "Filled" },
{ "value": "minimal", "label": "Minimal" }
]
}
]
}
]
The style selector means a merchant can switch between card appearances without needing a new block type or developer involvement. One block type — three visual variants.
Scoped SCSS/BEM and the section.id pattern
One practical problem with reusable sections: if you have the same section placed twice on a page, global CSS classes will collide if a merchant needs different settings for each instance. The solution is to scope styles to the section ID:
<style>
#shopify-section-{{ section.id }} {
--grid-columns: {{ section.settings.columns }};
--gap: {{ section.settings.gap }}px;
padding-block: {{ section.settings.padding_vertical }}px;
}
</style>
This outputs a scoped CSS custom property block per section instance. The actual component CSS uses var(--grid-columns) — layout is controlled by the merchant's settings without JavaScript.
In Dawn-based themes, inline <style> tags inside section files are collected and deduped by the theme — so you get scoped styles without performance penalty on repeated renders.
Metafields as a flexible content source
For content that belongs to a product or collection but doesn't fit into standard Shopify fields, metafields are the right tool. A section that renders product specs, for example, shouldn't hardcode those fields in the schema — it should pull from a metafield definition:
{%- assign specs = product.metafields.custom.specs.value -%}
{%- if specs != blank -%}
<ul class="product-specs">
{%- for spec in specs -%}
<li class="product-specs__item">
<span class="product-specs__label">{{- spec.label -}}</span>
<span class="product-specs__value">{{- spec.value -}}</span>
</li>
{%- endfor -%}
</ul>
{%- endif -%}
The section renders nothing if the metafield is empty — no broken layouts on products that don't have specs defined. This pattern scales well across a large catalogue where not every product has the same data structure.
What makes a section "reusable" in practice
When I review whether a section is truly reusable, I check these things:
- No hardcoded content — all text, images, and URLs come from settings or metafields
- Graceful empty states — if a setting is blank, the element doesn't render (no empty
<h2>tags in the DOM) - Scoped styles — CSS is either scoped to
#shopify-section-{{ section.id }}or uses BEM classes specific enough not to collide - No JS dependencies in the section file itself — logic lives in a separate asset file, the section only renders HTML
- Sensible defaults — a fresh section added to a page should look reasonable without any configuration
Sections that pass all five can be dropped into a new project with minimal adaptation — usually just a SCSS variable update to match the new brand's colour system.
Need a Shopify theme developer?
I build production Shopify themes — pixel-perfect from Figma, composable OS 2.0 architecture, Checkout UI Extensions, and clean Liquid. Available for remote projects.