Reusable Liquid snippet patterns with proper documentation and parameter handling
Install
npx skillscat add sarojpunde/shopify-dev-toolkit-claude-plugins/shopify-snippet-library Install via the SkillsCat registry.
About this skill
This skill provides a library of reusable Liquid snippet patterns for Shopify theme development, including standardized documentation and parameter handling conventions. It addresses the need for consistent, maintainable code by offering pre-built components like product cards with configurable options. Developers should use it when building Shopify themes to reduce duplication and ensure proper snippet structure.
SKILL.md
Shopify Snippet Library
Snippet Documentation Format
All snippets should include documentation using Liquid comments:
{% comment %}
Snippet: [Name]
Description: [What this snippet does]
Parameters:
- param_name {type} - Description [required/optional]
Usage:
{% render 'snippet-name', param_name: value %}
Example:
{% render 'product-card', product: product, show_vendor: true %}
{% endcomment %}Common Snippet Patterns
1. Product Card
{% comment %}
Snippet: Product Card
Description: Displays a product with image, title, vendor, and price
Parameters:
- product {product} - The product object [required]
- show_vendor {boolean} - Show product vendor (default: false) [optional]
- image_size {number} - Image width in pixels (default: 400) [optional]
- class {string} - Additional CSS classes [optional]
Usage:
{% render 'product-card', product: product, show_vendor: true, image_size: 600 %}
{% endcomment %}
{%- liquid
assign show_vendor = show_vendor | default: false
assign image_size = image_size | default: 400
-%}
{%- if product == blank -%}
<div class="product-card product-card--empty {{ class }}">
<p>Product not available</p>
</div>
{%- else -%}
<article class="product-card {{ class }}">
<a href="{{ product.url }}" class="product-card__link">
{%- if product.featured_image -%}
<div class="product-card__image">
<img
src="{{ product.featured_image | image_url: width: image_size }}"
alt="{{ product.featured_image.alt | escape }}"
loading="lazy"
width="{{ image_size }}"
height="{{ image_size | divided_by: product.featured_image.aspect_ratio | ceil }}"
>
</div>
{%- endif -%}
<div class="product-card__info">
{%- if show_vendor and product.vendor != blank -%}
<p class="product-card__vendor">{{ product.vendor | escape }}</p>
{%- endif -%}
<h3 class="product-card__title">{{ product.title | escape }}</h3>
<div class="product-card__price">
{%- if product.compare_at_price > product.price -%}
<span class="product-card__price--sale">{{ product.price | money }}</span>
<span class="product-card__price--compare">{{ product.compare_at_price | money }}</span>
{%- else -%}
<span>{{ product.price | money }}</span>
{%- endif -%}
</div>
</div>
</a>
</article>
{%- endif -%}2. Button / Link
{% comment %}
Snippet: Button
Description: Renders a customizable button or link
Parameters:
- text {string} - Button text [required]
- url {string} - Button URL (makes it a link) [optional]
- style {string} - Button style: 'primary', 'secondary', 'outline' (default: 'primary') [optional]
- size {string} - Button size: 'small', 'medium', 'large' (default: 'medium') [optional]
- class {string} - Additional CSS classes [optional]
Usage:
{% render 'button', text: 'Add to Cart', style: 'primary', size: 'large' %}
{% render 'button', text: 'Learn More', url: '/pages/about', style: 'outline' %}
{% endcomment %}
{%- liquid
assign style = style | default: 'primary'
assign size = size | default: 'medium'
assign btn_class = 'btn btn--' | append: style | append: ' btn--' | append: size
if class
assign btn_class = btn_class | append: ' ' | append: class
endif
-%}
{%- if url != blank -%}
<a href="{{ url }}" class="{{ btn_class }}">
{{ text | escape }}
</a>
{%- else -%}
<button type="button" class="{{ btn_class }}">
{{ text | escape }}
</button>
{%- endif -%}3. Icon (SVG)
{% comment %}
Snippet: Icon
Description: Renders an SVG icon
Parameters:
- name {string} - Icon name (e.g., 'cart', 'heart', 'search') [required]
- size {number} - Icon size in pixels (default: 24) [optional]
- class {string} - Additional CSS classes [optional]
Usage:
{% render 'icon', name: 'cart', size: 20, class: 'icon-primary' %}
{% endcomment %}
{%- liquid
assign size = size | default: 24
assign icon_class = 'icon icon-' | append: name
if class
assign icon_class = icon_class | append: ' ' | append: class
endif
-%}
<svg class="{{ icon_class }}" width="{{ size }}" height="{{ size }}" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
{%- case name -%}
{%- when 'cart' -%}
<path d="M9 2L8 4H4C2.9 4 2 4.9 2 6V18C2 19.1 2.9 20 4 20H20C21.1 20 22 19.1 22 18V6C22 4.9 21.1 4 20 4H16L15 2H9Z" stroke="currentColor" stroke-width="2"/>
{%- when 'heart' -%}
<path d="M20.84 4.61C19.91 3.68 18.69 3.17 17.42 3.17C16.15 3.17 14.93 3.68 14 4.61L12 6.61L10 4.61C9.07 3.68 7.85 3.17 6.58 3.17C5.31 3.17 4.09 3.68 3.16 4.61C1.22 6.55 1.22 9.69 3.16 11.63L12 20.47L20.84 11.63C22.78 9.69 22.78 6.55 20.84 4.61Z" stroke="currentColor" stroke-width="2"/>
{%- when 'search' -%}
<circle cx="11" cy="11" r="8" stroke="currentColor" stroke-width="2"/>
<path d="M21 21L16.65 16.65" stroke="currentColor" stroke-width="2"/>
{%- when 'close' -%}
<path d="M18 6L6 18M6 6L18 18" stroke="currentColor" stroke-width="2"/>
{%- else -%}
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
{%- endcase -%}
</svg>4. Form Input
{% comment %}
Snippet: Form Input
Description: Renders a form input field with label
Parameters:
- type {string} - Input type (text, email, tel, number, etc.) [required]
- name {string} - Input name attribute [required]
- label {string} - Input label [required]
- required {boolean} - Make field required (default: false) [optional]
- placeholder {string} - Placeholder text [optional]
- value {string} - Default value [optional]
- class {string} - Additional CSS classes [optional]
Usage:
{% render 'form-input', type: 'email', name: 'email', label: 'Email Address', required: true %}
{% endcomment %}
{%- liquid
assign required = required | default: false
assign input_id = 'input-' | append: name
-%}
<div class="form-field {{ class }}">
<label for="{{ input_id }}" class="form-field__label">
{{ label | escape }}
{%- if required -%}
<span class="form-field__required" aria-label="required">*</span>
{%- endif -%}
</label>
<input
type="{{ type }}"
id="{{ input_id }}"
name="{{ name }}"
class="form-field__input"
{%- if placeholder -%}placeholder="{{ placeholder | escape }}"{%- endif -%}
{%- if value -%}value="{{ value | escape }}"{%- endif -%}
{%- if required -%}required{%- endif -%}
{%- if type == 'email' -%}autocomplete="email"{%- endif -%}
>
</div>5. Image with Fallback
{% comment %}
Snippet: Responsive Image
Description: Renders a responsive image with fallback
Parameters:
- image {image} - The image object [required]
- width {number} - Image width (default: 800) [optional]
- height {number} - Image height [optional]
- alt {string} - Alt text (uses image.alt if not provided) [optional]
- class {string} - Additional CSS classes [optional]
- loading {string} - Loading attribute: 'lazy' or 'eager' (default: 'lazy') [optional]
Usage:
{% render 'image', image: product.featured_image, width: 600, alt: product.title %}
{% endcomment %}
{%- liquid
assign width = width | default: 800
assign loading = loading | default: 'lazy'
assign alt_text = alt | default: image.alt
-%}
{%- if image != blank -%}
{%- if height -%}
<img
src="{{ image | image_url: width: width, height: height }}"
alt="{{ alt_text | escape }}"
width="{{ width }}"
height="{{ height }}"
loading="{{ loading }}"
class="{{ class }}"
>
{%- else -%}
<img
srcset="
{{ image | image_url: width: 400 }} 400w,
{{ image | image_url: width: 800 }} 800w,
{{ image | image_url: width: 1200 }} 1200w
"
sizes="(min-width: 1024px) 33vw, (min-width: 768px) 50vw, 100vw"
src="{{ image | image_url: width: width }}"
alt="{{ alt_text | escape }}"
width="{{ width }}"
height="{{ width | divided_by: image.aspect_ratio | ceil }}"
loading="{{ loading }}"
class="{{ class }}"
>
{%- endif -%}
{%- else -%}
<div class="image-placeholder {{ class }}" role="img" aria-label="No image available"></div>
{%- endif -%}6. Price Display
{% comment %}
Snippet: Price
Description: Displays product price with sale indication
Parameters:
- product {product} - The product object [required]
- show_compare {boolean} - Show compare at price (default: true) [optional]
- class {string} - Additional CSS classes [optional]
Usage:
{% render 'price', product: product %}
{% render 'price', product: product, show_compare: false %}
{% endcomment %}
{%- 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 {{ class }}{% if on_sale %} price--on-sale{% endif %}">
{%- if on_sale -%}
<span class="price__sale">{{ product.price | money }}</span>
{%- if show_compare -%}
<span class="price__compare">{{ product.compare_at_price | money }}</span>
{%- endif -%}
{%- else -%}
<span class="price__regular">{{ product.price | money }}</span>
{%- endif -%}
</div>7. Badge / Tag
{% comment %}
Snippet: Badge
Description: Displays a badge or tag
Parameters:
- text {string} - Badge text [required]
- style {string} - Badge style: 'sale', 'new', 'featured', 'default' (default: 'default') [optional]
- class {string} - Additional CSS classes [optional]
Usage:
{% render 'badge', text: 'Sale', style: 'sale' %}
{% render 'badge', text: 'New Arrival', style: 'new' %}
{% endcomment %}
{%- liquid
assign style = style | default: 'default'
assign badge_class = 'badge badge--' | append: style
if class
assign badge_class = badge_class | append: ' ' | append: class
endif
-%}
{%- if text != blank -%}
<span class="{{ badge_class }}">
{{ text | escape }}
</span>
{%- endif -%}8. Loading Spinner
{% comment %}
Snippet: Loading Spinner
Description: Displays a loading spinner
Parameters:
- size {string} - Spinner size: 'small', 'medium', 'large' (default: 'medium') [optional]
- class {string} - Additional CSS classes [optional]
Usage:
{% render 'loading-spinner', size: 'large' %}
{% endcomment %}
{%- liquid
assign size = size | default: 'medium'
assign spinner_class = 'spinner spinner--' | append: size
if class
assign spinner_class = spinner_class | append: ' ' | append: class
endif
-%}
<div class="{{ spinner_class }}" role="status" aria-label="Loading">
<svg class="spinner__svg" viewBox="0 0 50 50">
<circle class="spinner__circle" cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
</svg>
<span class="sr-only">Loading...</span>
</div>Best Practices
1. Always Document Parameters
{% comment %}
Parameters:
- param {type} - Description [required/optional]
{% endcomment %}2. Provide Defaults
{%- liquid
assign show_vendor = show_vendor | default: false
assign image_size = image_size | default: 400
-%}3. Validate Required Parameters
{%- if product == blank -%}
<p class="error">Product parameter is required</p>
{%- return -%}
{%- endif -%}4. Handle Empty States
{%- if image != blank -%}
<!-- Image markup -->
{%- else -%}
<div class="placeholder">No image available</div>
{%- endif -%}5. Escape User-Provided Content
<h3>{{ product.title | escape }}</h3>
<p>{{ customer.name | escape }}</p>6. Use Semantic HTML
<article> {%- # For products -%}
<nav> {%- # For navigation -%}
<aside> {%- # For sidebars -%}
<section> {%- # For content sections -%}7. Include Accessibility Attributes
<button aria-label="Add {{ product.title | escape }} to cart">
Add to Cart
</button>
<img src="..." alt="{{ image.alt | escape }}">
<div role="status" aria-live="polite">
Item added to cart
</div>Create well-documented, reusable snippets that are easy to understand and maintain.