CursorPool
← 返回首页

Shopify Theme Development

Develop your theme focused on scalability and reusability

cursor.directory·9
规则

Shopify Reusable Snippets

A reference for building scalable, easy-to-use Liquid snippets

# How to Create Reusable Shopify Snippets

A reference for building scalable, easy-to-use Liquid snippets. Use these patterns when creating heading, CTA, announcement, image, and other reusable components.

## When to Create a Snippet

Create a snippet when:
- The same UI appears in 3+ sections or templates
- Logic is complex enough to warrant isolation
- You need consistency (e.g. all CTAs look the same)
- Schema settings would be duplicated across sections

Avoid snippets for:
- One-off layouts
- Content that varies wildly by context
- Simple output (< 5 lines)

## Snippet Documentation Format

Use structured `{% comment %}` blocks at the top of every snippet:

```liquid
{% comment %}
  ===================================================================================
  SNIPPET NAME
  ===================================================================================
  Brief description of what the snippet does and when to use it.

  ********************************************
  Parameters
  ********************************************
  * param_name (Type): Description
  * optional_param (Type, Optional): Description. Default: value

  ********************************************
  Usage
  ********************************************
  {% render 'snippet-name', param: value, optional: value %}

  ********************************************
  Schema (for section settings that pass into snippet)
  ********************************************
  { "type": "text", "id": "param_id", "label": "Label", "visible_if": "{{ section.settings.enable }}" }
{% endcomment %}
```

## Parameter Naming Conventions

### Kebab-case for Multi-word Parameters

Use kebab-case when the parameter name has multiple words:

```liquid
{% render 'section-heading'
  title: ss.heading_title,
  sub_text: ss.heading_sub_text,
  align: ss.heading_align,
  align_mb: ss.heading_align_mb,
  shop_all: ss.heading_shop_all
%}
```

### Action-based Select (vs Multiple Booleans)

For mutually exclusive behaviors, use a single `action` param instead of multiple booleans:

```liquid
{% render 'cta-button'
  text: ss.cta_text,
  link: product.url,
  type: ss.cta_type,
  action: ss.cta_action,
  variant_id: product.selected_or_first_available_variant.id,
  note: ss.cta_note,
  note_icon: ss.cta_note_icon
%}
```

Schema: `action` select with options like `""` (link), `"atc"` (add to cart), `"scroll"` (scroll to product).

### Consistent Parameter Order

Order parameters by: enable flag → content → styling → optional extras. Keeps calls readable.

## Conditional Rendering

### Wrapper Conditionals

Wrap the entire output when the snippet may render nothing:

```liquid
{% if enable %}
  <div class="section-heading">
    <!-- Content -->
  </div>
{% endif %}
```

### Element-level Conditionals

Check blanks before rendering individual elements:

```liquid
{% if title != blank %}
  <h2 class="section-heading__title">{{ title }}</h2>
{% endif %}

{% if text != blank %}
  <div class="section-heading__text">{{ text }}</div>
{% endif %}
```

### Early Skip

For complex snippets, wrap everything in a single top-level conditional:

```liquid
{% if enable and (title != blank or text != blank) %}
  <div class="section-heading">
    <!-- Content -->
  </div>
{% endif %}
```

Liquid has no early return; use a single wrapping conditional instead.

## Variable Assignments

### Default Values

Use `default` filter for fallbacks:

```liquid
{% assign heading_tag = h1 | default: false %}
{% assign heading_tag = heading_tag | replace: 'true', '1' | replace: 'false', '2' | prepend: 'h' %}
```

### Class Concatenation

Build class strings from parameters:

```liquid
{% assign classes = variant | append: ' ' | append: class | strip %}
<div class="cta-button {{ classes }}">
```

### Liquid Block for Multiple Assignments

Use `{% liquid %}` for cleaner multi-step logic:

```liquid
{% liquid
  assign product = product | default: section.settings.product
  assign link = link | default: product.url
  assign variant_id = variant_id | default: product.selected_or_first_available_variant.id
%}
```

