Initial commit
This commit is contained in:
518
skills/shopify-section-patterns/SKILL.md
Normal file
518
skills/shopify-section-patterns/SKILL.md
Normal file
@@ -0,0 +1,518 @@
|
||||
---
|
||||
name: Shopify Section Patterns
|
||||
description: 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
|
||||
|
||||
```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.
|
||||
Reference in New Issue
Block a user