Snippets and Render Patterns
Learn how to create reusable Liquid snippets and use the render tag to build maintainable, component-based Shopify themes.
Snippets are reusable pieces of Liquid code that help you avoid repetition and keep your theme organized. Think of them as components you can use throughout your theme. Let’s explore how to create and use them effectively.
What Are Snippets?
Snippets are Liquid files stored in the snippets/ directory. They contain reusable code that you can include in templates, sections, or other snippets.
Common uses for snippets:
- Product cards
- Price displays
- Icon components
- Social sharing buttons
- Form inputs
- Pagination
theme/├── snippets/│ ├── product-card.liquid│ ├── price.liquid│ ├── icon.liquid│ ├── social-share.liquid│ └── pagination.liquidThe render Tag
Use render to include a snippet in your code:
{% render 'product-card' %}This looks for snippets/product-card.liquid and inserts its contents.
Passing Variables
Snippets have isolated scope by default. They can’t access variables from the parent template unless you pass them explicitly:
{# In a section or template #}{% render 'product-card', product: featured_product %}{# snippets/product-card.liquid #}<article class="product-card"> <h3>{{ product.title }}</h3> <p>{{ product.price | money }}</p></article>Multiple Variables
Pass multiple variables by separating them with commas:
{% render 'product-card', product: featured_product, show_vendor: true, show_description: false, image_size: 'medium'%}{# snippets/product-card.liquid #}<article class="product-card"> {% if product.featured_image %} {% case image_size %} {% when 'small' %} {% assign width = 200 %} {% when 'large' %} {% assign width = 600 %} {% else %} {% assign width = 400 %} {% endcase %}
<img src="{{ product.featured_image | image_url: width: width }}" alt="{{ product.featured_image.alt | escape }}"> {% endif %}
<h3>{{ product.title }}</h3>
{% if show_vendor %} <p class="vendor">{{ product.vendor }}</p> {% endif %}
{% if show_description %} <p class="description">{{ product.description | truncate: 100 }}</p> {% endif %}
<p class="price">{{ product.price | money }}</p></article>The for Shorthand
When rendering a snippet for each item in an array, use the for shorthand:
{# Instead of this: #}{% for product in collection.products %} {% render 'product-card', product: product %}{% endfor %}
{# Use this: #}{% render 'product-card' for collection.products as product %}The for shorthand is cleaner and slightly more performant. Inside the snippet, you have access to forloop:
{# snippets/product-card.liquid #}<article class="product-card {% if forloop.first %}first{% endif %}"> <span class="index">{{ forloop.index }} of {{ forloop.length }}</span> <h3>{{ product.title }}</h3></article>Combining for with Additional Variables
{% render 'product-card' for collection.products as product, show_vendor: true %}The with Shorthand
When passing a single object that should be named the same as the snippet:
{# Instead of this: #}{% render 'product-card', product: featured_product %}
{# Use this when snippet expects 'product': #}{% render 'product-card' with featured_product as product %}This is especially useful when the variable name differs from what the snippet expects:
{% assign my_item = collection.products.first %}{% render 'product-card' with my_item as product %}Scope Isolation
The render tag creates an isolated scope. The snippet can only access:
- Variables you explicitly pass
- Global objects (
shop,cart,settings, etc.)
{# In a section #}{% assign my_variable = "Hello" %}{% render 'my-snippet' %}{# snippets/my-snippet.liquid #}{{ my_variable }} {# Empty! Not accessible #}{{ shop.name }} {# Works! Global object #}{{ settings.color }} {# Works! Theme settings are global #}Why Isolation Matters
- Predictability: Snippets behave the same everywhere
- No hidden dependencies: You can see exactly what data a snippet needs
- Easier debugging: No mysterious variables appearing from parent scope
- Better reusability: Snippets are self-contained
The Deprecated include Tag
You might see include in older themes:
{# Old way (deprecated) #}{% include 'product-card' %}{% include 'product-card' with product %}{% include 'product-card', product: featured_product %}Don’t use include. It’s deprecated and has problems:
- No scope isolation (leaks variables)
- Slower performance
- Harder to debug
- May be removed in future
Always use render for new code.
Building a Component Library
Organize your snippets like a component library:
snippets/├── components/│ ├── button.liquid│ ├── badge.liquid│ ├── icon.liquid│ └── modal.liquid├── cards/│ ├── product-card.liquid│ ├── collection-card.liquid│ └── article-card.liquid├── product/│ ├── price.liquid│ ├── variant-picker.liquid│ └── quantity-selector.liquid└── layout/ ├── pagination.liquid └── breadcrumbs.liquidNote: Shopify’s snippets/ directory is flat (no subdirectories), so use prefixes instead:
snippets/├── component-button.liquid├── component-badge.liquid├── card-product.liquid├── card-collection.liquid├── product-price.liquid└── layout-pagination.liquidExample: Button Component
{# snippets/component-button.liquid #}{# Button component
Parameters: - label: Button text (required) - url: Link URL (optional, renders as <a> if provided) - style: 'primary' | 'secondary' | 'outline' (default: 'primary') - size: 'small' | 'medium' | 'large' (default: 'medium') - full_width: boolean (default: false) - disabled: boolean (default: false) - type: 'button' | 'submit' (default: 'button', only for non-link)#}
{%- assign button_style = style | default: 'primary' -%}{%- assign button_size = size | default: 'medium' -%}
{%- capture button_classes -%} button button--{{ button_style }} button--{{ button_size }} {% if full_width %}button--full-width{% endif %}{%- endcapture -%}
{% if url %} <a href="{{ url }}" class="{{ button_classes | strip_newlines | strip }}"> {{ label }} </a>{% else %} <button type="{{ type | default: 'button' }}" class="{{ button_classes | strip_newlines | strip }}" {% if disabled %}disabled{% endif %} > {{ label }} </button>{% endif %}Usage:
{% render 'component-button', label: 'Shop Now', url: '/collections/all', style: 'primary' %}
{% render 'component-button', label: 'Add to Cart', type: 'submit', style: 'secondary' %}
{% render 'component-button', label: 'Sold Out', disabled: true %}Example: Price Component
{# snippets/product-price.liquid #}{# Price display component
Parameters: - product: Product object (required) - show_compare_price: boolean (default: true) - show_saved_amount: boolean (default: false)#}
{%- assign show_compare = show_compare_price | default: true -%}{%- assign on_sale = false -%}
{%- if product.compare_at_price > product.price -%} {%- assign on_sale = true -%}{%- endif -%}
<div class="price {% if on_sale %}price--on-sale{% endif %}"> <span class="price__current"> {{ product.price | money }} </span>
{%- if on_sale and show_compare -%} <span class="price__compare"> <del>{{ product.compare_at_price | money }}</del> </span>
{%- if show_saved_amount -%} {%- assign saved = product.compare_at_price | minus: product.price -%} <span class="price__saved"> Save {{ saved | money }} </span> {%- endif -%} {%- endif -%}
{%- if product.price_varies -%} <span class="price__from">from</span> {%- endif -%}</div>Usage:
{% render 'product-price', product: product %}
{% render 'product-price', product: product, show_saved_amount: true %}
{% render 'product-price', product: product, show_compare_price: false %}Example: Icon Component
{# snippets/component-icon.liquid #}{# SVG Icon component
Parameters: - name: Icon name (required) - size: Icon size in pixels (default: 24) - class: Additional CSS classes#}
{%- assign icon_size = size | default: 24 -%}
<svg class="icon icon-{{ name }} {{ class }}" width="{{ icon_size }}" height="{{ icon_size }}" aria-hidden="true" focusable="false"> <use href="#icon-{{ name }}"></use></svg>Then define your icons in your layout:
{# In theme.liquid or a snippet included in the layout #}<svg style="display: none;"> <symbol id="icon-cart" viewBox="0 0 24 24"> <path d="M9 22c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm10 0c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1z"/> {# ... rest of path #} </symbol> <symbol id="icon-search" viewBox="0 0 24 24"> {# ... #} </symbol></svg>Usage:
{% render 'component-icon', name: 'cart' %}{% render 'component-icon', name: 'search', size: 20 %}{% render 'component-icon', name: 'menu', size: 32, class: 'header-icon' %}Snippets vs Sections
When should you use a snippet vs a section?
| Use Snippets When | Use Sections When |
|---|---|
| No customization needed in theme editor | Merchants need to customize via theme editor |
| Reused inside sections | Stand-alone, customizable content blocks |
| Simple, presentational components | Complex layouts with settings and blocks |
| Need to pass data programmatically | Content comes from theme editor settings |
Example decision:
- Product card in a grid: Snippet (data comes from loop)
- Featured product on homepage: Section (merchant picks product in editor)
- Icon component: Snippet (no customization needed)
- Hero banner: Section (merchant customizes text, images, buttons)
Best Practices
1. Document Your Snippets
Add a comment block at the top explaining parameters:
{# Product Card Component
Renders a product card with image, title, and price.
@param {product} product - The product object (required) @param {boolean} show_vendor - Display vendor name (default: false) @param {string} image_size - 'small' | 'medium' | 'large' (default: 'medium') @param {boolean} lazy_load - Use lazy loading for image (default: true)#}2. Use Default Values
Handle missing parameters gracefully:
{%- assign image_width = image_size | default: 'medium' -%}{%- assign show_vendor = show_vendor | default: false -%}3. Keep Snippets Focused
One snippet, one purpose. If a snippet does too many things, split it up.
4. Avoid Deep Nesting
Snippets can render other snippets, but avoid going too deep. Two or three levels is usually fine; more than that becomes hard to follow.
{# OK: snippet renders another snippet #}{% render 'product-card', product: product %} {# product-card renders 'product-price' #}
{# Avoid: too many levels #}{% render 'page-wrapper' %} {# renders 'content-section' #} {# renders 'product-grid' #} {# renders 'product-card' #} {# renders 'product-price' #} {# renders 'money-format' #}Practice Exercise
Create a social sharing snippet that:
- Accepts a URL and title to share
- Renders buttons for Twitter, Facebook, and Pinterest
- Uses the icon component for each platform
- Has an optional
show_labelsparameter
{# snippets/social-share.liquid #}{# Social sharing buttons
@param {string} url - URL to share (required) @param {string} title - Content title (required) @param {boolean} show_labels - Show platform names (default: false)#}
{%- assign share_url = url | url_encode -%}{%- assign share_title = title | url_encode -%}
<div class="social-share"> <span class="social-share__label">Share:</span>
<a href="https://twitter.com/intent/tweet?url={{ share_url }}&text={{ share_title }}" class="social-share__link social-share__link--twitter" target="_blank" rel="noopener noreferrer" aria-label="Share on Twitter"> {% render 'component-icon', name: 'twitter', size: 20 %} {% if show_labels %}<span>Twitter</span>{% endif %} </a>
<a href="https://www.facebook.com/sharer/sharer.php?u={{ share_url }}" class="social-share__link social-share__link--facebook" target="_blank" rel="noopener noreferrer" aria-label="Share on Facebook"> {% render 'component-icon', name: 'facebook', size: 20 %} {% if show_labels %}<span>Facebook</span>{% endif %} </a>
<a href="https://pinterest.com/pin/create/button/?url={{ share_url }}&description={{ share_title }}" class="social-share__link social-share__link--pinterest" target="_blank" rel="noopener noreferrer" aria-label="Share on Pinterest"> {% render 'component-icon', name: 'pinterest', size: 20 %} {% if show_labels %}<span>Pinterest</span>{% endif %} </a></div>Usage:
{% render 'social-share', url: product.url, title: product.title %}
{% render 'social-share', url: article.url, title: article.title, show_labels: true %}Key Takeaways
- Snippets are reusable code in the
snippets/directory renderincludes snippets with isolated scope- Pass variables explicitly since snippets can’t access parent scope
forshorthand iterates over arrays efficientlywithshorthand passes a single object with a specific name- Document snippets with comments explaining parameters
- Use snippets for components, sections for customizable blocks
What’s Next?
You now know how to build reusable components. The final lesson in this module covers Common Pitfalls to help you avoid the mistakes that trip up most Liquid developers.
Finished this lesson?
Mark it complete to track your progress.
Discussion
Loading comments...