### String Manipulation for Derived Values

Extract link title from URL for `title` attribute:

```liquid
{% assign link_title = link | split: '?' | first | split: '//' | last | split: '/' | slice: 1, 2 | join: ' ' | capitalize %}
```

## Dynamic HTML Elements

### Dynamic Tag Names

Use variables for tag names (e.g. h1 vs h2):

```liquid
{% assign tag = h1 | default: false | replace: 'true', '1' | replace: 'false', '2' | prepend: 'h' %}
<{{ tag }} class="section-heading__title">{{ title }}</{{ tag }}>
```

### Configurable Element Type

Allow link vs button based on context:

```liquid
{% assign el = element | default: 'a' %}
<{{ el }} href="{{ link }}" class="cta-button">
  {{ text }}
</{{ el }}>
```

## Multiple Rendering Modes

### Action-based Branching

Use `action` param for mutually exclusive behaviors. Keeps schema simple (one select vs multiple checkboxes):

```liquid
{% if scroll or action == 'scroll' %}
  <div class="cta-button {{ type }}" data-scroll-to="product" aria-label="Scroll to product">
    {% if text != blank %}{{ text }}{% else %}{{ 'products.product.shop_now' | t }}{% endif %}
  </div>
{% elsif action == 'atc' and variant_id != blank %}
  <div class="cta-button {{ type }}" data-variant-id="{{ variant_id }}" aria-label="Add to Cart">
    {% if text != blank %}{{ text }}{% else %}{{ 'products.product.add_to_cart' | t }}{% endif %}
  </div>
{% else %}
  <a href="{% if link != blank %}{{ link }}{% else %}/{% endif %}" title="{{ link_title }}" class="cta-button {{ type }}">
    {% if text != blank %}{{ text }}{% else %}{{ 'products.product.shop_now' | t }}{% endif %}
  </a>
{% endif %}
```

Add-to-cart uses a `div` with `data-variant-id`; JavaScript handles the form submission. No `<form>` in snippet.

### Captured Auxiliary Content

Use `{% capture %}` for optional content reused across all modes (e.g. CTA note below button):

```liquid
{% capture cta_note %}
  {% if note != blank %}
    <div class="cta-note">
      {% if note_icon != blank %}
        {{ note_icon | image_url: width: 30 | image_tag: loading: 'lazy', alt: '' }}
      {% endif %}
      <p>{{ note }}</p>
    </div>
  {% endif %}
{% endcapture %}

{% if action == 'scroll' %}
  <div class="cta-button {{ type }}">...</div>
  {{ cta_note }}
{% elsif action == 'atc' %}
  <div class="cta-button {{ type }}">...</div>
  {{ cta_note }}
{% else %}
  <a href="{{ link }}" class="cta-button {{ type }}">...</a>
  {{ cta_note }}
{% endif %}
```

### Translation Fallbacks

Use locale key when text is blank:

```liquid
{% if text != blank %}{{ text }}{% else %}{{ 'products.product.add_to_cart' | t }}{% endif %}
```

## Nested Snippet Rendering

### Conditional Nesting

Render child snippets only when relevant:

```liquid
{% if show_ratings %}
  {% if ratings_type == 'custom' %}
    {% render 'ratings-custom', stars: ratings_stars, logo: ratings_logo, text: ratings_text %}
  {% else %}
    {% render 'ratings-widget', type: ratings_type %}
  {% endif %}
{% endif %}
```

### Pass-through Parameters

Forward all needed params to nested snippets. Avoid passing whole objects when a few values suffice:

```liquid
{% render 'countdown-timer'
  enabled: timer,
  type: timer_type,
  end_date: timer_date,
  timezone: timer_timezone,
  format: timer_format,
  class: 'section-heading__timer'
%}
```

## Block Iteration

When sections pass blocks into a snippet:

