Agent skill

Shopify Section Patterns

Best practices for creating Shopify 2.0 sections with inline CSS and JavaScript

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/shopify-section-patterns

SKILL.md

Shopify Section Patterns

Section Anatomy

A complete Shopify section consists of:

  1. Liquid markup - HTML structure with dynamic content
  2. Inline CSS - {% stylesheet %} tag (optional)
  3. Inline JavaScript - {% javascript %} tag (optional)
  4. Schema - {% schema %} JSON configuration

Complete Section Template

liquid
{%- comment -%}
  Section: [Name]
  Description: [Brief description]
  Usage: Add via Theme Editor
{%- endcomment -%}

{%- liquid
  assign heading = section.settings.heading | default: 'Default Heading'
  assign bg_color = section.settings.bg_color
  assign text_color = section.settings.text_color
-%}

<section
  id="section-{{ section.id }}"
  class="my-section"
  data-section-id="{{ section.id }}"
>
  <div class="container">
    {%- if heading != blank -%}
      <h2>{{ heading | escape }}</h2>
    {%- endif -%}

    {%- comment -%} Section content {%- endcomment -%}
  </div>
</section>

{% stylesheet %}
  #section-{{ section.id }} {
    padding: {{ section.settings.padding_top }}px 0 {{ section.settings.padding_bottom }}px;
    background-color: {{ bg_color }};
    color: {{ text_color }};
  }

  .container {
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 20px;
  }

  @media (min-width: 768px) {
    /* Tablet styles */
  }

  @media (min-width: 1024px) {
    /* Desktop styles */
  }
{% endstylesheet %}

{% javascript %}
  (function() {
    'use strict';

    function initSection(sectionId) {
      const section = document.querySelector('[data-section-id="' + sectionId + '"]');
      if (!section) return;

      // Your JavaScript here
    }

    // Initialize on page load
    document.addEventListener('DOMContentLoaded', function() {
      initSection('{{ section.id }}');
    });

    // Re-initialize when section reloads in theme editor
    document.addEventListener('shopify:section:load', function(event) {
      if (event.detail.sectionId === '{{ section.id }}') {
        initSection('{{ section.id }}');
      }
    });
  })();
{% endjavascript %}

{% schema %}
{
  "name": "Section Name",
  "tag": "section",
  "class": "section-wrapper",
  "settings": [
    {
      "type": "text",
      "id": "heading",
      "label": "Heading",
      "default": "Default Heading"
    },
    {
      "type": "color",
      "id": "bg_color",
      "label": "Background Color",
      "default": "#ffffff"
    },
    {
      "type": "color",
      "id": "text_color",
      "label": "Text Color",
      "default": "#000000"
    },
    {
      "type": "range",
      "id": "padding_top",
      "label": "Top Spacing",
      "min": 0,
      "max": 100,
      "step": 4,
      "unit": "px",
      "default": 40
    },
    {
      "type": "range",
      "id": "padding_bottom",
      "label": "Bottom Spacing",
      "min": 0,
      "max": 100,
      "step": 4,
      "unit": "px",
      "default": 40
    }
  ],
  "presets": [
    {
      "name": "Section Name"
    }
  ]
}
{% endschema %}

When to Use Inline CSS/JS

Use Inline {% stylesheet %} When:

  • CSS needs Liquid variables: {{ section.settings.color }}
  • Dynamic styles based on merchant settings
  • Section-specific styles that don't need external file

Use Inline {% javascript %} When:

  • JS needs Liquid settings: {{ section.settings.enable_feature }}
  • Section-specific functionality
  • Need access to {{ section.id }} or other Liquid values

Skip Inline Tags When:

  • Styles/scripts are static and reusable
  • Better to use theme.css or theme.js
  • No Liquid variables needed

Common Section Patterns

1. Product Grid Section

liquid
{%- liquid
  assign collection = section.settings.collection
  assign products_count = section.settings.products_count
  assign columns = section.settings.columns_desktop
-%}

<div class="product-grid">
  {%- for product in collection.products limit: products_count -%}
    <div class="product-item">
      <a href="{{ product.url }}">
        {%- if product.featured_image -%}
          <img
            src="{{ product.featured_image | image_url: width: 400 }}"
            alt="{{ product.featured_image.alt | escape }}"
            loading="lazy"
          >
        {%- endif -%}

        <h3>{{ product.title | escape }}</h3>
        <p>{{ product.price | money }}</p>
      </a>
    </div>
  {%- endfor -%}
</div>

{% stylesheet %}
  .product-grid {
    display: grid;
    gap: 1.5rem;
    grid-template-columns: 1fr;
  }

  @media (min-width: 768px) {
    .product-grid {
      grid-template-columns: repeat({{ columns }}, 1fr);
    }
  }
{% endstylesheet %}

2. Hero Banner Section

liquid
{%- liquid
  assign image = section.settings.image
  assign heading = section.settings.heading
  assign text = section.settings.text
  assign button_label = section.settings.button_label
  assign button_link = section.settings.button_link
-%}

<div class="hero-banner">
  {%- if image -%}
    <div class="hero-banner__image">
      <img
        src="{{ image | image_url: width: 1600 }}"
        alt="{{ image.alt | escape }}"
      >
    </div>
  {%- endif -%}

  <div class="hero-banner__content">
    {%- if heading != blank -%}
      <h1>{{ heading | escape }}</h1>
    {%- endif -%}

    {%- if text != blank -%}
      <p>{{ text }}</p>
    {%- endif -%}

    {%- if button_label != blank and button_link != blank -%}
      <a href="{{ button_link }}" class="button">
        {{ button_label | escape }}
      </a>
    {%- endif -%}
  </div>
</div>

