Reusable Patterns with Snippets
Build a library of reusable snippet components that make your sections more maintainable and your development faster.
As you build more sections, you’ll notice the same patterns appearing again and again: product cards, price displays, buttons, icons. Instead of duplicating this code, extract it into snippets. This creates a component library that speeds up development and ensures consistency across your theme.
Sections vs Snippets: When to Use Each
Use Sections When:
- Merchants need to customize via theme editor
- Content appears as a distinct page section
- Different pages need different configurations
- The component has its own blocks
Use Snippets When:
- The same UI pattern appears in multiple sections
- No merchant customization is needed
- You want consistent styling across the theme
- The component receives data programmatically
Common Snippet Patterns
Product Card
The most common snippet in any theme:
{# snippets/product-card.liquid #}{# Product Card Component
@param {product} product - Product object (required) @param {boolean} show_vendor - Display vendor name (default: false) @param {boolean} show_rating - Display rating stars (default: false) @param {boolean} lazy_load - Use lazy loading (default: true) @param {string} image_ratio - 'portrait' | 'square' | 'landscape' (default: 'portrait')#}
{%- liquid assign show_vendor = show_vendor | default: false assign show_rating = show_rating | default: false assign lazy_load = lazy_load | default: true assign image_ratio = image_ratio | default: 'portrait' assign loading = 'lazy' if lazy_load == false assign loading = 'eager' endif-%}
<article class="product-card"> <a href="{{ product.url }}" class="product-card__link"> <div class="product-card__media product-card__media--{{ image_ratio }}"> {%- if product.featured_image -%} {{ product.featured_image | image_url: width: 600 | image_tag: loading: loading, class: 'product-card__image', alt: product.featured_image.alt | escape }} {%- else -%} {{ 'product-1' | placeholder_svg_tag: 'product-card__placeholder' }} {%- endif -%}
{%- if product.compare_at_price > product.price -%} <span class="product-card__badge product-card__badge--sale">Sale</span> {%- elsif product.available == false -%} <span class="product-card__badge product-card__badge--sold-out">Sold Out</span> {%- endif -%} </div>
<div class="product-card__info"> {%- if show_vendor and product.vendor != blank -%} <p class="product-card__vendor">{{ product.vendor }}</p> {%- endif -%}
<h3 class="product-card__title">{{ product.title }}</h3>
{%- if show_rating -%} {% render 'rating-stars', rating: product.metafields.reviews.rating %} {%- endif -%}
{% render 'price', product: product %} </div> </a></article>Usage:
{% render 'product-card', product: product %}
{% render 'product-card', product: product, show_vendor: true, image_ratio: 'square'%}
{% render 'product-card' for collection.products as product %}Price Display
{# snippets/price.liquid #}{# Price Display Component
@param {product} product - Product object (required) @param {boolean} show_compare - Show compare-at price (default: true)#}
{%- liquid assign show_compare = show_compare | 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 %}"> {%- if on_sale and show_compare -%} <span class="price__compare"> <span class="visually-hidden">Regular price</span> <s>{{ product.compare_at_price | money }}</s> </span> {%- endif -%}
<span class="price__current"> {%- if on_sale -%} <span class="visually-hidden">Sale price</span> {%- endif -%} {{ product.price | money }} </span>
{%- if product.price_varies -%} <span class="price__suffix">from</span> {%- endif -%}</div>Button Component
{# snippets/button.liquid #}{# Button Component
@param {string} label - Button text (required) @param {string} url - Link URL (renders as <a> if provided) @param {string} style - 'primary' | 'secondary' | 'outline' (default: 'primary') @param {string} size - 'small' | 'medium' | 'large' (default: 'medium') @param {boolean} full_width - Full width button (default: false) @param {string} type - 'button' | 'submit' (default: 'button', for non-links) @param {boolean} disabled - Disabled state (default: false) @param {string} icon - Icon name to display (optional)#}
{%- liquid assign style = style | default: 'primary' assign size = size | default: 'medium' assign full_width = full_width | default: false assign type = type | default: 'button' assign disabled = disabled | default: false-%}
{%- capture classes -%} button button--{{ style }} button--{{ size }} {%- if full_width %} button--full-width{% endif -%}{%- endcapture -%}
{%- if url != blank -%} <a href="{{ url }}" class="{{ classes | strip_newlines | strip }}"> {%- if icon -%} {% render 'icon', name: icon %} {%- endif -%} {{ label }} </a>{%- else -%} <button type="{{ type }}" class="{{ classes | strip_newlines | strip }}" {% if disabled %}disabled{% endif %} > {%- if icon -%} {% render 'icon', name: icon %} {%- endif -%} {{ label }} </button>{%- endif -%}Usage:
{% render 'button', label: 'Shop Now', url: '/collections/all' %}
{% render 'button', label: 'Add to Cart', type: 'submit', style: 'primary' %}
{% render 'button', label: 'Learn More', url: page.url, style: 'outline', icon: 'arrow-right' %}Icon Component
{# snippets/icon.liquid #}{# SVG Icon Component
@param {string} name - Icon name (required) @param {number} size - Size in pixels (default: 24) @param {string} class - Additional CSS classes#}
{%- liquid 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>Define icons in your layout:
{# In theme.liquid or a dedicated snippet #}{% render 'icon-symbols' %}{# snippets/icon-symbols.liquid #}<svg xmlns="http://www.w3.org/2000/svg" style="display: none;"> <symbol id="icon-cart" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <circle cx="9" cy="21" r="1"/> <circle cx="20" cy="21" r="1"/> <path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/> </symbol>
<symbol id="icon-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <circle cx="11" cy="11" r="8"/> <path d="m21 21-4.35-4.35"/> </symbol>
<symbol id="icon-menu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <line x1="3" y1="12" x2="21" y2="12"/> <line x1="3" y1="6" x2="21" y2="6"/> <line x1="3" y1="18" x2="21" y2="18"/> </symbol>
<symbol id="icon-close" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <line x1="18" y1="6" x2="6" y2="18"/> <line x1="6" y1="6" x2="18" y2="18"/> </symbol>
<symbol id="icon-arrow-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M5 12h14M12 5l7 7-7 7"/> </symbol></svg>Image Component
{# snippets/responsive-image.liquid #}{# Responsive Image Component
@param {image} image - Image object (required) @param {string} alt - Alt text (falls back to image.alt) @param {string} sizes - Sizes attribute for responsive images @param {boolean} lazy - Lazy load (default: true) @param {string} class - Additional CSS classes#}
{%- liquid assign alt_text = alt | default: image.alt | escape assign lazy = lazy | default: true assign loading = 'lazy' if lazy == false assign loading = 'eager' endif assign default_sizes = '(min-width: 1200px) 1200px, 100vw' assign sizes_attr = sizes | default: default_sizes-%}
{%- if image != blank -%} <img src="{{ image | image_url: width: 800 }}" srcset=" {{ image | image_url: width: 400 }} 400w, {{ image | image_url: width: 600 }} 600w, {{ image | image_url: width: 800 }} 800w, {{ image | image_url: width: 1000 }} 1000w, {{ image | image_url: width: 1200 }} 1200w " sizes="{{ sizes_attr }}" alt="{{ alt_text }}" loading="{{ loading }}" width="{{ image.width }}" height="{{ image.height }}" {% if class %}class="{{ class }}"{% endif %} >{%- endif -%}Social Share Links
{# snippets/social-share.liquid #}{# Social Sharing Links
@param {string} url - URL to share (required) @param {string} title - Share title (required) @param {string} image - Image URL for Pinterest (optional)#}
{%- liquid assign encoded_url = url | url_encode assign encoded_title = title | url_encode-%}
<div class="social-share"> <span class="social-share__label">Share:</span>
<a href="https://www.facebook.com/sharer/sharer.php?u={{ encoded_url }}" class="social-share__link" target="_blank" rel="noopener noreferrer" aria-label="Share on Facebook" > {% render 'icon', name: 'facebook', size: 20 %} </a>
<a href="https://twitter.com/intent/tweet?url={{ encoded_url }}&text={{ encoded_title }}" class="social-share__link" target="_blank" rel="noopener noreferrer" aria-label="Share on Twitter" > {% render 'icon', name: 'twitter', size: 20 %} </a>
<a href="https://pinterest.com/pin/create/button/?url={{ encoded_url }}&description={{ encoded_title }}{% if image %}&media={{ image | url_encode }}{% endif %}" class="social-share__link" target="_blank" rel="noopener noreferrer" aria-label="Share on Pinterest" > {% render 'icon', name: 'pinterest', size: 20 %} </a>
<a href="mailto:?subject={{ encoded_title }}&body={{ encoded_url }}" class="social-share__link" aria-label="Share via Email" > {% render 'icon', name: 'mail', size: 20 %} </a></div>Organizing Your Snippets
Since Shopify’s snippets/ folder is flat, use prefixes to organize:
snippets/├── card-product.liquid├── card-collection.liquid├── card-article.liquid├── component-button.liquid├── component-icon.liquid├── component-price.liquid├── component-quantity.liquid├── form-newsletter.liquid├── form-contact.liquid├── icon-symbols.liquid├── layout-pagination.liquid├── layout-breadcrumbs.liquid└── util-responsive-image.liquidCommon prefixes:
card-: Card componentscomponent-: UI componentsform-: Form elementsicon-: Icon-relatedlayout-: Layout helpersutil-: Utility snippets
Documenting Snippets
Always document parameters at the top of each snippet:
{# Component Name Brief description of what it does.
@param {type} name - Description (required/optional, default: value) @param {product} product - Product object (required) @param {boolean} show_vendor - Show vendor name (default: false) @param {string} size - 'small' | 'medium' | 'large' (default: 'medium')
@example {% render 'component-name', product: product, show_vendor: true %}#}Using Snippets in Sections
A section using multiple snippets:
{# sections/featured-collection.liquid #}
<section class="featured-collection"> <div class="container"> <h2>{{ section.settings.heading }}</h2>
<div class="product-grid"> {%- for product in section.settings.collection.products limit: section.settings.limit -%} {% render 'card-product', product: product, show_vendor: section.settings.show_vendor, show_rating: section.settings.show_rating, image_ratio: section.settings.image_ratio, lazy_load: forloop.index > 4 %} {%- endfor -%} </div>
{%- if section.settings.show_view_all -%} {% render 'component-button', label: section.settings.view_all_text, url: section.settings.collection.url, style: 'secondary' %} {%- endif -%} </div></section>Avoiding Over-Abstraction
Don’t create snippets for everything. A good rule of thumb:
Create a snippet when:
- The pattern appears 3+ times
- It’s complex enough to benefit from isolation
- It has clear inputs and outputs
Don’t create a snippet when:
- It’s used once
- It’s just a few lines
- It makes the code harder to follow
{# Too granular - just use inline code #}{% render 'heading', text: section.settings.heading, tag: 'h2' %}
{# Just write it #}<h2>{{ section.settings.heading }}</h2>Practice Exercise
Create a card-collection.liquid snippet that displays:
- Collection image (with placeholder fallback)
- Collection title
- Product count
- Optional “Shop now” button
{# snippets/card-collection.liquid #}{# Collection Card Component
@param {collection} collection - Collection object (required) @param {boolean} show_count - Show product count (default: true) @param {boolean} show_button - Show CTA button (default: false) @param {string} button_text - Button text (default: 'Shop Now')#}
{%- liquid assign show_count = show_count | default: true assign show_button = show_button | default: false assign button_text = button_text | default: 'Shop Now'-%}
<article class="collection-card"> <a href="{{ collection.url }}" class="collection-card__link"> <div class="collection-card__media"> {%- if collection.image -%} {{ collection.image | image_url: width: 600 | image_tag: loading: 'lazy', class: 'collection-card__image' }} {%- else -%} {{ 'collection-1' | placeholder_svg_tag: 'collection-card__placeholder' }} {%- endif -%} </div>
<div class="collection-card__info"> <h3 class="collection-card__title">{{ collection.title }}</h3>
{%- if show_count -%} <p class="collection-card__count"> {{ collection.all_products_count }} {{ collection.all_products_count | pluralize: 'product', 'products' }} </p> {%- endif -%} </div> </a>
{%- if show_button -%} {% render 'component-button', label: button_text, url: collection.url, style: 'secondary', size: 'small' %} {%- endif -%}</article>Key Takeaways
- Extract repeated patterns into snippets
- Document parameters at the top of each snippet
- Provide sensible defaults for optional parameters
- Use consistent prefixes to organize snippets
- Snippets call other snippets to build complex UIs
- Don’t over-abstract simple code
- Snippets have isolated scope with
render
What’s Next?
Congratulations! You’ve completed Module 5: Sections, Blocks, and Schema Design. You now know how to build powerful, customizable section components with well-organized settings and reusable snippet patterns.
In the next module, you’ll learn about Assets: CSS, JS, and Front-End Architecture for styling and adding interactivity to your theme.
Finished this lesson?
Mark it complete to track your progress.
Discussion
Loading comments...