```liquid
{% if items.size > 0 %}
  <div class="section-heading__items">
    {% for item in items %}
      <div class="section-heading__item" {{ item.shopify_attributes }}>
        {% if item.settings.icon %}
          {{ item.settings.icon | image_url: width: 60 | image_tag: loading: 'lazy', alt: item.settings.text }}
        {% endif %}
        <p>{{ item.settings.text }}</p>
      </div>
    {% endfor %}
  </div>
{% endif %}
```

## Data Attributes for JavaScript

Use data attributes instead of inline scripts:

```liquid
<div class="countdown-timer {{ class }}"
  data-type="{{ type }}"
  data-end-date="{{ end_date }}"
  {% if timezone %}data-timezone="{{ timezone }}"{% endif %}
  {% if format %}data-format="{{ format }}"{% endif %}>
  <span class="countdown-timer__display">00:00:00</span>
</div>
```

JavaScript reads these and initializes behavior. Keeps snippet portable.

## Inline Styles for Dynamic Values

Use inline styles for colors/sizes passed from schema:

```liquid
<div class="announcement-bar" style="background-color: {{ bg_color }}; color: {{ text_color }};">
  <!-- Content -->
</div>
```

## Case Statements

Use `case` for multiple variants:

```liquid
{% case variant %}
  {% when 'accent' %}
    {% assign btn_class = 'cta-button--accent' %}
  {% when 'outline' %}
    {% assign btn_class = 'cta-button--outline' %}
  {% else %}
    {% assign btn_class = 'cta-button--primary' %}
{% endcase %}
```

## Class Variants

Support variants via parameter concatenation:

```liquid
{% assign classes = variant | append: ' ' | append: class | strip %}
<div class="cta-button {{ classes }}">
```

Conditional modifier:

```liquid
<div class="section-heading__text{% if read_more %} section-heading__text--expandable{% endif %}">{{ text }}</div>
```

## Object Access

Pass objects when the snippet needs them. Derive values with fallbacks:

```liquid
{% liquid
  assign product = product | default: section.settings.product
  assign link = link | default: product.url
  assign variant_id = variant_id | default: product.selected_or_first_available_variant.id
%}
```

For collection links:

```liquid
{% if shop_all != blank %}
  <a href="{{ shop_all.url }}" class="section-heading__shop-all">{{ shop_all.title }}</a>
{% endif %}
```

## Icon and Image Rendering

Conditional icon:

```liquid
{% if show_icon %}{% render 'icon-arrow' %}{% endif %}
```

If your theme has an image snippet, use it. Otherwise use built-in filters:

```liquid
{% if image %}
  {% assign img_width = width | default: 400 %}
  {{ image | image_url: width: img_width | image_tag: loading: 'lazy', alt: alt | default: '' }}
{% endif %}
```

## Snippet Types (Reference Examples)

### Heading Snippet

- **Purpose**: Reusable section heading (title, subtitle, alignment, optional link)
- **Params**: `title`, `sub_text`, `align`, `align_mb`, `shop_all`, `h1`, `class`
- **Pattern**: Wrapper conditional, dynamic tag, element-level blanks

### CTA Snippet

- **Purpose**: Links, scroll-to-product, or add-to-cart with optional note
- **Params**: `text`, `link`, `type` (style variant), `action` (`""` | `"atc"` | `"scroll"`), `variant_id`, `product` (fallback for link/variant_id), `note`, `note_icon`
- **Pattern**: Action-based branching, `{% capture %}` for note, translation fallbacks, data attributes for ATC/scroll (JS handles submit)
- **Add-to-cart**: Use `div` with `data-variant-id`; theme JS intercepts and submits AJAX form

### Announcement Bar Snippet

- **Purpose**: Top bar with text, optional timer, colors from schema
- **Params**: `text`, `bg_color`, `text_color`, `timer`, `timer_*` (pass-through)
- **Pattern**: Inline styles, nested timer snippet, wrapper conditional

### Image Snippet

- **Purpose**: Consistent image output with lazy load, alt, sizes
- **Params**: `image`, `width`, `alt`, `loading`, `class`
- **Pattern**: Blank check, default width, optional class

### Countdown/Timer Snippet