{% stylesheet %}
  .hero-banner {
    position: relative;
    min-height: 500px;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
  }

  .hero-banner__image {
    position: absolute;
    inset: 0;
    z-index: -1;
  }

  .hero-banner__image img {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  .hero-banner__content {
    padding: 2rem;
    background: rgba(255, 255, 255, 0.9);
    border-radius: 8px;
  }

  .button {
    display: inline-block;
    padding: 1rem 2rem;
    background: #000;
    color: #fff;
    text-decoration: none;
    border-radius: 4px;
    margin-top: 1rem;
  }
{% endstylesheet %}

3. Testimonials Section with Blocks

liquid
<div class="testimonials">
  {%- for block in section.blocks -%}
    <div class="testimonial" {{ block.shopify_attributes }}>
      {%- if block.settings.quote != blank -%}
        <blockquote>
          {{ block.settings.quote }}
        </blockquote>
      {%- endif -%}

      {%- if block.settings.author != blank -%}
        <cite>{{ block.settings.author | escape }}</cite>
      {%- endif -%}
    </div>
  {%- endfor -%}
</div>

{% schema %}
{
  "name": "Testimonials",
  "blocks": [
    {
      "type": "testimonial",
      "name": "Testimonial",
      "settings": [
        {
          "type": "textarea",
          "id": "quote",
          "label": "Quote"
        },
        {
          "type": "text",
          "id": "author",
          "label": "Author"
        }
      ]
    }
  ],
  "presets": [
    {
      "name": "Testimonials",
      "blocks": [
        {
          "type": "testimonial"
        }
      ]
    }
  ]
}
{% endschema %}

Shopify Section Events

Handle theme editor events:

javascript
{% javascript %}
  (function() {
    'use strict';

    function initSection(sectionId) {
      const section = document.querySelector('[data-section-id="' + sectionId + '"]');
      if (!section) return;

      console.log('Section initialized:', sectionId);
    }

    function cleanupSection(sectionId) {
      // Clean up event listeners, timers, etc.
      console.log('Section cleaned up:', sectionId);
    }

    // Load event - section added or page loaded
    document.addEventListener('shopify:section:load', function(event) {
      initSection(event.detail.sectionId);
    });

    // Unload event - section removed
    document.addEventListener('shopify:section:unload', function(event) {
      cleanupSection(event.detail.sectionId);
    });

    // Select event - section selected in theme editor
    document.addEventListener('shopify:section:select', function(event) {
      console.log('Section selected:', event.detail.sectionId);
    });

    // Deselect event - section deselected in theme editor
    document.addEventListener('shopify:section:deselect', function(event) {
      console.log('Section deselected:', event.detail.sectionId);
    });

    // Block select event - block selected in theme editor
    document.addEventListener('shopify:block:select', function(event) {
      console.log('Block selected:', event.detail.blockId);
    });

    // Block deselect event - block deselected in theme editor
    document.addEventListener('shopify:block:deselect', function(event) {
      console.log('Block deselected:', event.detail.blockId);
    });

    // Initialize on page load
    document.addEventListener('DOMContentLoaded', function() {
      initSection('{{ section.id }}');
    });
  })();
{% endjavascript %}

Best Practices

1. Use Section ID for Unique Styling

liquid
<div id="section-{{ section.id }}" class="my-section">
  <!-- Content -->
</div>

{% stylesheet %}
  #section-{{ section.id }} {
    /* Section-specific styles using Liquid variables */
    background: {{ section.settings.bg_color }};
  }

  .my-section {
    /* General styles without Liquid variables */
    padding: 2rem 0;
  }
{% endstylesheet %}

2. Provide Sensible Defaults

liquid
{%- liquid
  assign heading = section.settings.heading | default: 'Default Heading'
  assign columns = section.settings.columns | default: 3
  assign show_prices = section.settings.show_prices | default: true
-%}

3. Handle Empty States

liquid
{%- if collection.products.size > 0 -%}
  <!-- Show products -->
{%- else -%}
  <p>No products available in this collection.</p>
{%- endif -%}

4. Mobile-First Responsive

css
/* Mobile first - base styles */
.grid {
  grid-template-columns: 1fr;
}

/* Tablet and up */
@media (min-width: 768px) {
  .grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* Desktop and up */
@media (min-width: 1024px) {
  .grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

5. Use data- Attributes for JavaScript

liquid
<div
  class="slider"
  data-autoplay="{{ section.settings.autoplay }}"
  data-speed="{{ section.settings.speed }}"
>
  <!-- Slider content -->
</div>

{% javascript %}
  const slider = document.querySelector('.slider');
  const autoplay = slider.dataset.autoplay === 'true';
  const speed = parseInt(slider.dataset.speed, 10);
{% endjavascript %}

Common Patterns

Conditional Classes

liquid
<div class="card{% if product.available %} in-stock{% else %} sold-out{% endif %}">
  <!-- Card content -->
</div>

Dynamic Grid Columns

liquid
{% stylesheet %}
  .product-grid {
    display: grid;
    gap: 1.5rem;
    grid-template-columns: repeat({{ section.settings.columns_mobile }}, 1fr);
  }

  @media (min-width: 1024px) {
    .product-grid {
      grid-template-columns: repeat({{ section.settings.columns_desktop }}, 1fr);
    }
  }
{% endstylesheet %}

Loading States

javascript
{% javascript %}
  function addToCart(button) {
    button.classList.add('loading');
    button.disabled = true;

    // Add to cart logic...

    button.classList.remove('loading');
    button.disabled = false;
  }
{% endjavascript %}

Create sections that are flexible, performant, and easy for merchants to customize through the theme editor.

Didn't find tool you were looking for?

Be as detailed as possible for better results