Instruction file imported from EcomExperts-io/Base (
.cursor/rules/blocks.mdc). Copyright stays with the author.
Theme Blocks Development Standards
Follow Shopify's theme blocks documentation.
Theme Block Fundamentals
Theme blocks are reusable components defined at the theme level that can be:
- Nested under sections and blocks
- Configured using settings in the theme editor
- Given presets and added by merchants
- Used as static blocks by theme developers
Blocks render in the editor and storefront when they are referenced in template files.
Basic Block Structure
{% doc %}
Block description and usage examples
@example
{% content_for 'block', type: 'block-name', id: 'unique-id' %}
{% enddoc %}
<div {{ block.shopify_attributes }} class="block-name">
<!-- Block content using block.settings -->
</div>
{% stylesheet %}
/*
Scoped CSS for this block
Use BEM structure
CSS written in here should be for components that are exclusively in this block. If the CSS will be used elsewhere, it should instead be written in assets/base.css
*/
{% endstylesheet %}
{% schema %}
{
"name": "Block Name",
"settings": [],
"presets": []
}
{% endschema %}
Static Block Usage
Static blocks are theme blocks that are rendered directly in Liquid templates by developers, rather than being dynamically added through the theme editor. This allows for predetermined block placement with optional default settings.
Basic Static Block Syntax:
{% content_for 'block', type: 'text', id: 'header-announcement' %}
Example: Product Template with Mixed Static and Dynamic Blocks
<!-- templates/product.liquid -->
<div class="product-page">
{% comment %} Static breadcrumb block {% endcomment %}
{% content_for 'block', type: 'breadcrumb', id: 'product-breadcrumb' %}
<div class="product-main">
<div class="product-media">
{% comment %} Static product gallery block {% endcomment %}
{% content_for 'block', type: 'product-gallery', id: 'main-gallery', settings: {
enable_zoom: true,
thumbnails_position: "bottom"
} %}
</div>
<div class="product-info">
{% comment %} Static product info blocks {% endcomment %}
{% content_for 'block', type: 'product-title', id: 'product-title' %}
{% content_for 'block', type: 'product-price', id: 'product-price' %}
{% content_for 'block', type: 'product-form', id: 'product-form' %}
{% comment %} Dynamic blocks area for additional content {% endcomment %}
<div class="product-extra-content">
{% content_for 'blocks' %}
</div>
</div>
</div>
{% comment %} Static related products block {% endcomment %}
{% content_for 'block', type: 'related-products', id: 'related-products', settings: {
heading: "You might also like",
limit: 4
} %}
</div>
Key Points about Static Blocks:
- They have a fixed
idthat makes them identifiable in the theme editor - Settings can be overridden in the theme editor despite having defaults
- They appear in the theme editor as locked blocks that can't be removed or reordered
- Useful for consistent layout elements that should always be present
- Can be mixed with dynamic block areas using
{% content_for 'blocks' %}
Schema Configuration
See schemas.md for rules on schemas
Advanced Schema Features
Exclude wrapper
{
"tag": null // No wrapper - must include {{ block.shopify_attributes }} for proper editor function
}
Block Implementation Patterns
Accessing Block Data
Block Settings:
{{ block.settings.text }}
{{ block.settings.heading | escape }}
{{ block.settings.image | image_url: width: 800 }}
Block Properties:
{{ block.id }} // Unique block identifier
{{ block.type }} // Block type name
{{ block.shopify_attributes }} // Required for theme editor
Section Context:
{{ section.id }} // Parent section ID
{{ section.settings.heading | escape }}
{{ section.settings.image | image_url: width: 800 }}
Nested Blocks Implementation
Rendering Nested Blocks
<div class="block-container" {{ block.shopify_attributes }}>
<h2>{{ block.settings.heading | escape }}</h2>
<div class="nested-blocks">
{% content_for 'blocks' %}
</div>
</div>
Nesting with Layout Control
<div
class="group {{ block.settings.layout_direction }}"
style="--gap: {{ block.settings.gap }}px;"
{{ block.shopify_attributes }}
>
{% content_for 'blocks' %}
</div>
Presets with Nested Blocks
{
"presets": [
{
"name": "t:names.two_column_layout",
"category": "Layout",
"settings": {
"layout_direction": "horizontal"
},
"blocks": [
{
"type": "text",
"settings": {
"text": "Column 1 content"
}
},
{
"type": "text",
"settings": {
"text": "Column 2 content"
}
}
]
}
]
}
CSS and Styling
See css-standards.md for rules on writing CSS
Scoped Styles
{% stylesheet %}
.block-name {
padding: var(--block-padding, 1rem);
background: var(--block-background, transparent);
}
.block-name__title {
font-size: var(--title-size, 1.5rem);
color: var(--title-color, inherit);
}
.block-name--primary {
background-color: var(--color-primary);
}
.block-name--secondary {
background-color: var(--color-secondary);
}
{% endstylesheet %}
Dynamic CSS Variables
<div
class="custom-block"
style="
--block-padding: {{ block.settings.padding }}px;
--text-align: {{ block.settings.alignment }};
--background: {{ block.settings.background_color }};
"
{{ block.shopify_attributes }}
>
Block Targeting
Section Schema for Theme Blocks
{
"blocks": [
{ "type": "@theme" }, // Accept all theme blocks
{ "type": "@app" } // Accept app blocks
]
}
Restricted Block Targeting
{
"blocks": [
{
"type": "text",
"name": "Text Content"
},
{
"type": "image",
"name": "Image Content"
}
]
}
Common Block Patterns
Content Block
<div class="content-block {{ block.settings.style }}" {{ block.shopify_attributes }}>
{% if block.settings.heading != blank %}
<h3 class="content-block__heading">{{ block.settings.heading | escape }}</h3>
{% endif %}
{% if block.settings.text != blank %}
<div class="content-block__text">{{ block.settings.text }}</div>
{% endif %}
{% if block.settings.button_text != blank %}
<a href="{{ block.settings.button_url }}" class="content-block__button">
{{ block.settings.button_text | escape }}
</a>
{% endif %}
</div>
Media Block
<div class="media-block" {{ block.shopify_attributes }}>
{% if block.settings.image %}
<div class="media-block__image">
{{ block.settings.image | image_url: width: 800 | image_tag:
alt: block.settings.image.alt | default: block.settings.alt_text
}}
</div>
{% endif %}
{% if block.settings.video %}
<div class="media-block__video">
{{ block.settings.video | video_tag: controls: true }}
</div>
{% endif %}
</div>
Layout Block (Container)
<div
class="layout-block layout-block--{{ block.settings.layout_type }}"
style="
--columns: {{ block.settings.columns }};
--gap: {{ block.settings.gap }}px;
"
{{ block.shopify_attributes }}
>
{% content_for 'blocks' %}
</div>
Performance Best Practices
Conditional Rendering
{% liquid
assign has_content = false
if block.settings.heading != blank or block.settings.text != blank
assign has_content = true
endif
%}
{% if has_content %}
<div class="block-content" {{ block.shopify_attributes }}>
<!-- Content here -->
</div>
{% endif %}
Examples Referenced
text.liquid - Basic content block from existing project group.liquid - Container with nested blocks from existing project