- **Purpose**: JS-driven countdown with config from data attributes
- **Params**: `type`, `end_date`, `timezone`, `format`, `class`
- **Pattern**: Data attributes, no inline JS, placeholder display text

## Schema Integration

When sections use snippets, schema should mirror snippet params:

1. **Header** to group related settings
2. **Checkbox** for enable/disable
3. **Text/richtext** for content
4. **Select** for variant/type/action
5. **Color** for dynamic colors
6. **URL** for links
7. **image_picker** for optional icons (e.g. CTA note icon)
8. **visible_if** – **Required on every setting** that depends on an enable/parent checkbox

### CTA Schema Example

```json
{
  "type": "header",
  "content": "CTA"
},
{
  "type": "checkbox",
  "id": "show_cta",
  "label": "Show CTA",
  "default": true
},
{
  "type": "select",
  "id": "cta_action",
  "label": "CTA Action",
  "default": "atc",
  "visible_if": "{{ section.settings.show_cta }}",
  "options": [
    { "value": "", "label": "Link" },
    { "value": "atc", "label": "Add to Cart" },
    { "value": "scroll", "label": "Scroll to Product" }
  ]
},
{
  "type": "select",
  "id": "cta_type",
  "label": "CTA Style",
  "default": "accent",
  "visible_if": "{{ section.settings.show_cta }}",
  "options": [
    { "value": "light", "label": "Light" },
    { "value": "dark", "label": "Dark" },
    { "value": "accent", "label": "Accent" }
  ]
},
{
  "type": "text",
  "id": "cta_text",
  "label": "CTA Text",
  "default": "Add to Cart",
  "visible_if": "{{ section.settings.show_cta }}"
},
{
  "type": "text",
  "id": "cta_note",
  "label": "CTA Note",
  "visible_if": "{{ section.settings.show_cta }}"
},
{
  "type": "image_picker",
  "id": "cta_note_icon",
  "label": "CTA Note Icon",
  "visible_if": "{{ section.settings.show_cta }}"
}
```

### Heading Schema Example

```json
{
  "type": "header",
  "content": "Heading"
},
{
  "type": "checkbox",
  "id": "heading",
  "label": "Show Heading",
  "default": true
},
{
  "type": "text",
  "id": "heading_title",
  "label": "Title",
  "visible_if": "{{ section.settings.heading }}"
},
{
  "type": "text",
  "id": "heading_sub_text",
  "label": "Subtitle",
  "visible_if": "{{ section.settings.heading }}"
},
{
  "type": "select",
  "id": "heading_align",
  "label": "Alignment",
  "default": "center",
  "visible_if": "{{ section.settings.heading }}",
  "options": [
    { "value": "start", "label": "Start" },
    { "value": "center", "label": "Center" },
    { "value": "end", "label": "End" }
  ]
}
```

## Best Practices

1. **Wrap in conditionals** when snippet might output nothing
2. **Use kebab-case** for multi-word parameters
3. **Provide defaults** with `default` filter (`product | default: section.settings.product`)
4. **Check blank** before rendering content
5. **Document params** in structured comment blocks
6. **Use action select** instead of multiple booleans for mutually exclusive modes
7. **Use `{% capture %}`** for auxiliary content (notes, badges) reused across branches
8. **Use data attributes** for JS (ATC, scroll); keep snippets free of inline scripts
9. **Use translation fallbacks** when text is blank: `{% if text != blank %}{{ text }}{% else %}{{ 'key' | t }}{% endif %}`
10. **Support class/variant params** for styling flexibility
11. **Use inline styles** only for schema-driven values (colors, etc.)
12. **Use `case`** for 3+ conditional branches
13. **Keep snippets focused** – one clear responsibility per file
14. **Avoid deep nesting** – 2–3 levels max; extract if deeper
15. **Name consistently** – `section-heading`, `cta-button`, `announcement-bar`
16. **Add visible_if to every setting** – All settings under an enable checkbox must have `visible_if` referencing that checkbox
规则

Shopify Rich Schema Sections

