Files
gh-sarojpunde-shopify-dev-t…/skills/shopify-section-patterns/SKILL.md
2025-11-30 08:54:05 +08:00

11 KiB

name, description
name description
Shopify Section Patterns Best practices for creating Shopify 2.0 sections with inline CSS and JavaScript

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

{%- 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
  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
  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

<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 %}
  (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

<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
  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

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

4. Mobile-First Responsive

/* 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

<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

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

Dynamic Grid Columns

{% 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 %}
  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.