Cursor rule
.cursor/rules/blocks.mdcDevelopment standards and best practices for creating/configuring/styling theme blocks, including static and nested blocks, schema configuration, CSS, and usage examples
Cursor rules
Quality
62/100
Scores the file, not the repository.Length
1,197 words
27 headings · 22 code blocksRepository
428
— · pushed 17 days agoLast changed
3 days ago
First indexed 3 days ago.1234567# Theme Blocks Development Standards89Follow [Shopify's theme blocks documentation](mdc:https:/shopify.dev/docs/storefronts/themes/architecture/blocks/theme-blocks/quick-start?framework=liquid.txt).1011## Theme Block Fundamentals1213Theme blocks are reusable components defined at the theme level that can be:1415- Nested under sections and blocks16- Configured using settings in the theme editor17- Given presets and added by merchants18- Used as [static blocks](mdc:https:/shopify.dev/docs/storefronts/themes/architecture/blocks/theme-blocks/static-blocks#statically-vs-dynamically-rendered-theme-blocks) by theme developers1920Blocks render in the editor and storefront when they are referenced in [template files](mdc:.cursor/rules/templates.mdc).2122### Basic Block Structure2324```liquid25{% doc %}26 Block description and usage examples2728 @example29 {% content_for 'block', type: 'block-name', id: 'unique-id' %}30{% enddoc %}3132<div33 {{ block.shopify_attributes }}34 class='block-name'35>36 <!-- Block content using block.settings -->37</div>3839{% stylesheet %}40 /*41 Scoped CSS for this block4243 Use BEM structure44 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](mdc:@assets/base.css)45 */46{% endstylesheet %}4748{% schema %}49{50 "name": "Block Name",51 "settings": [],52 "presets": []53}54{% endschema %}55```5657### Static Block Usage5859Static 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.6061**Basic Static Block Syntax:**6263```liquid64{% content_for 'block', type: 'text', id: 'header-announcement' %}65```6667**Example: Product Template with Mixed Static and Dynamic Blocks**6869```liquid70<!-- templates/product.liquid -->71<div class='product-page'>72 {% comment %} Static breadcrumb block {% endcomment %}73 {% content_for 'block', type: 'breadcrumb', id: 'product-breadcrumb' %}7475 <div class='product-main'>76 <div class='product-media'>77 {% comment %} Static product gallery block {% endcomment %}78 {%79 content_for 'block', type: 'product-gallery', id: 'main-gallery', settings: {80 enable_zoom: true,81 thumbnails_position: "bottom"82 }83 %}84 </div>8586 <div class='product-info'>87 {% comment %} Static product info blocks {% endcomment %}88 {% content_for 'block', type: 'product-title', id: 'product-title' %}89 {% content_for 'block', type: 'product-price', id: 'product-price' %}90 {% content_for 'block', type: 'product-form', id: 'product-form' %}9192 {% comment %} Dynamic blocks area for additional content {% endcomment %}93 <div class='product-extra-content'>94 {% content_for 'blocks' %}95 </div>96 </div>97 </div>9899 {% comment %} Static related products block {% endcomment %}100 {%101 content_for 'block', type: 'related-products', id: 'related-products', settings: {102 heading: "You might also like",103 limit: 4104 }105 %}106</div>107```108109**Key Points about Static Blocks:**110111- They have a fixed `id` that makes them identifiable in the theme editor112- Settings can be overridden in the theme editor despite having defaults113- They appear in the theme editor as locked blocks that can't be removed or reordered114- Useful for consistent layout elements that should always be present115- Can be mixed with dynamic block areas using `{% content_for 'blocks' %}`116117## Schema Configuration118119See [schemas.mdc](mdc:.cursor/rules/schemas.mdc) for rules on schemas120121### Advanced Schema Features122123#### Exclude wrapper124125```json126{127 "tag": null // No wrapper - must include {{ block.shopify_attributes }} for proper editor function128}129```130131## Block Implementation Patterns132133### Accessing Block Data134135**Block Settings:**136137```liquid138{{ block.settings.text }}139{{ block.settings.heading | escape }}140{{ block.settings.image | image_url: width: 800 }}141```142143**Block Properties:**144145```liquid146{{ block.id }} // Unique block identifier {{ block.type }} // Block type name {{ block.shopify_attributes }} // Required147for theme editor148```149150**Section Context:**151152```liquid153{{ section.id }} // Parent section ID154{{ section.settings.heading | escape }}155{{ section.settings.image | image_url: width: 800 }}156```157158## Nested Blocks Implementation159160### Critical Constraint: Single `content_for 'blocks'` Per File161162**IMPORTANT:** There can only be **ONE** `{% content_for 'blocks' %}` call per Liquid file. If you need to use the blocks content in multiple places (e.g., in conditional branches), you must capture it first:163164```liquid165{% comment %} ✅ CORRECT - Capture once, use multiple times {% endcomment %}166{% capture blocks_content %}167 {% content_for 'blocks' %}168{% endcapture %}169170{% if condition %}171 <div class='layout-a'>172 {{ blocks_content }}173 </div>174{% else %}175 <div class='layout-b'>176 {{ blocks_content }}177 </div>178{% endif %}179```180181```liquid182{% comment %} ❌ INCORRECT - Multiple content_for calls will cause errors {% endcomment %}183{% if condition %}184 <div class='layout-a'>185 {% content_for 'blocks' %}186 <!-- First call -->187 </div>188{% else %}189 <div class='layout-b'>190 {% content_for 'blocks' %}191 <!-- ERROR: Duplicate entry -->192 </div>193{% endif %}194```195196**Common Error Message:**197198```199Liquid syntax error: Duplicate entries for 'content_for "blocks"'200```201202### Rendering Nested Blocks203204```liquid205<div206 class='block-container'207 {{ block.shopify_attributes }}208>209 <h2>{{ block.settings.heading | escape }}</h2>210211 <div class='nested-blocks'>212 {% content_for 'blocks' %}213 </div>214</div>215```216217### Nesting with Layout Control218219```liquid220<div221 class='group {{ block.settings.layout_direction }}'222 style='--gap: {{ block.settings.gap }}px;'223 {{ block.shopify_attributes }}224>225 {% content_for 'blocks' %}226</div>227```228229### Presets with Nested Blocks230231```json232{233 "presets": [234 {235 "name": "t:names.two_column_layout",236 "category": "Layout",237 "settings": {238 "layout_direction": "horizontal"239 },240 "blocks": [241 {242 "type": "text",243 "settings": {244 "text": "Column 1 content"245 }246 },247 {248 "type": "text",249 "settings": {250 "text": "Column 2 content"251 }252 }253 ]254 }255 ]256}257```258259When blocks are declared as an object instead of an array, include `block_order`:260261```javascript262blocks: {263 header: {264 type: 'group',265 blocks: {266 title: { type: 'product-title' },267 price: { type: 'price' },268 },269 block_order: ['title', 'price'],270 },271}272```273274## CSS and Styling275276See [css-standards.mdc](mdc:.cursor/rules/css-standards.mdc) for rules on writing CSS277278### Scoped Styles279280```liquid281{% stylesheet %}282 .block-name {283 padding: var(--block-padding, 1rem);284 background: var(--block-background, transparent);285 }286287 .block-name__title {288 font-size: var(--title-size, 1.5rem);289 color: var(--title-color, inherit);290 }291292 .block-name--primary {293 background-color: var(--color-primary);294 }295296 .block-name--secondary {297 background-color: var(--color-secondary);298 }299{% endstylesheet %}300```301302### Dynamic CSS Variables303304```liquid305<div306 class="custom-block"307 style="308 --block-padding: {{ block.settings.padding }}px;309 --text-align: {{ block.settings.alignment }};310 --background: {{ block.settings.background_color }};311 "312 {{ block.shopify_attributes }}313>314```315316## Block Targeting317318### Section Schema for Theme Blocks319320```json321{322 "blocks": [323 { "type": "@theme" }, // Accept all theme blocks324 { "type": "@app" } // Accept app blocks325 ]326}327```328329### Restricted Block Targeting330331```json332{333 "blocks": [334 {335 "type": "text",336 "name": "Text Content"337 },338 {339 "type": "image",340 "name": "Image Content"341 }342 ]343}344```345346## Common Block Patterns347348### Content Block349350```liquid351<div352 class='content-block {{ block.settings.style }}'353 {{ block.shopify_attributes }}354>355 {% if block.settings.heading != blank %}356 <h3 class='content-block__heading'>{{ block.settings.heading | escape }}</h3>357 {% endif %}358359 {% if block.settings.text != blank %}360 <div class='content-block__text'>{{ block.settings.text }}</div>361 {% endif %}362363 {% if block.settings.button_text != blank %}364 <a365 href='{{ block.settings.button_url }}'366 class='content-block__button'367 >368 {{ block.settings.button_text | escape }}369 </a>370 {% endif %}371</div>372```373374### Media Block375376```liquid377<div378 class='media-block'379 {{ block.shopify_attributes }}380>381 {% if block.settings.image %}382 <div class='media-block__image'>383 {{384 block.settings.image385 | image_url: width: 800386 | image_tag: alt: block.settings.image.alt387 | default: block.settings.alt_text388 }}389 </div>390 {% endif %}391392 {% if block.settings.video %}393 <div class='media-block__video'>394 {{ block.settings.video | video_tag: controls: true }}395 </div>396 {% endif %}397</div>398```399400### Layout Block (Container)401402```liquid403<div404 class='layout-block layout-block--{{ block.settings.layout_type }}'405 style='406 --columns: {{ block.settings.columns }};407 --gap: {{ block.settings.gap }}px;408 '409 {{ block.shopify_attributes }}410>411 {% content_for 'blocks' %}412</div>413```414415## Performance Best Practices416417### Conditional Rendering418419```liquid420{% liquid421 assign has_content = false422 if block.settings.heading != blank or block.settings.text != blank423 assign has_content = true424 endif425%}426427{% if has_content %}428 <div429 class='block-content'430 {{ block.shopify_attributes }}431 >432 <!-- Content here -->433 </div>434{% endif %}435```436437## Examples Referenced438439[text.liquid](mdc:.cursor/rules/examples/block-example-text.liquid) - Basic content block from existing project440[group.liquid](mdc:.cursor/rules/examples/block-example-group.liquid) - Container with nested blocks from existing project441
Also in Shopify/horizon
Diff this repo’s formatsOne repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| Shopify/horizon.cursor/rules/accordion-accessibility.mdc · 428 | Cursor rules | ui | 40/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/animation-accessibility.mdc · 428 | Cursor rules | ui | 40/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/breadcrumb-accessibility.mdc · 428 | Cursor rules | ui | 36/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/carousel-accessibility.mdc · 428 | Cursor rules | ui | 32/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/cart-drawer-accessibility.mdc · 428 | Cursor rules | ui | 40/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/chat-window-accessibility.mdc · 428 | Cursor rules | styleui | 24/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/color-contrast-accessibility.mdc · 428 | Cursor rules | lint-formatui | 44/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/color-swatch-accessibility.mdc · 428 | Cursor rules | ui | 40/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/commit-messages.mdc · 428 | Cursor rules | setuplint-formattypesgit | 62/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/css-standards.mdc · 428 | Cursor rules | stylearchuiperformance+3 | 49/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/disclosure-accessibility.mdc · 428 | Cursor rules | ui | 40/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/dropdown-navigation-accessibility.mdc · 428 | Cursor rules | styleui | 36/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/flip-card-accessibility.mdc · 428 | Cursor rules | styleui | 36/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/form-accessibility.mdc · 428 | Cursor rules | ui | 32/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/javascript-standards.mdc · 428 | Cursor rules | stylearchtypesui+2 | 54/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/landmark-accessibility.mdc · 428 | Cursor rules | ui | 40/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/liquid.mdc · 428 | Cursor rules | buildstyletypesdatabase+2 | 77/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/locales.mdc · 428 | Cursor rules | arch | 49/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/localization.mdc · 428 | Cursor rules | style | 54/100 | 3 days ago | |
| Shopify/horizon.cursor/rules/mobile-accessibility-standards.mdc · 428 | Cursor rules | styleui | 36/100 | 3 days ago |
Diff against .cursor/rules/accordion-accessibility.mdc Diff against .cursor/rules/animation-accessibility.mdc Diff against .cursor/rules/breadcrumb-accessibility.mdc Diff against .cursor/rules/carousel-accessibility.mdc Diff against .cursor/rules/cart-drawer-accessibility.mdc Diff against .cursor/rules/chat-window-accessibility.mdc Diff against .cursor/rules/color-contrast-accessibility.mdc Diff against .cursor/rules/color-swatch-accessibility.mdc Diff against .cursor/rules/commit-messages.mdc Diff against .cursor/rules/css-standards.mdc Diff against .cursor/rules/disclosure-accessibility.mdc Diff against .cursor/rules/dropdown-navigation-accessibility.mdc Diff against .cursor/rules/flip-card-accessibility.mdc Diff against .cursor/rules/form-accessibility.mdc Diff against .cursor/rules/javascript-standards.mdc Diff against .cursor/rules/landmark-accessibility.mdc Diff against .cursor/rules/liquid.mdc Diff against .cursor/rules/locales.mdc Diff against .cursor/rules/localization.mdc Diff against .cursor/rules/mobile-accessibility-standards.mdc