Universal conventions for building Shopify theme sections with blocks, schema, and Liquid. Works with any theme setup

# Rich Shopify Schema Sections Conventions

Universal conventions for building Shopify theme sections with blocks, schema, and Liquid. Works with any theme setup.

## Liquid Variable Assignments

### Section Settings Shorthand

Always assign section settings to a shorthand variable at the top of the file:

```liquid
{% assign ss = section.settings %}
```

### Section Selector Variable

Create a section selector variable for CSS scoping:

```liquid
{% assign sc = '#shopify-section-' | append: section.id %}
```

### Block Settings Shorthand

When iterating over blocks, assign block settings to a shorthand variable:

```liquid
{% for block in section.blocks %}
  {% assign bs = block.settings %}
  <!-- Use bs instead of block.settings -->
{% endfor %}
```

### Block Filtering by Type

Filter blocks by type using the `where` filter. Use `first` to get a single block, or iterate over the filtered array:

```liquid
{% liquid
  assign faqs = section.blocks | where: 'type', 'faq'
  assign hero_block = section.blocks | where: 'type', 'hero' | first
  assign cards = section.blocks | where: 'type', 'card'
%}
```

### Default Fallbacks

Use the `default` filter for fallback values:

```liquid
{% assign image_mb = ss.image_mb | default: ss.image %}
{% assign bg_image_mb = ss.bg_image_mb | default: ss.bg_image %}
```

## HTML Structure

### Root Container

Use a `<div>` with a section-specific class as the root wrapper:

```liquid
<div class="feature-cards" {{ block.shopify_attributes }}>
  <!-- Content -->
</div>
```

For dynamic styling, use inline styles when needed:

```liquid
<div class="feature-cards" style="background-color: {{ ss.bg_color }};">
  <!-- Content -->
</div>
```

### Semantic HTML

Use standard semantic elements for layout:

- `<div>` with flex/grid classes for layout containers
- `<section>`, `<article>`, `<header>` for semantic grouping
- `<ul>`/`<li>` for list content (e.g. FAQ, feature lists)
- `<a>` for links

```liquid
<div class="feature-cards__wrapper">
  <div class="feature-cards__content">
    <div class="feature-cards__items">
      <!-- Cards -->
    </div>
  </div>
  <div class="feature-cards__image">
    <!-- Image -->
  </div>
</div>
```

### BEM Naming Convention

Follow BEM (Block Element Modifier) naming:

- **Block**: Component name (e.g. `feature-cards`)
- **Element**: `feature-cards__item`, `feature-cards__title`, `feature-cards__content`
- **Modifier**: Use `--` prefix (e.g. `feature-cards--compact`)

### Conditional Rendering

Always check for blank values before rendering:

```liquid
{% if bs.subtitle != blank %}
  <div class="feature-cards__subtitle">{{ bs.subtitle }}</div>
{% endif %}
```

### Snippet Rendering

Use `{% render %}` for reusable snippets when your theme provides them. Pass all necessary parameters:

```liquid
{% render 'section-heading',
  title: ss.heading_title,
  alignment: ss.heading_align,
  subtitle: ss.heading_subtitle
%}
```

If your theme does not have a heading snippet, output the HTML directly in the section.

## CSS Styling

### Scoped Styles

Use `{% style %}` tag for section-specific styles that reference settings:

```liquid
{% style %}
  {{ sc }} {
    background-color: {{ ss.bg_color }};
  }
  {{ sc }} .feature-cards__item {
    --accent-color: {{ ss.accent_color }};
    background-color: {{ ss.card_bg_color }};
  }
{% endstyle %}
```

### CSS Variables

Define CSS variables in the style tag for dynamic values, then reference in CSS:

```liquid
{{ sc }} .feature-cards__item {
  --accent-color: {{ ss.accent_color }};
}
```

```css
.feature-cards__item-title strong {
  color: var(--accent-color, #000);
}
```

### Inline Styles

Use inline styles for dynamic block-level values:

```liquid
<div class="feature-cards__progress" style="background-color: {{ bs.progress_bg_color }};">
  <div class="feature-cards__progress-bar" style="width: {{ bs.progress_num }}%; background-color: {{ bs.progress_color }};">
    {{ bs.progress_num }}% {{ bs.progress_text }}
  </div>
</div>
```

### Conditional Style Tags

Use `<style>` tag for conditional responsive styles (not `{% style %}`):

```liquid
{% if ss.content_first or ss.content_first_mb %}
  <style>
    {% if ss.content_first_mb %}
      @media (max-width: 767px) {
        {{ sc }} .feature-cards__content {
          order: -1;
        }
      }
    {% endif %}
    {% if ss.content_first %}
      @media (min-width: 768px) {
        {{ sc }} .feature-cards__content {
          order: -1;
        }
      }
    {% endif %}
  </style>
{% endif %}
```

### Complex Style Logic

Use Liquid logic within style tags for calculations:

```liquid
<style>
  {{ sc }} .hero__card {
    {% assign opacity = ss.card_bg_opacity | divided_by: 100.0 %}
    background-color: {{ ss.card_bg_color | color_modify: 'alpha', opacity }};
  }
  {{ sc }} .hero__title {
    max-width: {{ ss.title_max_width }}px;
    font-family: {{ ss.title_font }};
    font-weight: {{ ss.title_font_weight }};
  }
  @media (min-width: 1024px) {
    {{ sc }} .hero {
      height: {{ ss.background_height }}px;
    }
  }
</style>
```

## Schema Structure

### Section Name

Use descriptive, capitalized names:

```json
{
  "name": "Feature Cards"
}
```

### Settings Organization

Group related settings with headers:

```json
{
  "type": "header",
  "content": "Heading"
}
```

### Setting Types

#### Color Pickers

Always provide defaults:

```json
{
  "type": "color",
  "id": "bg_color",
  "label": "Background Color",
  "default": "#ffffff"
}
```

#### Checkboxes

Use for boolean toggles:

```json
{
  "type": "checkbox",
  "id": "heading_h1",
  "label": "Heading as H1"
}
```

#### Select Dropdowns

Provide clear, short labels:

```json
{
  "type": "select",
  "id": "heading_align",
  "label": "Heading Alignment",
  "default": "center",
  "options": [
    { "value": "start", "label": "Start" },
    { "value": "center", "label": "Center" },
    { "value": "end", "label": "End" }
  ]
}
```

#### Range Inputs

Specify min, max, step, and default:

```json
{
  "type": "range",
  "id": "progress_num",
  "label": "Progress Percentage",
  "default": 80,
  "min": 0,
  "max": 100,
  "step": 1
}
```

#### Rich Text

Use for formatted text with optional info hints:

```json
{
  "type": "richtext",
  "id": "spec_title",
  "label": "Specification Title",
  "info": "Use bold to emphasize with accent color",
  "default": "<p>Feature <strong>highlight</strong></p>"
}
```

#### Textarea

Use for longer text without formatting:

```json
{
  "type": "textarea",
  "id": "description",
  "label": "Description",
  "default": "Default description text"
}
```

#### Video

Use for video picker:

```json
{
  "type": "video",
  "id": "video",
  "label": "Background Video (Optional)"
}
```

#### Image Picker

Use for image selection:

```json
{
  "type": "image_picker",
  "id": "image",
  "label": "Image"
}
```

#### URL

Use for link inputs:

```json
{
  "type": "url",
  "id": "cta_url",
  "label": "CTA Link"
}
```

#### Collection

Use for collection picker:

```json
{
  "type": "collection",
  "id": "collection",
  "label": "Collection"
}
```

### Conditional Settings (visible_if)

Use `visible_if` to show settings conditionally:

```json
{
  "type": "text",
  "id": "heading_title",
  "label": "Heading Title",
  "visible_if": "{{ section.settings.heading }}"
}
```

Chain multiple conditions:

```json
{
  "type": "select",
  "id": "ratings_type",
  "label": "Ratings Type",
  "options": [
    { "value": "custom", "label": "Custom" },
    { "value": "widget", "label": "Widget" }
  ],
  "visible_if": "{{ section.settings.show_ratings }}"
}
```

### Blocks Configuration

#### Block Type and Name

Use descriptive names with kebab-case for type:

```json
{
  "type": "card",
  "name": "Card",
  "limit": 3
}
```

#### Multiple Block Types

Sections can have multiple block types:

```json
{
  "blocks": [
    {
      "type": "video-item",
      "name": "Video",
      "settings": [
        {
          "type": "video",
          "id": "video",
          "label": "Video"
        }
      ]
    },
    {
      "type": "heading-block",
      "name": "Heading",
      "limit": 2,
      "settings": [
        {
          "type": "text",
          "id": "title",
          "label": "Title"
        }
      ]
    }
  ]
}
```

#### Block Settings

Follow same patterns as section settings, organized logically.

### Presets

Always include a preset. Optionally include default settings and blocks:

```json
{
  "presets": [
    {
      "name": "Feature Cards",
      "category": "Content"
    }
  ]
}
```

With default settings and blocks:

```json
{
  "presets": [
    {
      "name": "FAQ Section",
      "settings": {
        "bg_color": "#f7f7f7",
        "heading": true,
        "heading_title": "FREQUENTLY ASKED QUESTIONS",
        "cta_url": "shopify://collections/all"
      },
      "blocks": [
        {
          "type": "faq",
          "settings": {
            "title": "Question title?",
            "text": "<p>Answer text here</p>"
          }
        }
      ]
    }
  ]
}
```

## Block Iteration Patterns

### Basic Loop

Iterate over blocks and assign settings:

```liquid
{% for block in section.blocks %}
  {% assign bs = block.settings %}
  <div class="feature-cards__item" {{ block.shopify_attributes }}>
    <!-- Block content -->
  </div>
{% endfor %}
```

### Conditional Block Features

Check for feature flags before rendering:

```liquid
{% if bs.progress %}
  <div class="feature-cards__progress">
    <!-- Progress bar content -->
  </div>
{% endif %}
```

### Filtered Block Iteration

Iterate over filtered blocks:

```liquid
{% assign faqs = section.blocks | where: 'type', 'faq' %}
{% if faqs.size > 0 %}
  <ul class="faq-section__list">
    {% for faq in faqs %}
      <li class="faq-section__item" {{ faq.shopify_attributes }}>
        {{ faq.settings.title }}
      </li>
    {% endfor %}
  </ul>
{% endif %}
```

### Multiple Block Type Handling

Handle different block types separately:

```liquid
{% assign videos = section.blocks | where: 'type', 'video-item' %}
{% assign heading_blocks = section.blocks | where: 'type', 'heading-block' %}

{% if heading_blocks.size > 0 %}
  <div class="video-section__headings">
    {% for block in heading_blocks %}
      <div class="video-section__heading" {{ block.shopify_attributes }}>
        <h4>{{ block.settings.title }}</h4>
        <p>{{ block.settings.text }}</p>
      </div>
    {% endfor %}
  </div>
{% endif %}

<div class="video-section__videos">
  {% for video in videos %}
    <div class="video-section__video" {{ video.shopify_attributes }}>
      <!-- Video content -->
    </div>
  {% endfor %}
</div>
```

### Image Rendering

Use Shopify's built-in filters for images:

```liquid
{% if bs.icon %}
  <div class="feature-cards__icon">
    {{ bs.icon | image_url: width: 200 | image_tag: loading: 'lazy', alt: bs.icon_alt | default: 'Icon' }}
  </div>
{% endif %}
```

For responsive images with mobile/desktop variants, use `default` for fallback:

```liquid
{% assign image_mb = ss.image_mb | default: ss.image %}
{% if image_mb %}
  {% if ss.image and ss.image_mb %}
    <picture>
      <source media="(min-width: 768px)" srcset="{{ ss.image | image_url: width: 840 }}">
      {{ image_mb | image_url: width: 600 | image_tag: loading: 'lazy' }}
    </picture>
  {% else %}
    {{ image_mb | image_url: width: 840 | image_tag: loading: 'lazy' }}
  {% endif %}
{% endif %}
```

If your theme provides a `responsive-image` or `responsive-picture` snippet, use that instead.

### Video Rendering

Use the `video_tag` filter for video elements:

```liquid
{% if ss.video %}
  {% if ss.bg_preload %}
    {% assign video_preload = 'metadata' %}
  {% else %}
    {% assign video_preload = 'none' %}
  {% endif %}
  <div class="hero__video">
    {{ ss.video | video_tag: controls: false, autoplay: true, loop: true, muted: true, playsinline: true, preload: video_preload }}
  </div>
{% endif %}
```

For video blocks:

```liquid
{% if video.settings.video %}
  {{ video.settings.video | video_tag: autoplay: false, loop: false, muted: true, controls: true }}
{% endif %}
```

## CTA Rendering

Wrap CTAs in a wrapper div for consistent spacing:

```liquid
{% if ss.cta and ss.cta_url != blank %}
  <div class="feature-cards__cta-wrapper">
    <a href="{{ ss.cta_url }}" class="button button--{{ ss.cta_style }}">
      {{ ss.cta_text | default: 'Learn more' }}
    </a>
  </div>
{% endif %}
```

If your theme has a CTA/button snippet (e.g. `main-cta`, `button`), use `{% render %}` and pass the parameters your snippet expects.

## Conditional Classes

Add conditional classes based on settings:

```liquid
<div class="cards-section__grid {{ ss.layout }}{% if ss.enable_carousel %} cards-section__grid--carousel{% endif %}">
  <!-- Content -->
</div>
```

## CSS Patterns

### Component Structure

- Use BEM naming consistently
- Mobile-first responsive design
- Use CSS variables for dynamic colors
- Define variables in `{% style %}` tag, reference in global CSS

### Responsive Breakpoints

Follow standard breakpoints:

- Mobile: default (no media query)
- Tablet: `@media screen and (min-width: 768px)`
- Desktop: `@media screen and (min-width: 1024px)`
- Large Desktop: `@media screen and (min-width: 1280px)`

### Card Layout Example

```css
.feature-cards__items {
  display: flex;
  flex-direction: column;
  gap: 48px;
}

@media screen and (min-width: 1024px) {
  .feature-cards__items {
    flex-direction: row;
    justify-content: space-evenly;
    align-items: stretch;
    gap: 24px;
  }
  .feature-cards__item {
    flex: 1;
  }
}
```

## Icon Rendering

If your theme provides icon snippets, use them:

```liquid
<div class="video-button play">
  {% render 'icon-play' %}Play
</div>
```

Otherwise use inline SVG, icon fonts, or image pickers. Avoid hardcoding icons that merchants cannot customize.

## Best Practices

1. **Always check for blank values** before rendering content
2. **Use shorthand variables** (`ss`, `bs`) for cleaner code
3. **Group related settings** with headers in schema
4. **Provide defaults** for all settings
5. **Use descriptive labels** in schema (avoid redundancy with type)
6. **Scope CSS** using section selector variable (`sc`)
7. **Use CSS variables** for dynamic colors referenced in global CSS
8. **Use inline styles** for block-level dynamic values
9. **Include `{{ block.shopify_attributes }}`** on block root elements for theme editor
10. **Follow BEM naming** consistently throughout component
11. **Filter blocks by type** when sections have multiple block types
12. **Use `default` filter** for fallback values (especially mobile images)
13. **Use conditional `<style>` tags** for responsive conditional styles
14. **Use `visible_if`** in schema for progressive disclosure of settings
15. **Include default settings in presets** for better merchant experience
16. **Check block size** before iterating: `{% if faqs.size > 0 %}`
17. **Use `video_tag` filter** with appropriate attributes for video elements
18. **Prefer built-in Liquid filters** (`image_url`, `image_tag`) unless your theme has standardized snippets

来源:https://github.com/eyyMinda/directories