From 8f5ce097c49d3401ae335491ab1dbd1ea62fb2d6 Mon Sep 17 00:00:00 2001 From: Alok Swamy Date: Fri, 8 Aug 2025 11:04:40 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=A4=96=20Sync=20AI=20Instructions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ai/claude/CLAUDE.md | 1475 +++++++++++++++++ ai/cursor/rules/assets.mdc | 15 + ai/cursor/rules/blocks.mdc | 339 ++++ .../rules/examples/block-example-group.mdc | 103 ++ .../rules/examples/block-example-text.mdc | 59 + ai/cursor/rules/examples/section-example.mdc | 61 + ai/cursor/rules/examples/snippet-example.mdc | 72 + ai/cursor/rules/liquid.mdc | 827 +++++++++ ai/cursor/rules/locales.mdc | 100 ++ ai/cursor/rules/localization.mdc | 67 + ai/cursor/rules/mcp.mdc | 2 + ai/cursor/rules/schemas.mdc | 184 ++ ai/cursor/rules/sections.mdc | 84 + ai/cursor/rules/settings-schema.mdc | 51 + ai/cursor/rules/snippets.mdc | 119 ++ ai/github/copilot-instructions.md | 1475 +++++++++++++++++ 16 files changed, 5033 insertions(+) create mode 100644 ai/claude/CLAUDE.md create mode 100644 ai/cursor/rules/assets.mdc create mode 100644 ai/cursor/rules/blocks.mdc create mode 100644 ai/cursor/rules/examples/block-example-group.mdc create mode 100644 ai/cursor/rules/examples/block-example-text.mdc create mode 100644 ai/cursor/rules/examples/section-example.mdc create mode 100644 ai/cursor/rules/examples/snippet-example.mdc create mode 100644 ai/cursor/rules/liquid.mdc create mode 100644 ai/cursor/rules/locales.mdc create mode 100644 ai/cursor/rules/localization.mdc create mode 100644 ai/cursor/rules/mcp.mdc create mode 100644 ai/cursor/rules/schemas.mdc create mode 100644 ai/cursor/rules/sections.mdc create mode 100644 ai/cursor/rules/settings-schema.mdc create mode 100644 ai/cursor/rules/snippets.mdc create mode 100644 ai/github/copilot-instructions.md diff --git a/ai/claude/CLAUDE.md b/ai/claude/CLAUDE.md new file mode 100644 index 0000000..0960bbb --- /dev/null +++ b/ai/claude/CLAUDE.md @@ -0,0 +1,1475 @@ +🚨 MANDATORY: YOU MUST CALL "learn_shopify_api" ONCE WHEN WORKING WITH LIQUID THEMES. + +## Theme Architecture + +**Key principles: focus on generating snippets, blocks, and sections; users may create templates using the theme editor** + +### Directory structure + +``` +. +├── assets # Stores static assets (CSS, JS, images, fonts, etc.) +├── blocks # Reusable, nestable, customizable components +├── config # Global theme settings and customization options +├── layout # Top-level wrappers for pages (layout templates) +├── locales # Translation files for theme internationalization +├── sections # Modular full-width page components +├── snippets # Reusable Liquid code or HTML fragments +└── templates # Templates combining sections and blocks to define page structures +``` + +#### `sections` + +- Sections are `.liquid` files that allow you to create reusable modules that can be customized by merchants +- Sections can include blocks which allow merchants to add, remove, and reorder content within a section +- Sections are made customizable by including the required `{% schema %}` tag that exposes settings in the theme editor via a JSON object. Validate that JSON object using the `schemas/section.json` JSON schema +- Examples of sections: hero banners, product grids, testimonials, featured collections + +#### `blocks` + +- Blocks are `.liquid` files that allow you to create reusable small components that can be customized by merchants (they don't need to fit the full-width of the page) +- Blocks are ideal for logic that needs to be reused and also edited in the theme editor by merchants +- Blocks can include other nested blocks which allow merchants to add, remove, and reorder content within a block too +- Blocks are made customizable by including the required `{% schema %}` tag that exposes settings in the theme editor via a JSON object. Validate that JSON object using the `schemas/theme_block.json` JSON schema +- Blocks must have the `{% doc %}` tag as the header if you directly/staticly render them in other file via `{% content_for 'block', id: '42', type: 'block_name' %}` +- Examples of blocks: individual testimonials, slides in a carousel, feature items + +#### `snippets` + +- Snippets are reusable code fragments rendered in blocks, sections, and layouts files via the `render` tag +- Snippets are ideal for logic that needs to be reused but not directly edited in the theme editor by merchants +- Snippets accept parameters when rendered for dynamic behavior +- Snippets must have the `{% doc %}` tag as the header +- Examples of sections: buttons, meta-tags, css-variables, and form elements + +#### `layout` + +- Defines the overall HTML structure of the site, including `` and ``, and wraps other templates to provide a consistent frame +- Contains repeated global elements like navigation, cart drawer, footer, and usually includes CSS/JS assets and meta tags +- Must include `{{ content_for_header }}` to inject Shopify scripts in the `` and `{{ content_for_layout }}` to render the page content + +#### `config` + +- `config/settings_schema.json` is a JSON file that defines schema for global theme settings. Validate the shape shape of this JSON file using the `schemas/theme_settings.json` JSON schema +- `config/settings_data.json` is JSON file that holds the data for the settings defined by `config/settings_schema.json` + +#### `assets` + +- Contains static files like CSS, JavaScript, and images—including compiled and optimized assets—referenced in templates via the `asset_url` filter +- Keep it here only `critical.css` and static files necessary for every page, otherwise prefer the usage of the `{% stylesheet %}` and `{% javascript %}` tags + +#### `locales` + +- Stores translation files organized by language code (e.g., `en.default.json`, `fr.json`) to localize all user-facing theme content and editor strings +- Enables multi-language support by providing translations accessible via filters like `{{ 'key' | t }}` in Liquid for proper internationalization +- Validate `locales` JSON files using the `schemas/translations.json` JSON schema + +#### `templates` + +- JSON file that define the structure, ordering, and which sections and blocks appear on each page type, allowing merchants to customize layouts without code changes + +### CSS & JavaScript + +- Write CSS and JavaScript per components using the `{% stylesheet %}` and `{% javascript %}` tags +- Note: `{% stylesheet %}` and `{% javascript %}` are only supported in `snippets/`, `blocks/`, and `sections/` + +### LiquidDoc + +Snippets and blocks (when blocks are statically rendered) must include the LiquidDoc header that documents the purpose of the file and required parameters. Example: + +```liquid +{% doc %} + Renders a responsive image that might be wrapped in a link. + + @param {image} image - The image to be rendered + @param {string} [url] - An optional destination URL for the image + + @example + {% render 'image', image: product.featured_image %} +{% enddoc %} + +{{ image | image_url: width: 200, height: 200 | image_tag }} +``` + +## The `{% schema %}` tag on blocks and sections + +**Key principles: follow the "Good practices" and "Validate the `{% schema %}` content" using JSON schemas** + +### Good practices + +When defining the `{% schema %}` tag on sections and blocks, follow these guidelines to use the values: + +**Single property settings**: For settings that correspond to a single CSS property, use CSS variables: +```liquid +
+ Example +
+ +{% stylesheet %} + .collection { + gap: var(--gap); + } +{% endstylesheet %} + +{% schema %} +{ + "settings": [{ + "type": "range", + "label": "gap", + "id": "gap", + "min": 0, + "max": 100, + "unit": "px", + "default": 0, + }] +} +{% endschema %} +``` + +**Multiple property settings**: For settings that control multiple CSS properties, use CSS classes: +```liquid +
+ Example +
+ +{% stylesheet %} + .collection--full-width { + /* multiple styles */ + } + .collection--narrow { + /* multiple styles */ + } +{% endstylesheet %} + +{% schema %} +{ + "settings": [{ + "type": "select", + "id": "layout", + "label": "layout", + "values": [ + { "value": "collection--full-width", "label": "t:options.full" }, + { "value": "collection--narrow", "label": "t:options.narrow" } + ] + }] +} +{% endschema %} +``` + +#### Mobile layouts + +If you need to create a mobile layout and you want the merchant to be able to select one or two columns, use a select input: + +```liquid +{% schema %} +{ + "type": "select", + "id": "columns_mobile", + "label": "Columns on mobile", + "options": [ + { "value": 1, "label": "1" }, + { "value": "2", "label": "2" } + ] +} +{% endschema %} +``` + +## Liquid + +### Liquid delimiters + +- **`{{ ... }}`**: Output – prints a value. +- **`{{- ... -}}`**: Output, trims whitespace around the value. +- **`{% ... %}`**: Logic/control tag (if, for, assign, etc.), does not print anything, no whitespace trim. +- **`{%- ... -%}`**: Logic/control tag, trims whitespace around the tag. + +**Tip:** +Adding a dash (`-`) after `{%`/`{{` or before `%}`/`}}` trims spaces or newlines next to the tag. + +**Examples:** +- `{{- product.title -}}` → print value, remove surrounding spaces or lines. +- `{%- if available -%}In stock{%- endif -%}` → logic, removes extra spaces/lines. + +### Liquid operators + +**Comparison operators:** +- == +- != +- > +- < +- >= +- <= + +**Logical operators:** +- `or` +- `and` +- `contains` - checks if a string contains a substring, or if an array contains a string + +#### Comparison and comparison tags + +**Key condition principles:** +- For simplificity, ALWAYS use nested `if` conditions when the logic requires more than one logical operator +- Parentheses are not supported in Liquid +- Ternary conditionals are not supported in Liquid, so always use `{% if cond %}` + +**Basic comparison example:** +```liquid +{% if product.title == "Awesome Shoes" %} + These shoes are awesome! +{% endif %} +``` + +**Multiple Conditions:** +```liquid +{% if product.type == "Shirt" or product.type == "Shoes" %} + This is a shirt or a pair of shoes. +{% endif %} +``` + +**Contains Usage:** +- For strings: `{% if product.title contains "Pack" %}` +- For arrays: `{% if product.tags contains "Hello" %}` +- Note: `contains` only works with strings, not objects in arrays + +**{% elsif %} (used inside if/unless only)** +```liquid +{% if a %} + ... +{% elsif b %} + ... +{% endif %} +``` + +**{% unless %}** +```liquid +{% unless condition %} + ... +{% endunless %} +``` + +**{% case %}** +```liquid +{% case variable %} + {% when 'a' %} + a + {% when 'b' %} + b + {% else %} + other +{% endcase %} +``` + +**{% else %} (used inside if, unless, case, or for)** +```liquid +{% if product.available %} + In stock +{% else %} + Sold out +{% endif %} +``` +_or inside a for loop:_ +```liquid +{% for item in collection.products %} + {{ item.title }} +{% else %} + No products found. +{% endfor %} +``` + +#### Variables and variable tags + +```liquid +{% assign my_variable = 'value' %} + +{% capture my_variable %} + Contents of variable +{% endcapture %} + +{% increment counter %} +{% decrement counter %} +``` + +### Liquid filters + +You can chain filters in Liquid, passing the result of one filter as the input to the next. + +See these filters: + +- `upcase`: `{{ string | upcase }}` returns a **string** +- `split`: `{{ string | split: string }}` returns an **array** (as we may notice in the docs, `split` receives a string as its argument) +- `last`: `{{ array | last }}` returns **untyped** + +Each filter can pass its return value to the next filter as long as the types match. + +For example, `upcase` returns a string, which is suitable input for `split`, which then produces an array for `last` to use. + +Here's how the filters are executed step by step to eventually return `"WORLD"`: + +```liquid +{{ "hello world" | upcase | split: " " | last }} +``` + +- First, `"hello world"` is converted to uppercase: `"HELLO WORLD"`, which is a string +- Next, `split` can act on strings, so it splits the value by space into an array: `["HELLO", "WORLD"]` +- Finally, the `last` filter work with array, so `"WORLD"` is returned + +#### Array +- `compact`: `{{ array | compact }}` returns `array` +- `concat`: `{{ array | concat: array }}` returns `array` +- `find`: `{{ array | find: string, string }}` returns `untyped` +- `find_index`: `{{ array | find_index: string, string }}` returns `number` +- `first`: `{{ array | first }}` returns `untyped` +- `has`: `{{ array | has: string, string }}` returns `boolean` +- `join`: `{{ array | join }}` returns `string` +- `last`: `{{ array | last }}` returns `untyped` +- `map`: `{{ array | map: string }}` returns `array` +- `reject`: `{{ array | reject: string, string }}` returns `array` +- `reverse`: `{{ array | reverse }}` returns `array` +- `size`: `{{ variable | size }}` returns `number` +- `sort`: `{{ array | sort }}` returns `array` +- `sort_natural`: `{{ array | sort_natural }}` returns `array` +- `sum`: `{{ array | sum }}` returns `number` +- `uniq`: `{{ array | uniq }}` returns `array` +- `where`: `{{ array | where: string, string }}` returns `array` + +#### Cart +- `item_count_for_variant`: `{{ cart | item_count_for_variant: {variant_id} }}` returns `number` +- `line_items_for`: `{{ cart | line_items_for: object }}` returns `array` + +#### Collection +- `link_to_type`: `{{ string | link_to_type }}` returns `string` +- `link_to_vendor`: `{{ string | link_to_vendor }}` returns `string` +- `sort_by`: `{{ string | sort_by: string }}` returns `string` +- `url_for_type`: `{{ string | url_for_type }}` returns `string` +- `url_for_vendor`: `{{ string | url_for_vendor }}` returns `string` +- `within`: `{{ string | within: collection }}` returns `string` +- `highlight_active_tag`: `{{ string | highlight_active_tag }}` returns `string` + +#### Color +- `brightness_difference`: `{{ string | brightness_difference: string }}` returns `number` +- `color_brightness`: `{{ string | color_brightness }}` returns `number` +- `color_contrast`: `{{ string | color_contrast: string }}` returns `number` +- `color_darken`: `{{ string | color_darken: number }}` returns `string` +- `color_desaturate`: `{{ string | color_desaturate: number }}` returns `string` +- `color_difference`: `{{ string | color_difference: string }}` returns `number` +- `color_extract`: `{{ string | color_extract: string }}` returns `number` +- `color_lighten`: `{{ string | color_lighten: number }}` returns `string` +- `color_mix`: `{{ string | color_mix: string, number }}` returns `string` +- `color_modify`: `{{ string | color_modify: string, number }}` returns `string` +- `color_saturate`: `{{ string | color_saturate: number }}` returns `string` +- `color_to_hex`: `{{ string | color_to_hex }}` returns `string` +- `color_to_hsl`: `{{ string | color_to_hsl }}` returns `string` +- `color_to_oklch`: `{{ string | color_to_oklch }}` returns `string` +- `color_to_rgb`: `{{ string | color_to_rgb }}` returns `string` +- `hex_to_rgba`: `{{ string | hex_to_rgba }}` returns `string` + +#### Customer +- `customer_login_link`: `{{ string | customer_login_link }}` returns `string` +- `customer_logout_link`: `{{ string | customer_logout_link }}` returns `string` +- `customer_register_link`: `{{ string | customer_register_link }}` returns `string` +- `avatar`: `{{ customer | avatar }}` returns `string` +- `login_button`: `{{ shop | login_button }}` returns `string` + +#### Date +- `date`: `{{ date | date: string }}` returns `string` + +#### Default +- `default_errors`: `{{ string | default_errors }}` returns `string` +- `default`: `{{ variable | default: variable }}` returns `untyped` +- `default_pagination`: `{{ paginate | default_pagination }}` returns `string` + +#### Font +- `font_face`: `{{ font | font_face }}` returns `string` +- `font_modify`: `{{ font | font_modify: string, string }}` returns `font` +- `font_url`: `{{ font | font_url }}` returns `string` + +#### Format +- `date`: `{{ string | date: string }}` returns `string` +- `json`: `{{ variable | json }}` returns `string` +- `structured_data`: `{{ variable | structured_data }}` returns `string` +- `unit_price_with_measurement`: `{{ number | unit_price_with_measurement: unit_price_measurement }}` returns `string` +- `weight_with_unit`: `{{ number | weight_with_unit }}` returns `string` + +#### Hosted_file +- `asset_img_url`: `{{ string | asset_img_url }}` returns `string` +- `asset_url`: `{{ string | asset_url }}` returns `string` +- `file_img_url`: `{{ string | file_img_url }}` returns `string` +- `file_url`: `{{ string | file_url }}` returns `string` +- `global_asset_url`: `{{ string | global_asset_url }}` returns `string` +- `shopify_asset_url`: `{{ string | shopify_asset_url }}` returns `string` + +#### Html +- `class_list`: `{{ settings.layout | class_list }}` returns `string` +- `time_tag`: `{{ string | time_tag: string }}` returns `string` +- `inline_asset_content`: `{{ asset_name | inline_asset_content }}` returns `string` +- `highlight`: `{{ string | highlight: string }}` returns `string` +- `link_to`: `{{ string | link_to: string }}` returns `string` +- `placeholder_svg_tag`: `{{ string | placeholder_svg_tag }}` returns `string` +- `preload_tag`: `{{ string | preload_tag: as: string }}` returns `string` +- `script_tag`: `{{ string | script_tag }}` returns `string` +- `stylesheet_tag`: `{{ string | stylesheet_tag }}` returns `string` + +#### Localization +- `currency_selector`: `{{ form | currency_selector }}` returns `string` +- `translate`: `{{ string | t }}` returns `string` +- `format_address`: `{{ address | format_address }}` returns `string` + +#### Math +- `abs`: `{{ number | abs }}` returns `number` +- `at_least`: `{{ number | at_least }}` returns `number` +- `at_most`: `{{ number | at_most }}` returns `number` +- `ceil`: `{{ number | ceil }}` returns `number` +- `divided_by`: `{{ number | divided_by: number }}` returns `number` +- `floor`: `{{ number | floor }}` returns `number` +- `minus`: `{{ number | minus: number }}` returns `number` +- `modulo`: `{{ number | modulo: number }}` returns `number` +- `plus`: `{{ number | plus: number }}` returns `number` +- `round`: `{{ number | round }}` returns `number` +- `times`: `{{ number | times: number }}` returns `number` + +#### Media +- `external_video_tag`: `{{ variable | external_video_tag }}` returns `string` +- `external_video_url`: `{{ media | external_video_url: attribute: string }}` returns `string` +- `image_tag`: `{{ string | image_tag }}` returns `string` +- `media_tag`: `{{ media | media_tag }}` returns `string` +- `model_viewer_tag`: `{{ media | model_viewer_tag }}` returns `string` +- `video_tag`: `{{ media | video_tag }}` returns `string` +- `article_img_url`: `{{ variable | article_img_url }}` returns `string` +- `collection_img_url`: `{{ variable | collection_img_url }}` returns `string` +- `image_url`: `{{ variable | image_url: width: number, height: number }}` returns `string` +- `img_tag`: `{{ string | img_tag }}` returns `string` +- `img_url`: `{{ variable | img_url }}` returns `string` +- `product_img_url`: `{{ variable | product_img_url }}` returns `string` + +#### Metafield +- `metafield_tag`: `{{ metafield | metafield_tag }}` returns `string` +- `metafield_text`: `{{ metafield | metafield_text }}` returns `string` + +#### Money +- `money`: `{{ number | money }}` returns `string` +- `money_with_currency`: `{{ number | money_with_currency }}` returns `string` +- `money_without_currency`: `{{ number | money_without_currency }}` returns `string` +- `money_without_trailing_zeros`: `{{ number | money_without_trailing_zeros }}` returns `string` + +#### Payment +- `payment_button`: `{{ form | payment_button }}` returns `string` +- `payment_terms`: `{{ form | payment_terms }}` returns `string` +- `payment_type_img_url`: `{{ string | payment_type_img_url }}` returns `string` +- `payment_type_svg_tag`: `{{ string | payment_type_svg_tag }}` returns `string` + +#### String +- `hmac_sha1`: `{{ string | hmac_sha1: string }}` returns `string` +- `hmac_sha256`: `{{ string | hmac_sha256: string }}` returns `string` +- `md5`: `{{ string | md5 }}` returns `string` +- `sha1`: `{{ string | sha1: string }}` returns `string` +- `sha256`: `{{ string | sha256: string }}` returns `string` +- `append`: `{{ string | append: string }}` returns `string` +- `base64_decode`: `{{ string | base64_decode }}` returns `string` +- `base64_encode`: `{{ string | base64_encode }}` returns `string` +- `base64_url_safe_decode`: `{{ string | base64_url_safe_decode }}` returns `string` +- `base64_url_safe_encode`: `{{ string | base64_url_safe_encode }}` returns `string` +- `capitalize`: `{{ string | capitalize }}` returns `string` +- `downcase`: `{{ string | downcase }}` returns `string` +- `escape`: `{{ string | escape }}` returns `string` +- `escape_once`: `{{ string | escape_once }}` returns `string` +- `lstrip`: `{{ string | lstrip }}` returns `string` +- `newline_to_br`: `{{ string | newline_to_br }}` returns `string` +- `prepend`: `{{ string | prepend: string }}` returns `string` +- `remove`: `{{ string | remove: string }}` returns `string` +- `remove_first`: `{{ string | remove_first: string }}` returns `string` +- `remove_last`: `{{ string | remove_last: string }}` returns `string` +- `replace`: `{{ string | replace: string, string }}` returns `string` +- `replace_first`: `{{ string | replace_first: string, string }}` returns `string` +- `replace_last`: `{{ string | replace_last: string, string }}` returns `string` +- `rstrip`: `{{ string | rstrip }}` returns `string` +- `slice`: `{{ string | slice }}` returns `string` +- `split`: `{{ string | split: string }}` returns `array` +- `strip`: `{{ string | strip }}` returns `string` +- `strip_html`: `{{ string | strip_html }}` returns `string` +- `strip_newlines`: `{{ string | strip_newlines }}` returns `string` +- `truncate`: `{{ string | truncate: number }}` returns `string` +- `truncatewords`: `{{ string | truncatewords: number }}` returns `string` +- `upcase`: `{{ string | upcase }}` returns `string` +- `url_decode`: `{{ string | url_decode }}` returns `string` +- `url_encode`: `{{ string | url_encode }}` returns `string` +- `camelize`: `{{ string | camelize }}` returns `string` +- `handleize`: `{{ string | handleize }}` returns `string` +- `url_escape`: `{{ string | url_escape }}` returns `string` +- `url_param_escape`: `{{ string | url_param_escape }}` returns `string` +- `pluralize`: `{{ number | pluralize: string, string }}` returns `string` + +#### Tag +- `link_to_add_tag`: `{{ string | link_to_add_tag }}` returns `string` +- `link_to_remove_tag`: `{{ string | link_to_remove_tag }}` returns `string` +- `link_to_tag`: `{{ string | link_to_tag }}` returns `string` + +### Liquid objects + +#### Global objects +- `collections` +- `pages` +- `all_products` +- `articles` +- `blogs` +- `cart` +- `closest` +- `content_for_header` +- `customer` +- `images` +- `linklists` +- `localization` +- `metaobjects` +- `request` +- `routes` +- `shop` +- `theme` +- `settings` +- `template` +- `additional_checkout_buttons` +- `all_country_option_tags` +- `canonical_url` +- `content_for_additional_checkout_buttons` +- `content_for_index` +- `content_for_layout` +- `country_option_tags` +- `current_page` +- `handle` +- `page_description` +- `page_image` +- `page_title` +- `powered_by_link` +- `scripts` + +#### `/article` page +- `article` +- `blog` + +#### `/blog` page +- `blog` +- `current_tags` + +#### `/cart` page +- `cart` + +#### `/checkout` page +- `checkout` + +#### `/collection` page +- `collection` +- `current_tags` + +#### `/customers/account` page +- `customer` + +#### `/customers/addresses` page +- `customer` + +#### `/customers/order` page +- `customer` +- `order` + +#### `/gift_card.liquid` page +- `gift_card` +- `recipient` + +#### `/metaobject` page +- `metaobject` + +#### `/page` page +- `page` + +#### `/product` page +- `product` + +#### `/robots.txt.liquid` page +- `robots` + +#### `/search` page +- `search` +### Liquid tags + + +#### content_for +The `content_for` tag requires a type parameter to differentiate between rendering a number of theme blocks (`'blocks'`) and a single static block (`'block'`). + + +Syntax: +``` +{% content_for 'blocks' %} +{% content_for 'block', type: "slide", id: "slide-1" %} +``` + +#### form +Because there are many different form types available in Shopify themes, the `form` tag requires a type. Depending on the +form type, an additional parameter might be required. You can specify the following form types: + +- [`activate_customer_password`](https://shopify.dev/docs/api/liquid/tags/form#form-activate_customer_password) +- [`cart`](https://shopify.dev/docs/api/liquid/tags/form#form-cart) +- [`contact`](https://shopify.dev/docs/api/liquid/tags/form#form-contact) +- [`create_customer`](https://shopify.dev/docs/api/liquid/tags/form#form-create_customer) +- [`currency`](https://shopify.dev/docs/api/liquid/tags/form#form-currency) +- [`customer`](https://shopify.dev/docs/api/liquid/tags/form#form-customer) +- [`customer_address`](https://shopify.dev/docs/api/liquid/tags/form#form-customer_address) +- [`customer_login`](https://shopify.dev/docs/api/liquid/tags/form#form-customer_login) +- [`guest_login`](https://shopify.dev/docs/api/liquid/tags/form#form-guest_login) +- [`localization`](https://shopify.dev/docs/api/liquid/tags/form#form-localization) +- [`new_comment`](https://shopify.dev/docs/api/liquid/tags/form#form-new_comment) +- [`product`](https://shopify.dev/docs/api/liquid/tags/form#form-product) +- [`recover_customer_password`](https://shopify.dev/docs/api/liquid/tags/form#form-recover_customer_password) +- [`reset_customer_password`](https://shopify.dev/docs/api/liquid/tags/form#form-reset_customer_password) +- [`storefront_password`](https://shopify.dev/docs/api/liquid/tags/form#form-storefront_password) + + +Syntax: +``` +{% form 'form_type' %} + content +{% endform %} +``` + +#### layout + +Syntax: +``` +{% layout name %} +``` + +#### assign +You can create variables of any [basic type](https://shopify.dev/docs/api/liquid/basics#types), [object](https://shopify.dev/docs/api/liquid/objects), or object property. + + +Syntax: +``` +{% assign variable_name = value %} +``` + +#### break + +Syntax: +``` +{% break %} +``` + +#### capture +You can create complex strings with Liquid logic and variables. + + +Syntax: +``` +{% capture variable %} + value +{% endcapture %} +``` + +#### case + +Syntax: +``` +{% case variable %} + {% when first_value %} + first_expression + {% when second_value %} + second_expression + {% else %} + third_expression +{% endcase %} +``` + +#### comment +Any text inside `comment` tags won't be output, and any Liquid code will be parsed, but not executed. + + +Syntax: +``` +{% comment %} + content +{% endcomment %} +``` + +#### continue + +Syntax: +``` +{% continue %} +``` + +#### cycle +The `cycle` tag must be used inside a `for` loop. + +> Tip: +> Use the `cycle` tag to output text in a predictable pattern. For example, to apply odd/even classes to rows in a table. + + +Syntax: +``` +{% cycle string, string, ... %} +``` + +#### decrement +Variables that are declared with `decrement` are unique to the [layout](/themes/architecture/layouts), [template](/themes/architecture/templates), +or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across +[snippets](/themes/architecture/snippets) included in the file. + +Similarly, variables that are created with `decrement` are independent from those created with [`assign`](https://shopify.dev/docs/api/liquid/tags/assign) +and [`capture`](https://shopify.dev/docs/api/liquid/tags/capture). However, `decrement` and [`increment`](https://shopify.dev/docs/api/liquid/tags/increment) share +variables. + + +Syntax: +``` +{% decrement variable_name %} +``` + +#### doc +The `doc` tag allows developers to include documentation within Liquid +templates. Any content inside `doc` tags is not rendered or outputted. +Liquid code inside will be parsed but not executed. This facilitates +tooling support for features like code completion, linting, and inline +documentation. + +For detailed documentation syntax and examples, see the +[`LiquidDoc` reference](https://shopify.dev/docs/storefronts/themes/tools/liquid-doc). + + +Syntax: +``` +{% doc %} + Renders a message. + + @param {string} foo - A string value. + @param {string} [bar] - An optional string value. + + @example + {% render 'message', foo: 'Hello', bar: 'World' %} +{% enddoc %} +{{ foo }}, {{ bar }}! +``` + +#### echo +Using the `echo` tag is the same as wrapping an expression in curly brackets (`{{` and `}}`). However, unlike the curly +bracket method, you can use the `echo` tag inside [`liquid` tags](https://shopify.dev/docs/api/liquid/tags/liquid). + +> Tip: +> You can use [filters](https://shopify.dev/docs/api/liquid/filters) on expressions inside `echo` tags. + + +Syntax: +``` +{% liquid + echo expression +%} +``` + +#### for +You can do a maximum of 50 iterations with a `for` loop. If you need to iterate over more than 50 items, then use the +[`paginate` tag](https://shopify.dev/docs/api/liquid/tags/paginate) to split the items over multiple pages. + +> Tip: +> Every `for` loop has an associated [`forloop` object](https://shopify.dev/docs/api/liquid/objects/forloop) with information about the loop. + + +Syntax: +``` +{% for variable in array %} + expression +{% endfor %} +``` + +#### if + +Syntax: +``` +{% if condition %} + expression +{% endif %} +``` + +#### increment +Variables that are declared with `increment` are unique to the [layout](/themes/architecture/layouts), [template](/themes/architecture/templates), +or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across +[snippets](/themes/architecture/snippets) included in the file. + +Similarly, variables that are created with `increment` are independent from those created with [`assign`](https://shopify.dev/docs/api/liquid/tags/assign) +and [`capture`](https://shopify.dev/docs/api/liquid/tags/capture). However, `increment` and [`decrement`](https://shopify.dev/docs/api/liquid/tags/decrement) share +variables. + + +Syntax: +``` +{% increment variable_name %} +``` + +#### raw + +Syntax: +``` +{% raw %} + expression +{% endraw %} +``` + +#### render +Inside snippets and app blocks, you can't directly access variables that are [created](https://shopify.dev/docs/api/liquid/tags/variable-tags) outside +of the snippet or app block. However, you can [specify variables as parameters](https://shopify.dev/docs/api/liquid/tags/render#render-passing-variables-to-a-snippet) +to pass outside variables to snippets. + +While you can't directly access created variables, you can access global objects, as well as any objects that are +directly accessible outside the snippet or app block. For example, a snippet or app block inside the [product template](/themes/architecture/templates/product) +can access the [`product` object](https://shopify.dev/docs/api/liquid/objects/product), and a snippet or app block inside a [section](/themes/architecture/sections) +can access the [`section` object](https://shopify.dev/docs/api/liquid/objects/section). + +Outside a snippet or app block, you can't access variables created inside the snippet or app block. + +> Note: +> When you render a snippet using the `render` tag, you can't use the [`include` tag](https://shopify.dev/docs/api/liquid/tags/include) +> inside the snippet. + + +Syntax: +``` +{% render 'filename' %} +``` + +#### tablerow +The `tablerow` tag must be wrapped in HTML `` and `
` tags. + +> Tip: +> Every `tablerow` loop has an associated [`tablerowloop` object](https://shopify.dev/docs/api/liquid/objects/tablerowloop) with information about the loop. + + +Syntax: +``` +{% tablerow variable in array %} + expression +{% endtablerow %} +``` + +#### unless +> Tip: +> Similar to the [`if` tag](https://shopify.dev/docs/api/liquid/tags/if), you can use `elsif` to add more conditions to an `unless` tag. + + +Syntax: +``` +{% unless condition %} + expression +{% endunless %} +``` + +#### paginate +Because [`for` loops](https://shopify.dev/docs/api/liquid/tags/for) are limited to 50 iterations per page, you need to use the `paginate` tag to +iterate over an array that has more than 50 items. The following arrays can be paginated: + +- [`all_products`](https://shopify.dev/docs/api/liquid/objects/all_products) +- [`article.comments`](https://shopify.dev/docs/api/liquid/objects/article#article-comments) +- [`blog.articles`](https://shopify.dev/docs/api/liquid/objects/blog#blog-articles) +- [`collections`](https://shopify.dev/docs/api/liquid/objects/collections) +- [`collection.products`](https://shopify.dev/docs/api/liquid/objects/collection#collection-products) +- [`customer.addresses`](https://shopify.dev/docs/api/liquid/objects/customer#customer-addresses) +- [`customer.orders`](https://shopify.dev/docs/api/liquid/objects/customer#customer-orders) +- [`pages`](https://shopify.dev/docs/api/liquid/objects/pages) +- [`product.variants`](https://shopify.dev/docs/api/liquid/objects/product#variants) +- [`search.results`](https://shopify.dev/docs/api/liquid/objects/search#search-results) +- [`collection_list` settings](/themes/architecture/settings/input-settings#collection_list) +- [`product_list` settings](/themes/architecture/settings/input-settings#product_list) + +Within the `paginate` tag, you have access to the [`paginate` object](https://shopify.dev/docs/api/liquid/objects/paginate). You can use this +object, or the [`default_pagination` filter](https://shopify.dev/docs/api/liquid/filters/default_pagination), to build page navigation. + + +Syntax: +``` +{% paginate array by page_size %} + {% for item in array %} + forloop_content + {% endfor %} +{% endpaginate %} + +The `paginate` tag allows the user to paginate to the 25,000th item in the array and no further. To reach items further in +the array the array should be filtered further before paginating. See +[Pagination Limits](/themes/best-practices/performance/platform#pagination-limits) for more information. +``` + +#### javascript +Each section, block or snippet can have only one `{% javascript %}` tag. + +To learn more about how JavaScript that's defined between the `javascript` tags is loaded and run, refer to the documentation for [javascript tags](/storefronts/themes/best-practices/javascript-and-stylesheet-tags#javascript). +> Caution: +> Liquid isn't rendered inside of `{% javascript %}` tags. Including Liquid code can cause syntax errors. + + +Syntax: +``` +{% javascript %} + javascript_code +{% endjavascript %} +``` + +#### section +Rendering a section with the `section` tag renders a section statically. To learn more about sections and how to use +them in your theme, refer to [Render a section](/themes/architecture/sections#render-a-section). + + +Syntax: +``` +{% section 'name' %} +``` + +#### stylesheet +Each section, block or snippet can have only one `{% stylesheet %}` tag. + +To learn more about how CSS that's defined between the `stylesheet` tags is loaded and run, refer to the documentation for [stylesheet tags](/storefronts/themes/best-practices/javascript-and-stylesheet-tags#stylesheet). +> Caution: +> Liquid isn't rendered inside of `{% stylesheet %}` tags. Including Liquid code can cause syntax errors. + + +Syntax: +``` +{% stylesheet %} + css_styles +{% endstylesheet %} +``` + +#### sections +Use this tag to render section groups as part of the theme's [layout](/themes/architecture/layouts) content. Place the `sections` tag where you want to render it in the layout. + +To learn more about section groups and how to use them in your theme, refer to [Section groups](/themes/architecture/section-groups#usage). + + +Syntax: +``` +{% sections 'name' %} +``` + +#### style +> Note: +> If you reference [color settings](/themes/architecture/settings/input-settings#color) inside `style` tags, then +> the associated CSS rules will update as the setting is changed in the theme editor, without a page refresh. + + +Syntax: +``` +{% style %} + CSS_rules +{% endstyle %} +``` + +#### else +You can use the `else` tag with the following tags: + +- [`case`](https://shopify.dev/docs/api/liquid/tags/case) +- [`if`](https://shopify.dev/docs/api/liquid/tags/if) +- [`unless`](https://shopify.dev/docs/api/liquid/tags/unless) + + +Syntax: +``` +{% else %} + expression +``` + +#### else + +Syntax: +``` +{% for variable in array %} + first_expression +{% else %} + second_expression +{% endfor %} +``` + +#### liquid +Because the tags don't have delimeters, each tag needs to be on its own line. + +> Tip: +> Use the [`echo` tag](https://shopify.dev/docs/api/liquid/tags/echo) to output an expression inside `liquid` tags. + + +Syntax: +``` +{% liquid + expression +%} +``` + + +## Translation development standards + +### Translation requirements + +- **Every user-facing text** must use translation filters. +- **Update `locales/en.default.json`** with all new keys. +- **Use descriptive, hierarchical keys** for organization. +- **Only add English text**; translators handle other languages. + +### Translation filter usage + +**Use `{{ 'key' | t }}` for all text:** + +```liquid + +

{{ 'sections.featured_collection.title' | t }}

+

{{ 'sections.featured_collection.description' | t }}

+ + + +

Featured Collection

+

Check out our best products

+ +``` + +### Translation with variables + +**Use variables for interpolation:** + +```liquid + +

{{ 'products.price_range' | t: min: product.price_min | money, max: product.price_max | money }}

+

{{ 'general.pagination.page' | t: page: paginate.current_page, pages: paginate.pages }}

+``` + +**Corresponding keys in locale files:** + +```json +{ + "products": { + "price_range": "From {{ min }} to {{ max }}" + }, + "general": { + "pagination": { + "page": "Page {{ page }} of {{ pages }}" + } + } +} +``` + +### Best practices + +**Content guidelines:** +- Write clear, concise text. +- **Use sentence case** for all user-facing text, including titles, headings, and button labels (capitalize only the first word and proper nouns; e.g., `Featured collection` → `Featured collection`, not `Featured Collection`). +- Be consistent with terminology. +- Consider character limits for UI elements. + +**Variable usage:** +- Use interpolation rather than appending strings together. +- Prioritize clarity over brevity for variable naming. +- Escape variables unless they output HTML: `{{ variable | escape }}`. + + +## Localization standards + +Auto-attached when working in `locales/` directory. + +### File structure + +``` +locales/ +├── en.default.json # English (required) +├── en.default.schema.json # English (required) +├── es.json # Spanish +├── est.schema.json # Spanish +├── fr.json # French +├── frt.schema.json # French +└── pt-BR.json # Portuguese +└── pt-BR..schema.json # Portuguese +``` + +#### Locale files + +Locale files are JSON files containing translations for all the text strings used throughout a Shopify theme and its editor. They let merchants easily update and localize repeated words and phrases, making it possible to translate store content and settings into multiple languages for international customers. These files provide a centralized way to manage and edit translations. + +**Example:** +```json +{ + "general": { + "cart": "Cart", + "checkout": "Checkout" + }, + "products": { + "add_to_cart": "Add to Cart" + } +} +``` + +#### Schema locale files + +Schema locale files, saved with a .schema.json extension, store translation strings specifically for theme editor setting schemas. They follow a structured organization—category, group, and description—to give context to each translation, enabling accurate localization of editor content. Schema locale files must use the IETF language tag format in their naming, such as en-GB.schema.json for British English or fr-CA.schema.json for Canadian French. + +**Example:** +```json +{ + "products": { + "card": { + "description": "Product card layout" + } + } +} +``` + +### Key organization + +**Hierarchical structure:** +```json +{ + "general": { + "meta": { + "title": "{{ shop_name }}", + "description": "{{ shop_description }}" + }, + "accessibility": { + "skip_to_content": "Skip to content", + "close": "Close" + } + }, + "products": { + "add_to_cart": "Add to cart", + "quick_view": "Quick view", + "price": { + "regular": "Regular price", + "sale": "Sale price", + "unit": "Unit price" + } + } +} +``` +**Usage** +```liquid +{{ 'general.meta.title' | t: shop_name: shop.name }} +{{ 'general.meta.description' | t: shop_description: shop.description }} +``` + +### Translation guidelines + +**Key naming:** +- Use descriptive, hierarchical keys +- Maximum 3 levels deep +- Use snake_case for key names +- Group related translations + +**Content rules:** +- Keep text concise for UI elements +- Use variables for dynamic content +- Consider character limits +- Maintain consistent terminology + +## Examples per kind of asset + +### `snippet` + +```liquid +{% doc %} + Renders a responsive image that might be wrapped in a link. + + When `width`, `height` and `crop` are provided, the image will be rendered + with a fixed aspect ratio. + + Serves as an example of how to use the `image_url` filter and `image_tag` filter + as well as how you can use LiquidDoc to document your code. + + @param {image} image - The image to be rendered + @param {string} [url] - An optional destination URL for the image + @param {string} [css_class] - Optional class to be added to the image wrapper + @param {number} [width] - The highest resolution width of the image to be rendered + @param {number} [height] - The highest resolution height of the image to be rendered + @param {string} [crop] - The crop position of the image + + @example + {% render 'image', image: product.featured_image %} + {% render 'image', image: product.featured_image, url: product.url %} + {% render 'image', + css_class: 'product__image', + image: product.featured_image, + url: product.url, + width: 1200, + height: 800, + crop: 'center', + %} +{% enddoc %} + +{% liquid + unless height + assign width = width | default: image.width + endunless + + if url + assign wrapper = 'a' + else + assign wrapper = 'div' + endif +%} + +<{{ wrapper }} + class="image {{ css_class }}" + {% if url %} + href="{{ url }}" + {% endif %} +> + {{ image | image_url: width: width, height: height, crop: crop | image_tag }} + + +{% stylesheet %} + .image { + display: block; + position: relative; + overflow: hidden; + width: 100%; + height: auto; + } + + .image > img { + width: 100%; + height: auto; + } +{% endstylesheet %} + +{% javascript %} + function doSomething() { + // example + } + doSomething() +{% endjavascript %} + +``` + +### `block` + +#### Text + +```liquid +{% doc %} + Renders a text block. + + @example + {% content_for 'block', type: 'text', id: 'text' %} +{% enddoc %} + +
+ {{ block.settings.text }} +
+ +{% stylesheet %} + .text { + text-align: var(--text-align); + } + .text--title { + font-size: 2rem; + font-weight: 700; + } + .text--subtitle { + font-size: 1.5rem; + } +{% endstylesheet %} + +{% schema %} +{ + "name": "t:general.text", + "settings": [ + { + "type": "text", + "id": "text", + "label": "t:labels.text", + "default": "Text" + }, + { + "type": "select", + "id": "text_style", + "label": "t:labels.text_style", + "options": [ + { "value": "text--title", "label": "t:options.text_style.title" }, + { "value": "text--subtitle", "label": "t:options.text_style.subtitle" }, + { "value": "text--normal", "label": "t:options.text_style.normal" } + ], + "default": "text--title" + }, + { + "type": "text_alignment", + "id": "alignment", + "label": "t:labels.alignment", + "default": "left" + } + ], + "presets": [{ "name": "t:general.text" }] +} +{% endschema %} +``` + +#### Group + +```liquid +{% doc %} + Renders a group of blocks with configurable layout direction, gap and + alignment. + + All settings apply to only one dimension to reduce configuration complexity. + + This component is a wrapper concerned only with rendering its children in + the specified layout direction with appropriate padding and alignment. + + @example + {% content_for 'block', type: 'group', id: 'group' %} +{% enddoc %} + +
+ {% content_for 'blocks' %} +
+ +{% stylesheet %} + .group { + display: flex; + flex-wrap: nowrap; + overflow: hidden; + width: 100%; + } + .group--horizontal { + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 0 var(--padding); + } + .group--vertical { + flex-direction: column; + align-items: var(--alignment); + padding: var(--padding) 0; + } +{% endstylesheet %} + +{% schema %} +{ + "name": "t:general.group", + "blocks": [{ "type": "@theme" }], + "settings": [ + { + "type": "select", + "id": "layout_direction", + "label": "t:labels.layout_direction", + "default": "group--vertical", + "options": [ + { "value": "group--horizontal", "label": "t:options.direction.horizontal" }, + { "value": "group--vertical", "label": "t:options.direction.vertical" } + ] + }, + { + "visible_if": "{{ block.settings.layout_direction == 'group--vertical' }}", + "type": "select", + "id": "alignment", + "label": "t:labels.alignment", + "default": "flex-start", + "options": [ + { "value": "flex-start", "label": "t:options.alignment.left" }, + { "value": "center", "label": "t:options.alignment.center" }, + { "value": "flex-end", "label": "t:options.alignment.right" } + ] + }, + { + "type": "range", + "id": "padding", + "label": "t:labels.padding", + "default": 0, + "min": 0, + "max": 200, + "step": 2, + "unit": "px" + } + ], + "presets": [ + { + "name": "t:general.column", + "category": "t:general.layout", + "settings": { + "layout_direction": "group--vertical", + "alignment": "flex-start", + "padding": 0 + } + }, + { + "name": "t:general.row", + "category": "t:general.layout", + "settings": { + "layout_direction": "group--horizontal", + "padding": 0 + } + } + ] +} +{% endschema %} +``` + +### `section` + +```liquid +
+ {% if section.settings.background_image %} +
+ {{ section.settings.background_image | image_url: width: 2000 | image_tag }} +
+ {% endif %} + +
+ {% content_for 'blocks' %} +
+
+ +{% stylesheet %} + .example-section { + position: relative; + overflow: hidden; + width: 100%; + } + .example-section__background { + position: absolute; + width: 100%; + height: 100%; + z-index: -1; + overflow: hidden; + } + .example-section__background img { + position: absolute; + width: 100%; + height: auto; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + .example-section__content { + display: grid; + grid-template-columns: var(--content-grid); + } + + .example-section__content > * { + grid-column: 2; + } +{% endstylesheet %} + +{% schema %} +{ + "name": "t:general.custom_section", + "blocks": [{ "type": "@theme" }], + "settings": [ + { + "type": "image_picker", + "id": "background_image", + "label": "t:labels.background" + } + ], + "presets": [ + { + "name": "t:general.custom_section" + } + ] +} +{% endschema %} +``` + diff --git a/ai/cursor/rules/assets.mdc b/ai/cursor/rules/assets.mdc new file mode 100644 index 0000000..f39cb7c --- /dev/null +++ b/ai/cursor/rules/assets.mdc @@ -0,0 +1,15 @@ +--- +description: Static files (css, js, and images) for theme templates +globs: assets/* +alwaysApply: false +--- +# Assets + +The `assets` directory contains static files—such as CSS, JavaScript, images, and icons—that need to be referenced in `.liquid` files or included on your theme. + +- **No subdirectories:** this directory is flat and cannot contain folders. +- **Usage:** reference files with the [`asset_url`](mdc:https://shopify.dev/docs/api/liquid/filters/asset_url) filter. +- **SVG icons:** use the [`inline_asset_content`](mdc:https://shopify.dev/docs/api/liquid/filters/inline_asset_content) filter to include SVGs inline. +- **Critical static files:** Only include assets (like `critical.css` or global JS) needed on every page. For page or section specific assets, prefer the `{% stylesheet %}` and `{% javascript %}` tags. + +Keep all images, scripts, stylesheets, and icons required by your theme in this directory. diff --git a/ai/cursor/rules/blocks.mdc b/ai/cursor/rules/blocks.mdc new file mode 100644 index 0000000..6705132 --- /dev/null +++ b/ai/cursor/rules/blocks.mdc @@ -0,0 +1,339 @@ +--- +description: Development standards and best practices for creating, configuring, and styling theme blocks, including static and nested blocks, schema configuration, CSS, and usage examples +globs: blocks/*.liquid +alwaysApply: false +--- +# Theme blocks development standards + +Follow [Shopify's theme blocks documentation](mdc:https:/shopify.dev/docs/storefronts/themes/architecture/blocks/theme-blocks/quick-start?framework=liquid). + +## 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](mdc:https:/shopify.dev/docs/storefronts/themes/architecture/blocks/theme-blocks/static-blocks#statically-vs-dynamically-rendered-theme-blocks) by theme developers + +Blocks render in the editor and storefront when they are referenced in [template files](mdc:.cursor/rules/templates.mdc). + +### Basic block structure +```liquid +{% doc %} + Block description and usage examples + + @example + {% content_for 'block', type: 'block-name', id: 'unique-id' %} +{% enddoc %} + +
+ +
+ +{% 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](mdc:@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:** +```liquid +{% content_for 'block', type: 'text', id: 'header-announcement' %} +``` + +**Example: Product template with mixed static and dynamic blocks** +```liquid + +
+ {% comment %} Static breadcrumb block {% endcomment %} + {% content_for 'block', type: 'breadcrumb', id: 'product-breadcrumb' %} + +
+
+ {% comment %} Static product gallery block {% endcomment %} + {% content_for 'block', type: 'product-gallery', id: 'main-gallery', settings: { + enable_zoom: true, + thumbnails_position: "bottom" + } %} +
+ +
+ {% 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 %} +
+ {% content_for 'blocks' %} +
+
+
+ + {% comment %} Static related products block {% endcomment %} + {% content_for 'block', type: 'related-products', id: 'related-products', settings: { + heading: "You might also like", + limit: 4 + } %} +
+``` + +**Key points about static blocks:** +- They have a fixed `id` that 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.mdc](mdc:.cursor/rules/schemas.mdc) for rules on schemas + +### Advanced schema features + +#### Exclude wrapper + +```json +{ + "tag": null // No wrapper - must include {{ block.shopify_attributes }} for proper editor function +} +``` + +## Block implementation patterns + +### Accessing block data + +**Block settings:** +```liquid +{{ block.settings.text }} +{{ block.settings.heading | escape }} +{{ block.settings.image | image_url: width: 800 }} +``` + +**Block properties:** +```liquid +{{ block.id }} // Unique block identifier +{{ block.type }} // Block type name +{{ block.shopify_attributes }} // Required for theme editor +``` + +**Section context:** +```liquid +{{ section.id }} // Parent section ID +{{ section.settings.heading | escape }} +{{ section.settings.image | image_url: width: 800 }} +``` + +## Nested blocks implementation + +### Rendering nested blocks +```liquid +
+

{{ block.settings.heading | escape }}

+ +
+ {% content_for 'blocks' %} +
+
+``` + +### Nesting with layout control +```liquid +
+ {% content_for 'blocks' %} +
+``` + +### Presets with nested blocks +```json +{ + "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.mdc](mdc:.cursor/rules/css-standards.mdc) for rules on writing CSS + +### Scoped styles +```liquid +{% 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 +```liquid +
+``` + +## Block targeting + +### Section schema for theme blocks +```json +{ + "blocks": [ + { "type": "@theme" }, // Accept all theme blocks + { "type": "@app" } // Accept app blocks + ] +} +``` + +### Restricted block targeting +```json +{ + "blocks": [ + { + "type": "text", + "name": "Text Content" + }, + { + "type": "image", + "name": "Image Content" + } + ] +} +``` + +## Common block patterns + +### Content block +```liquid +
+ {% if block.settings.heading != blank %} +

{{ block.settings.heading | escape }}

+ {% endif %} + + {% if block.settings.text != blank %} +
{{ block.settings.text }}
+ {% endif %} + + {% if block.settings.button_text != blank %} + + {{ block.settings.button_text | escape }} + + {% endif %} +
+``` + +### Media block +```liquid +
+ {% if block.settings.image %} +
+ {{ block.settings.image | image_url: width: 800 | image_tag: + alt: block.settings.image.alt | default: block.settings.alt_text + }} +
+ {% endif %} + + {% if block.settings.video %} +
+ {{ block.settings.video | video_tag: controls: true }} +
+ {% endif %} +
+``` + +### Layout block (container) +```liquid +
+ {% content_for 'blocks' %} +
+``` + +## Performance best practices + +### Conditional rendering +```liquid +{% liquid + assign has_content = false + if block.settings.heading != blank or block.settings.text != blank + assign has_content = true + endif +%} + +{% if has_content %} +
+ +
+{% endif %} +``` + +## Examples referenced + +[text.liquid](mdc:.cursor/rules/examples/block-example-text.liquid) - Basic content block from existing project +[group.liquid](mdc:.cursor/rules/examples/block-example-group.liquid) - Container with nested blocks from existing project diff --git a/ai/cursor/rules/examples/block-example-group.mdc b/ai/cursor/rules/examples/block-example-group.mdc new file mode 100644 index 0000000..701e02f --- /dev/null +++ b/ai/cursor/rules/examples/block-example-group.mdc @@ -0,0 +1,103 @@ +{% doc %} + Renders a group of blocks with configurable layout direction, gap and + alignment. + + All settings apply to only one dimension to reduce configuration complexity. + + This component is a wrapper concerned only with rendering its children in + the specified layout direction with appropriate padding and alignment. + + @example + {% content_for 'block', type: 'group', id: 'group' %} +{% enddoc %} + +
+ {% content_for 'blocks' %} +
+ +{% stylesheet %} + .group { + display: flex; + flex-wrap: nowrap; + overflow: hidden; + width: 100%; + } + .group--horizontal { + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 0 var(--padding); + } + .group--vertical { + flex-direction: column; + align-items: var(--alignment); + padding: var(--padding) 0; + } +{% endstylesheet %} + +{% schema %} +{ + "name": "t:general.group", + "blocks": [{ "type": "@theme" }], + "settings": [ + { + "type": "select", + "id": "layout_direction", + "label": "t:labels.layout_direction", + "default": "group--vertical", + "options": [ + { "value": "group--horizontal", "label": "t:options.direction.horizontal" }, + { "value": "group--vertical", "label": "t:options.direction.vertical" } + ] + }, + { + "visible_if": "{{ block.settings.layout_direction == 'group--vertical' }}", + "type": "select", + "id": "alignment", + "label": "t:labels.alignment", + "default": "flex-start", + "options": [ + { "value": "flex-start", "label": "t:options.alignment.left" }, + { "value": "center", "label": "t:options.alignment.center" }, + { "value": "flex-end", "label": "t:options.alignment.right" } + ] + }, + { + "type": "range", + "id": "padding", + "label": "t:labels.padding", + "default": 0, + "min": 0, + "max": 200, + "step": 2, + "unit": "px" + } + ], + "presets": [ + { + "name": "t:general.column", + "category": "t:general.layout", + "settings": { + "layout_direction": "group--vertical", + "alignment": "flex-start", + "padding": 0 + } + }, + { + "name": "t:general.row", + "category": "t:general.layout", + "settings": { + "layout_direction": "group--horizontal", + "padding": 0 + } + } + ] +} +{% endschema %} diff --git a/ai/cursor/rules/examples/block-example-text.mdc b/ai/cursor/rules/examples/block-example-text.mdc new file mode 100644 index 0000000..b985c38 --- /dev/null +++ b/ai/cursor/rules/examples/block-example-text.mdc @@ -0,0 +1,59 @@ +{% doc %} + Renders a text block. + + @example + {% content_for 'block', type: 'text', id: 'text' %} +{% enddoc %} + +
+ {{ block.settings.text }} +
+ +{% stylesheet %} + .text { + text-align: var(--text-align); + } + .text--title { + font-size: 2rem; + font-weight: 700; + } + .text--subtitle { + font-size: 1.5rem; + } +{% endstylesheet %} + +{% schema %} +{ + "name": "t:general.text", + "settings": [ + { + "type": "text", + "id": "text", + "label": "t:labels.text", + "default": "Text" + }, + { + "type": "select", + "id": "text_style", + "label": "t:labels.text_style", + "options": [ + { "value": "text--title", "label": "t:options.text_style.title" }, + { "value": "text--subtitle", "label": "t:options.text_style.subtitle" }, + { "value": "text--normal", "label": "t:options.text_style.normal" } + ], + "default": "text--title" + }, + { + "type": "text_alignment", + "id": "alignment", + "label": "t:labels.alignment", + "default": "left" + } + ], + "presets": [{ "name": "t:general.text" }] +} +{% endschema %} diff --git a/ai/cursor/rules/examples/section-example.mdc b/ai/cursor/rules/examples/section-example.mdc new file mode 100644 index 0000000..6a58744 --- /dev/null +++ b/ai/cursor/rules/examples/section-example.mdc @@ -0,0 +1,61 @@ +
+ {% if section.settings.background_image %} +
+ {{ section.settings.background_image | image_url: width: 2000 | image_tag }} +
+ {% endif %} + +
+ {% content_for 'blocks' %} +
+
+ +{% stylesheet %} + .example-section { + position: relative; + overflow: hidden; + width: 100%; + } + .example-section__background { + position: absolute; + width: 100%; + height: 100%; + z-index: -1; + overflow: hidden; + } + .example-section__background img { + position: absolute; + width: 100%; + height: auto; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + .example-section__content { + display: grid; + grid-template-columns: var(--content-grid); + } + + .example-section__content > * { + grid-column: 2; + } +{% endstylesheet %} + +{% schema %} +{ + "name": "t:general.custom_section", + "blocks": [{ "type": "@theme" }], + "settings": [ + { + "type": "image_picker", + "id": "background_image", + "label": "t:labels.background" + } + ], + "presets": [ + { + "name": "t:general.custom_section" + } + ] +} +{% endschema %} diff --git a/ai/cursor/rules/examples/snippet-example.mdc b/ai/cursor/rules/examples/snippet-example.mdc new file mode 100644 index 0000000..e6d61f2 --- /dev/null +++ b/ai/cursor/rules/examples/snippet-example.mdc @@ -0,0 +1,72 @@ +{% doc %} + Renders a responsive image that might be wrapped in a link. + + When `width`, `height` and `crop` are provided, the image will be rendered + with a fixed aspect ratio. + + Serves as an example of how to use the `image_url` filter and `image_tag` filter + as well as how you can use LiquidDoc to document your code. + + @param {image} image - The image to be rendered + @param {string} [url] - An optional destination URL for the image + @param {string} [css_class] - Optional class to be added to the image wrapper + @param {number} [width] - The highest resolution width of the image to be rendered + @param {number} [height] - The highest resolution height of the image to be rendered + @param {string} [crop] - The crop position of the image + + @example + {% render 'image', image: product.featured_image %} + {% render 'image', image: product.featured_image, url: product.url %} + {% render 'image', + css_class: 'product__image', + image: product.featured_image, + url: product.url, + width: 1200, + height: 800, + crop: 'center', + %} +{% enddoc %} + +{% liquid + unless height + assign width = width | default: image.width + endunless + + if url + assign wrapper = 'a' + else + assign wrapper = 'div' + endif +%} + +<{{ wrapper }} + class="image {{ css_class }}" + {% if url %} + href="{{ url }}" + {% endif %} +> + {{ image | image_url: width: width, height: height, crop: crop | image_tag }} + + +{% stylesheet %} + .image { + display: block; + position: relative; + overflow: hidden; + width: 100%; + height: auto; + } + + .image > img { + width: 100%; + height: auto; + } +{% endstylesheet %} + +{% javascript %} + function doSomething() { + // example + } + doSomething() +{% endjavascript %} + diff --git a/ai/cursor/rules/liquid.mdc b/ai/cursor/rules/liquid.mdc new file mode 100644 index 0000000..3aa6cc4 --- /dev/null +++ b/ai/cursor/rules/liquid.mdc @@ -0,0 +1,827 @@ +--- +description: Valid Liquid syntax reference (including filters, tags, and objects) for Shopify themes +globs: *.liquid +alwaysApply: false +--- +# Liquid syntax standards + +## Liquid + +### Liquid delimiters + +- **`{{ ... }}`**: Output – prints a value. +- **`{{- ... -}}`**: Output, trims whitespace around the value. +- **`{% ... %}`**: Logic/control tag (if, for, assign, etc.), does not print anything, no whitespace trim. +- **`{%- ... -%}`**: Logic/control tag, trims whitespace around the tag. + +**Tip:** +Adding a dash (`-`) after `{%`/`{{` or before `%}`/`}}` trims spaces or newlines next to the tag. + +**Examples:** +- `{{- product.title -}}` → print value, remove surrounding spaces or lines. +- `{%- if available -%}In stock{%- endif -%}` → logic, removes extra spaces/lines. + +### Liquid operators + +**Comparison operators:** +- == +- != +- > +- < +- >= +- <= + +**Logical operators:** +- `or` +- `and` +- `contains` - checks if a string contains a substring, or if an array contains a string + +#### Comparison and comparison tags + +**Key condition principles:** +- For simplificity, ALWAYS use nested `if` conditions when the logic requires more than one logical operator +- Parentheses are not supported in Liquid +- Ternary conditionals are not supported in Liquid, so always use `{% if cond %}` + +**Basic comparison example:** +```liquid +{% if product.title == "Awesome Shoes" %} + These shoes are awesome! +{% endif %} +``` + +**Multiple Conditions:** +```liquid +{% if product.type == "Shirt" or product.type == "Shoes" %} + This is a shirt or a pair of shoes. +{% endif %} +``` + +**Contains Usage:** +- For strings: `{% if product.title contains "Pack" %}` +- For arrays: `{% if product.tags contains "Hello" %}` +- Note: `contains` only works with strings, not objects in arrays + +**{% elsif %} (used inside if/unless only)** +```liquid +{% if a %} + ... +{% elsif b %} + ... +{% endif %} +``` + +**{% unless %}** +```liquid +{% unless condition %} + ... +{% endunless %} +``` + +**{% case %}** +```liquid +{% case variable %} + {% when 'a' %} + a + {% when 'b' %} + b + {% else %} + other +{% endcase %} +``` + +**{% else %} (used inside if, unless, case, or for)** +```liquid +{% if product.available %} + In stock +{% else %} + Sold out +{% endif %} +``` +_or inside a for loop:_ +```liquid +{% for item in collection.products %} + {{ item.title }} +{% else %} + No products found. +{% endfor %} +``` + +#### Variables and variable tags + +```liquid +{% assign my_variable = 'value' %} + +{% capture my_variable %} + Contents of variable +{% endcapture %} + +{% increment counter %} +{% decrement counter %} +``` + +### Liquid filters + +You can chain filters in Liquid, passing the result of one filter as the input to the next. + +See these filters: + +- `upcase`: `{{ string | upcase }}` returns a **string** +- `split`: `{{ string | split: string }}` returns an **array** (as we may notice in the docs, `split` receives a string as its argument) +- `last`: `{{ array | last }}` returns **untyped** + +Each filter can pass its return value to the next filter as long as the types match. + +For example, `upcase` returns a string, which is suitable input for `split`, which then produces an array for `last` to use. + +Here's how the filters are executed step by step to eventually return `"WORLD"`: + +```liquid +{{ "hello world" | upcase | split: " " | last }} +``` + +- First, `"hello world"` is converted to uppercase: `"HELLO WORLD"`, which is a string +- Next, `split` can act on strings, so it splits the value by space into an array: `["HELLO", "WORLD"]` +- Finally, the `last` filter work with array, so `"WORLD"` is returned + +#### Array +- `compact`: `{{ array | compact }}` returns `array` +- `concat`: `{{ array | concat: array }}` returns `array` +- `find`: `{{ array | find: string, string }}` returns `untyped` +- `find_index`: `{{ array | find_index: string, string }}` returns `number` +- `first`: `{{ array | first }}` returns `untyped` +- `has`: `{{ array | has: string, string }}` returns `boolean` +- `join`: `{{ array | join }}` returns `string` +- `last`: `{{ array | last }}` returns `untyped` +- `map`: `{{ array | map: string }}` returns `array` +- `reject`: `{{ array | reject: string, string }}` returns `array` +- `reverse`: `{{ array | reverse }}` returns `array` +- `size`: `{{ variable | size }}` returns `number` +- `sort`: `{{ array | sort }}` returns `array` +- `sort_natural`: `{{ array | sort_natural }}` returns `array` +- `sum`: `{{ array | sum }}` returns `number` +- `uniq`: `{{ array | uniq }}` returns `array` +- `where`: `{{ array | where: string, string }}` returns `array` + +#### Cart +- `item_count_for_variant`: `{{ cart | item_count_for_variant: {variant_id} }}` returns `number` +- `line_items_for`: `{{ cart | line_items_for: object }}` returns `array` + +#### Collection +- `link_to_type`: `{{ string | link_to_type }}` returns `string` +- `link_to_vendor`: `{{ string | link_to_vendor }}` returns `string` +- `sort_by`: `{{ string | sort_by: string }}` returns `string` +- `url_for_type`: `{{ string | url_for_type }}` returns `string` +- `url_for_vendor`: `{{ string | url_for_vendor }}` returns `string` +- `within`: `{{ string | within: collection }}` returns `string` +- `highlight_active_tag`: `{{ string | highlight_active_tag }}` returns `string` + +#### Color +- `brightness_difference`: `{{ string | brightness_difference: string }}` returns `number` +- `color_brightness`: `{{ string | color_brightness }}` returns `number` +- `color_contrast`: `{{ string | color_contrast: string }}` returns `number` +- `color_darken`: `{{ string | color_darken: number }}` returns `string` +- `color_desaturate`: `{{ string | color_desaturate: number }}` returns `string` +- `color_difference`: `{{ string | color_difference: string }}` returns `number` +- `color_extract`: `{{ string | color_extract: string }}` returns `number` +- `color_lighten`: `{{ string | color_lighten: number }}` returns `string` +- `color_mix`: `{{ string | color_mix: string, number }}` returns `string` +- `color_modify`: `{{ string | color_modify: string, number }}` returns `string` +- `color_saturate`: `{{ string | color_saturate: number }}` returns `string` +- `color_to_hex`: `{{ string | color_to_hex }}` returns `string` +- `color_to_hsl`: `{{ string | color_to_hsl }}` returns `string` +- `color_to_oklch`: `{{ string | color_to_oklch }}` returns `string` +- `color_to_rgb`: `{{ string | color_to_rgb }}` returns `string` +- `hex_to_rgba`: `{{ string | hex_to_rgba }}` returns `string` + +#### Customer +- `customer_login_link`: `{{ string | customer_login_link }}` returns `string` +- `customer_logout_link`: `{{ string | customer_logout_link }}` returns `string` +- `customer_register_link`: `{{ string | customer_register_link }}` returns `string` +- `avatar`: `{{ customer | avatar }}` returns `string` +- `login_button`: `{{ shop | login_button }}` returns `string` + +#### Date +- `date`: `{{ date | date: string }}` returns `string` + +#### Default +- `default_errors`: `{{ string | default_errors }}` returns `string` +- `default`: `{{ variable | default: variable }}` returns `untyped` +- `default_pagination`: `{{ paginate | default_pagination }}` returns `string` + +#### Font +- `font_face`: `{{ font | font_face }}` returns `string` +- `font_modify`: `{{ font | font_modify: string, string }}` returns `font` +- `font_url`: `{{ font | font_url }}` returns `string` + +#### Format +- `date`: `{{ string | date: string }}` returns `string` +- `json`: `{{ variable | json }}` returns `string` +- `structured_data`: `{{ variable | structured_data }}` returns `string` +- `unit_price_with_measurement`: `{{ number | unit_price_with_measurement: unit_price_measurement }}` returns `string` +- `weight_with_unit`: `{{ number | weight_with_unit }}` returns `string` + +#### Hosted_file +- `asset_img_url`: `{{ string | asset_img_url }}` returns `string` +- `asset_url`: `{{ string | asset_url }}` returns `string` +- `file_img_url`: `{{ string | file_img_url }}` returns `string` +- `file_url`: `{{ string | file_url }}` returns `string` +- `global_asset_url`: `{{ string | global_asset_url }}` returns `string` +- `shopify_asset_url`: `{{ string | shopify_asset_url }}` returns `string` + +#### Html +- `class_list`: `{{ settings.layout | class_list }}` returns `string` +- `time_tag`: `{{ string | time_tag: string }}` returns `string` +- `inline_asset_content`: `{{ asset_name | inline_asset_content }}` returns `string` +- `highlight`: `{{ string | highlight: string }}` returns `string` +- `link_to`: `{{ string | link_to: string }}` returns `string` +- `placeholder_svg_tag`: `{{ string | placeholder_svg_tag }}` returns `string` +- `preload_tag`: `{{ string | preload_tag: as: string }}` returns `string` +- `script_tag`: `{{ string | script_tag }}` returns `string` +- `stylesheet_tag`: `{{ string | stylesheet_tag }}` returns `string` + +#### Localization +- `currency_selector`: `{{ form | currency_selector }}` returns `string` +- `translate`: `{{ string | t }}` returns `string` +- `format_address`: `{{ address | format_address }}` returns `string` + +#### Math +- `abs`: `{{ number | abs }}` returns `number` +- `at_least`: `{{ number | at_least }}` returns `number` +- `at_most`: `{{ number | at_most }}` returns `number` +- `ceil`: `{{ number | ceil }}` returns `number` +- `divided_by`: `{{ number | divided_by: number }}` returns `number` +- `floor`: `{{ number | floor }}` returns `number` +- `minus`: `{{ number | minus: number }}` returns `number` +- `modulo`: `{{ number | modulo: number }}` returns `number` +- `plus`: `{{ number | plus: number }}` returns `number` +- `round`: `{{ number | round }}` returns `number` +- `times`: `{{ number | times: number }}` returns `number` + +#### Media +- `external_video_tag`: `{{ variable | external_video_tag }}` returns `string` +- `external_video_url`: `{{ media | external_video_url: attribute: string }}` returns `string` +- `image_tag`: `{{ string | image_tag }}` returns `string` +- `media_tag`: `{{ media | media_tag }}` returns `string` +- `model_viewer_tag`: `{{ media | model_viewer_tag }}` returns `string` +- `video_tag`: `{{ media | video_tag }}` returns `string` +- `article_img_url`: `{{ variable | article_img_url }}` returns `string` +- `collection_img_url`: `{{ variable | collection_img_url }}` returns `string` +- `image_url`: `{{ variable | image_url: width: number, height: number }}` returns `string` +- `img_tag`: `{{ string | img_tag }}` returns `string` +- `img_url`: `{{ variable | img_url }}` returns `string` +- `product_img_url`: `{{ variable | product_img_url }}` returns `string` + +#### Metafield +- `metafield_tag`: `{{ metafield | metafield_tag }}` returns `string` +- `metafield_text`: `{{ metafield | metafield_text }}` returns `string` + +#### Money +- `money`: `{{ number | money }}` returns `string` +- `money_with_currency`: `{{ number | money_with_currency }}` returns `string` +- `money_without_currency`: `{{ number | money_without_currency }}` returns `string` +- `money_without_trailing_zeros`: `{{ number | money_without_trailing_zeros }}` returns `string` + +#### Payment +- `payment_button`: `{{ form | payment_button }}` returns `string` +- `payment_terms`: `{{ form | payment_terms }}` returns `string` +- `payment_type_img_url`: `{{ string | payment_type_img_url }}` returns `string` +- `payment_type_svg_tag`: `{{ string | payment_type_svg_tag }}` returns `string` + +#### String +- `hmac_sha1`: `{{ string | hmac_sha1: string }}` returns `string` +- `hmac_sha256`: `{{ string | hmac_sha256: string }}` returns `string` +- `md5`: `{{ string | md5 }}` returns `string` +- `sha1`: `{{ string | sha1: string }}` returns `string` +- `sha256`: `{{ string | sha256: string }}` returns `string` +- `append`: `{{ string | append: string }}` returns `string` +- `base64_decode`: `{{ string | base64_decode }}` returns `string` +- `base64_encode`: `{{ string | base64_encode }}` returns `string` +- `base64_url_safe_decode`: `{{ string | base64_url_safe_decode }}` returns `string` +- `base64_url_safe_encode`: `{{ string | base64_url_safe_encode }}` returns `string` +- `capitalize`: `{{ string | capitalize }}` returns `string` +- `downcase`: `{{ string | downcase }}` returns `string` +- `escape`: `{{ string | escape }}` returns `string` +- `escape_once`: `{{ string | escape_once }}` returns `string` +- `lstrip`: `{{ string | lstrip }}` returns `string` +- `newline_to_br`: `{{ string | newline_to_br }}` returns `string` +- `prepend`: `{{ string | prepend: string }}` returns `string` +- `remove`: `{{ string | remove: string }}` returns `string` +- `remove_first`: `{{ string | remove_first: string }}` returns `string` +- `remove_last`: `{{ string | remove_last: string }}` returns `string` +- `replace`: `{{ string | replace: string, string }}` returns `string` +- `replace_first`: `{{ string | replace_first: string, string }}` returns `string` +- `replace_last`: `{{ string | replace_last: string, string }}` returns `string` +- `rstrip`: `{{ string | rstrip }}` returns `string` +- `slice`: `{{ string | slice }}` returns `string` +- `split`: `{{ string | split: string }}` returns `array` +- `strip`: `{{ string | strip }}` returns `string` +- `strip_html`: `{{ string | strip_html }}` returns `string` +- `strip_newlines`: `{{ string | strip_newlines }}` returns `string` +- `truncate`: `{{ string | truncate: number }}` returns `string` +- `truncatewords`: `{{ string | truncatewords: number }}` returns `string` +- `upcase`: `{{ string | upcase }}` returns `string` +- `url_decode`: `{{ string | url_decode }}` returns `string` +- `url_encode`: `{{ string | url_encode }}` returns `string` +- `camelize`: `{{ string | camelize }}` returns `string` +- `handleize`: `{{ string | handleize }}` returns `string` +- `url_escape`: `{{ string | url_escape }}` returns `string` +- `url_param_escape`: `{{ string | url_param_escape }}` returns `string` +- `pluralize`: `{{ number | pluralize: string, string }}` returns `string` + +#### Tag +- `link_to_add_tag`: `{{ string | link_to_add_tag }}` returns `string` +- `link_to_remove_tag`: `{{ string | link_to_remove_tag }}` returns `string` +- `link_to_tag`: `{{ string | link_to_tag }}` returns `string` + +### Liquid objects + +#### Global objects +- `collections` +- `pages` +- `all_products` +- `articles` +- `blogs` +- `cart` +- `closest` +- `content_for_header` +- `customer` +- `images` +- `linklists` +- `localization` +- `metaobjects` +- `request` +- `routes` +- `shop` +- `theme` +- `settings` +- `template` +- `additional_checkout_buttons` +- `all_country_option_tags` +- `canonical_url` +- `content_for_additional_checkout_buttons` +- `content_for_index` +- `content_for_layout` +- `country_option_tags` +- `current_page` +- `handle` +- `page_description` +- `page_image` +- `page_title` +- `powered_by_link` +- `scripts` + +#### `/article` page +- `article` +- `blog` + +#### `/blog` page +- `blog` +- `current_tags` + +#### `/cart` page +- `cart` + +#### `/checkout` page +- `checkout` + +#### `/collection` page +- `collection` +- `current_tags` + +#### `/customers/account` page +- `customer` + +#### `/customers/addresses` page +- `customer` + +#### `/customers/order` page +- `customer` +- `order` + +#### `/gift_card.liquid` page +- `gift_card` +- `recipient` + +#### `/metaobject` page +- `metaobject` + +#### `/page` page +- `page` + +#### `/product` page +- `product` + +#### `/robots.txt.liquid` page +- `robots` + +#### `/search` page +- `search` +### Liquid tags + + +#### content_for +The `content_for` tag requires a type parameter to differentiate between rendering a number of theme blocks (`'blocks'`) and a single static block (`'block'`). + + +Syntax: +``` +{% content_for 'blocks' %} +{% content_for 'block', type: "slide", id: "slide-1" %} +``` + +#### form +Because there are many different form types available in Shopify themes, the `form` tag requires a type. Depending on the +form type, an additional parameter might be required. You can specify the following form types: + +- [`activate_customer_password`](https://shopify.dev/docs/api/liquid/tags/form#form-activate_customer_password) +- [`cart`](https://shopify.dev/docs/api/liquid/tags/form#form-cart) +- [`contact`](https://shopify.dev/docs/api/liquid/tags/form#form-contact) +- [`create_customer`](https://shopify.dev/docs/api/liquid/tags/form#form-create_customer) +- [`currency`](https://shopify.dev/docs/api/liquid/tags/form#form-currency) +- [`customer`](https://shopify.dev/docs/api/liquid/tags/form#form-customer) +- [`customer_address`](https://shopify.dev/docs/api/liquid/tags/form#form-customer_address) +- [`customer_login`](https://shopify.dev/docs/api/liquid/tags/form#form-customer_login) +- [`guest_login`](https://shopify.dev/docs/api/liquid/tags/form#form-guest_login) +- [`localization`](https://shopify.dev/docs/api/liquid/tags/form#form-localization) +- [`new_comment`](https://shopify.dev/docs/api/liquid/tags/form#form-new_comment) +- [`product`](https://shopify.dev/docs/api/liquid/tags/form#form-product) +- [`recover_customer_password`](https://shopify.dev/docs/api/liquid/tags/form#form-recover_customer_password) +- [`reset_customer_password`](https://shopify.dev/docs/api/liquid/tags/form#form-reset_customer_password) +- [`storefront_password`](https://shopify.dev/docs/api/liquid/tags/form#form-storefront_password) + + +Syntax: +``` +{% form 'form_type' %} + content +{% endform %} +``` + +#### layout + +Syntax: +``` +{% layout name %} +``` + +#### assign +You can create variables of any [basic type](https://shopify.dev/docs/api/liquid/basics#types), [object](https://shopify.dev/docs/api/liquid/objects), or object property. + + +Syntax: +``` +{% assign variable_name = value %} +``` + +#### break + +Syntax: +``` +{% break %} +``` + +#### capture +You can create complex strings with Liquid logic and variables. + + +Syntax: +``` +{% capture variable %} + value +{% endcapture %} +``` + +#### case + +Syntax: +``` +{% case variable %} + {% when first_value %} + first_expression + {% when second_value %} + second_expression + {% else %} + third_expression +{% endcase %} +``` + +#### comment +Any text inside `comment` tags won't be output, and any Liquid code will be parsed, but not executed. + + +Syntax: +``` +{% comment %} + content +{% endcomment %} +``` + +#### continue + +Syntax: +``` +{% continue %} +``` + +#### cycle +The `cycle` tag must be used inside a `for` loop. + +> Tip: +> Use the `cycle` tag to output text in a predictable pattern. For example, to apply odd/even classes to rows in a table. + + +Syntax: +``` +{% cycle string, string, ... %} +``` + +#### decrement +Variables that are declared with `decrement` are unique to the [layout](/themes/architecture/layouts), [template](/themes/architecture/templates), +or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across +[snippets](/themes/architecture/snippets) included in the file. + +Similarly, variables that are created with `decrement` are independent from those created with [`assign`](https://shopify.dev/docs/api/liquid/tags/assign) +and [`capture`](https://shopify.dev/docs/api/liquid/tags/capture). However, `decrement` and [`increment`](https://shopify.dev/docs/api/liquid/tags/increment) share +variables. + + +Syntax: +``` +{% decrement variable_name %} +``` + +#### doc +The `doc` tag allows developers to include documentation within Liquid +templates. Any content inside `doc` tags is not rendered or outputted. +Liquid code inside will be parsed but not executed. This facilitates +tooling support for features like code completion, linting, and inline +documentation. + +For detailed documentation syntax and examples, see the +[`LiquidDoc` reference](https://shopify.dev/docs/storefronts/themes/tools/liquid-doc). + + +Syntax: +``` +{% doc %} + Renders a message. + + @param {string} foo - A string value. + @param {string} [bar] - An optional string value. + + @example + {% render 'message', foo: 'Hello', bar: 'World' %} +{% enddoc %} +{{ foo }}, {{ bar }}! +``` + +#### echo +Using the `echo` tag is the same as wrapping an expression in curly brackets (`{{` and `}}`). However, unlike the curly +bracket method, you can use the `echo` tag inside [`liquid` tags](https://shopify.dev/docs/api/liquid/tags/liquid). + +> Tip: +> You can use [filters](https://shopify.dev/docs/api/liquid/filters) on expressions inside `echo` tags. + + +Syntax: +``` +{% liquid + echo expression +%} +``` + +#### for +You can do a maximum of 50 iterations with a `for` loop. If you need to iterate over more than 50 items, then use the +[`paginate` tag](https://shopify.dev/docs/api/liquid/tags/paginate) to split the items over multiple pages. + +> Tip: +> Every `for` loop has an associated [`forloop` object](https://shopify.dev/docs/api/liquid/objects/forloop) with information about the loop. + + +Syntax: +``` +{% for variable in array %} + expression +{% endfor %} +``` + +#### if + +Syntax: +``` +{% if condition %} + expression +{% endif %} +``` + +#### increment +Variables that are declared with `increment` are unique to the [layout](/themes/architecture/layouts), [template](/themes/architecture/templates), +or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across +[snippets](/themes/architecture/snippets) included in the file. + +Similarly, variables that are created with `increment` are independent from those created with [`assign`](https://shopify.dev/docs/api/liquid/tags/assign) +and [`capture`](https://shopify.dev/docs/api/liquid/tags/capture). However, `increment` and [`decrement`](https://shopify.dev/docs/api/liquid/tags/decrement) share +variables. + + +Syntax: +``` +{% increment variable_name %} +``` + +#### raw + +Syntax: +``` +{% raw %} + expression +{% endraw %} +``` + +#### render +Inside snippets and app blocks, you can't directly access variables that are [created](https://shopify.dev/docs/api/liquid/tags/variable-tags) outside +of the snippet or app block. However, you can [specify variables as parameters](https://shopify.dev/docs/api/liquid/tags/render#render-passing-variables-to-a-snippet) +to pass outside variables to snippets. + +While you can't directly access created variables, you can access global objects, as well as any objects that are +directly accessible outside the snippet or app block. For example, a snippet or app block inside the [product template](/themes/architecture/templates/product) +can access the [`product` object](https://shopify.dev/docs/api/liquid/objects/product), and a snippet or app block inside a [section](/themes/architecture/sections) +can access the [`section` object](https://shopify.dev/docs/api/liquid/objects/section). + +Outside a snippet or app block, you can't access variables created inside the snippet or app block. + +> Note: +> When you render a snippet using the `render` tag, you can't use the [`include` tag](https://shopify.dev/docs/api/liquid/tags/include) +> inside the snippet. + + +Syntax: +``` +{% render 'filename' %} +``` + +#### tablerow +The `tablerow` tag must be wrapped in HTML `` and `
` tags. + +> Tip: +> Every `tablerow` loop has an associated [`tablerowloop` object](https://shopify.dev/docs/api/liquid/objects/tablerowloop) with information about the loop. + + +Syntax: +``` +{% tablerow variable in array %} + expression +{% endtablerow %} +``` + +#### unless +> Tip: +> Similar to the [`if` tag](https://shopify.dev/docs/api/liquid/tags/if), you can use `elsif` to add more conditions to an `unless` tag. + + +Syntax: +``` +{% unless condition %} + expression +{% endunless %} +``` + +#### paginate +Because [`for` loops](https://shopify.dev/docs/api/liquid/tags/for) are limited to 50 iterations per page, you need to use the `paginate` tag to +iterate over an array that has more than 50 items. The following arrays can be paginated: + +- [`all_products`](https://shopify.dev/docs/api/liquid/objects/all_products) +- [`article.comments`](https://shopify.dev/docs/api/liquid/objects/article#article-comments) +- [`blog.articles`](https://shopify.dev/docs/api/liquid/objects/blog#blog-articles) +- [`collections`](https://shopify.dev/docs/api/liquid/objects/collections) +- [`collection.products`](https://shopify.dev/docs/api/liquid/objects/collection#collection-products) +- [`customer.addresses`](https://shopify.dev/docs/api/liquid/objects/customer#customer-addresses) +- [`customer.orders`](https://shopify.dev/docs/api/liquid/objects/customer#customer-orders) +- [`pages`](https://shopify.dev/docs/api/liquid/objects/pages) +- [`product.variants`](https://shopify.dev/docs/api/liquid/objects/product#variants) +- [`search.results`](https://shopify.dev/docs/api/liquid/objects/search#search-results) +- [`collection_list` settings](/themes/architecture/settings/input-settings#collection_list) +- [`product_list` settings](/themes/architecture/settings/input-settings#product_list) + +Within the `paginate` tag, you have access to the [`paginate` object](https://shopify.dev/docs/api/liquid/objects/paginate). You can use this +object, or the [`default_pagination` filter](https://shopify.dev/docs/api/liquid/filters/default_pagination), to build page navigation. + + +Syntax: +``` +{% paginate array by page_size %} + {% for item in array %} + forloop_content + {% endfor %} +{% endpaginate %} + +The `paginate` tag allows the user to paginate to the 25,000th item in the array and no further. To reach items further in +the array the array should be filtered further before paginating. See +[Pagination Limits](/themes/best-practices/performance/platform#pagination-limits) for more information. +``` + +#### javascript +Each section, block or snippet can have only one `{% javascript %}` tag. + +To learn more about how JavaScript that's defined between the `javascript` tags is loaded and run, refer to the documentation for [javascript tags](/storefronts/themes/best-practices/javascript-and-stylesheet-tags#javascript). +> Caution: +> Liquid isn't rendered inside of `{% javascript %}` tags. Including Liquid code can cause syntax errors. + + +Syntax: +``` +{% javascript %} + javascript_code +{% endjavascript %} +``` + +#### section +Rendering a section with the `section` tag renders a section statically. To learn more about sections and how to use +them in your theme, refer to [Render a section](/themes/architecture/sections#render-a-section). + + +Syntax: +``` +{% section 'name' %} +``` + +#### stylesheet +Each section, block or snippet can have only one `{% stylesheet %}` tag. + +To learn more about how CSS that's defined between the `stylesheet` tags is loaded and run, refer to the documentation for [stylesheet tags](/storefronts/themes/best-practices/javascript-and-stylesheet-tags#stylesheet). +> Caution: +> Liquid isn't rendered inside of `{% stylesheet %}` tags. Including Liquid code can cause syntax errors. + + +Syntax: +``` +{% stylesheet %} + css_styles +{% endstylesheet %} +``` + +#### sections +Use this tag to render section groups as part of the theme's [layout](/themes/architecture/layouts) content. Place the `sections` tag where you want to render it in the layout. + +To learn more about section groups and how to use them in your theme, refer to [Section groups](/themes/architecture/section-groups#usage). + + +Syntax: +``` +{% sections 'name' %} +``` + +#### style +> Note: +> If you reference [color settings](/themes/architecture/settings/input-settings#color) inside `style` tags, then +> the associated CSS rules will update as the setting is changed in the theme editor, without a page refresh. + + +Syntax: +``` +{% style %} + CSS_rules +{% endstyle %} +``` + +#### else +You can use the `else` tag with the following tags: + +- [`case`](https://shopify.dev/docs/api/liquid/tags/case) +- [`if`](https://shopify.dev/docs/api/liquid/tags/if) +- [`unless`](https://shopify.dev/docs/api/liquid/tags/unless) + + +Syntax: +``` +{% else %} + expression +``` + +#### else + +Syntax: +``` +{% for variable in array %} + first_expression +{% else %} + second_expression +{% endfor %} +``` + +#### liquid +Because the tags don't have delimeters, each tag needs to be on its own line. + +> Tip: +> Use the [`echo` tag](https://shopify.dev/docs/api/liquid/tags/echo) to output an expression inside `liquid` tags. + + +Syntax: +``` +{% liquid + expression +%} +``` + + diff --git a/ai/cursor/rules/locales.mdc b/ai/cursor/rules/locales.mdc new file mode 100644 index 0000000..ced57fe --- /dev/null +++ b/ai/cursor/rules/locales.mdc @@ -0,0 +1,100 @@ +--- +description: Locales coding standards and best practices guide +globs: locales/*.json +alwaysApply: false +--- +# Translation development standards + +Auto-attached when working in `locales/` directory. + +### File structure + +``` +locales/ +├── en.default.json # English (required) +├── en.default.schema.json # English (required) +├── es.json # Spanish +├── est.schema.json # Spanish +├── fr.json # French +├── frt.schema.json # French +└── pt-BR.json # Portuguese +└── pt-BR..schema.json # Portuguese +``` + +#### Locale files + +Locale files are JSON files containing translations for all the text strings used throughout a Shopify theme and its editor. They let merchants easily update and localize repeated words and phrases, making it possible to translate store content and settings into multiple languages for international customers. These files provide a centralized way to manage and edit translations. + +**Example:** +```json +{ + "general": { + "cart": "Cart", + "checkout": "Checkout" + }, + "products": { + "add_to_cart": "Add to Cart" + } +} +``` + +#### Schema locale files + +Schema locale files, saved with a .schema.json extension, store translation strings specifically for theme editor setting schemas. They follow a structured organization—category, group, and description—to give context to each translation, enabling accurate localization of editor content. Schema locale files must use the IETF language tag format in their naming, such as en-GB.schema.json for British English or fr-CA.schema.json for Canadian French. + +**Example:** +```json +{ + "products": { + "card": { + "description": "Product card layout" + } + } +} +``` + +### Key organization + +**Hierarchical structure:** +```json +{ + "general": { + "meta": { + "title": "{{ shop_name }}", + "description": "{{ shop_description }}" + }, + "accessibility": { + "skip_to_content": "Skip to content", + "close": "Close" + } + }, + "products": { + "add_to_cart": "Add to cart", + "quick_view": "Quick view", + "price": { + "regular": "Regular price", + "sale": "Sale price", + "unit": "Unit price" + } + } +} +``` +**Usage** +```liquid +{{ 'general.meta.title' | t: shop_name: shop.name }} +{{ 'general.meta.description' | t: shop_description: shop.description }} +``` + +### Translation guidelines + +**Key naming:** +- Use descriptive, hierarchical keys +- Maximum 3 levels deep +- Use snake_case for key names +- Group related translations + +**Content rules:** +- Keep text concise for UI elements +- Use variables for dynamic content +- Consider character limits +- Maintain consistent terminology diff --git a/ai/cursor/rules/localization.mdc b/ai/cursor/rules/localization.mdc new file mode 100644 index 0000000..1a68bde --- /dev/null +++ b/ai/cursor/rules/localization.mdc @@ -0,0 +1,67 @@ +--- +description: Shopify Liquid localization and translation guidelines +globs: *.liquid +alwaysApply: false +--- +# Localization Standards + +### Translation requirements + +- **Every user-facing text** must use translation filters. +- **Update `locales/en.default.json`** with all new keys. +- **Use descriptive, hierarchical keys** for organization. +- **Only add English text**; translators handle other languages. + +### Translation filter usage + +**Use `{{ 'key' | t }}` for all text:** + +```liquid + +

{{ 'sections.featured_collection.title' | t }}

+

{{ 'sections.featured_collection.description' | t }}

+ + + +

Featured Collection

+

Check out our best products

+ +``` + +### Translation with variables + +**Use variables for interpolation:** + +```liquid + +

{{ 'products.price_range' | t: min: product.price_min | money, max: product.price_max | money }}

+

{{ 'general.pagination.page' | t: page: paginate.current_page, pages: paginate.pages }}

+``` + +**Corresponding keys in locale files:** + +```json +{ + "products": { + "price_range": "From {{ min }} to {{ max }}" + }, + "general": { + "pagination": { + "page": "Page {{ page }} of {{ pages }}" + } + } +} +``` + +### Best practices + +**Content guidelines:** +- Write clear, concise text. +- **Use sentence case** for all user-facing text, including titles, headings, and button labels (capitalize only the first word and proper nouns; e.g., `Featured collection` → `Featured collection`, not `Featured Collection`). +- Be consistent with terminology. +- Consider character limits for UI elements. + +**Variable usage:** +- Use interpolation rather than appending strings together. +- Prioritize clarity over brevity for variable naming. +- Escape variables unless they output HTML: `{{ variable | escape }}`. diff --git a/ai/cursor/rules/mcp.mdc b/ai/cursor/rules/mcp.mdc new file mode 100644 index 0000000..c7ac27d --- /dev/null +++ b/ai/cursor/rules/mcp.mdc @@ -0,0 +1,2 @@ +🚨 MANDATORY: YOU MUST CALL "learn_shopify_api" ONCE WHEN WORKING WITH LIQUID THEMES. + diff --git a/ai/cursor/rules/schemas.mdc b/ai/cursor/rules/schemas.mdc new file mode 100644 index 0000000..0cb395b --- /dev/null +++ b/ai/cursor/rules/schemas.mdc @@ -0,0 +1,184 @@ +--- +description: JSON schemas to write config files, translations, and the {% schema %} tag in blocks and sections. +globs: blocks/*.liquid, sections/*.liquid, config/settings_schema.json, locales/*.json +alwaysApply: false +--- + +# JSON schemas for Liquid themes + +## The `{% schema %}` tag on blocks and sections + +**Key principles: follow the "Good practices" and "Validate the `{% schema %}` content" using JSON schemas** + +### Good practices + +When defining the `{% schema %}` tag on sections and blocks, follow these guidelines to use the values: + +**Single property settings**: For settings that correspond to a single CSS property, use CSS variables: +```liquid +
+ Example +
+ +{% stylesheet %} + .collection { + gap: var(--gap); + } +{% endstylesheet %} + +{% schema %} +{ + "settings": [{ + "type": "range", + "label": "gap", + "id": "gap", + "min": 0, + "max": 100, + "unit": "px", + "default": 0, + }] +} +{% endschema %} +``` + +**Multiple property settings**: For settings that control multiple CSS properties, use CSS classes: +```liquid +
+ Example +
+ +{% stylesheet %} + .collection--full-width { + /* multiple styles */ + } + .collection--narrow { + /* multiple styles */ + } +{% endstylesheet %} + +{% schema %} +{ + "settings": [{ + "type": "select", + "id": "layout", + "label": "layout", + "values": [ + { "value": "collection--full-width", "label": "t:options.full" }, + { "value": "collection--narrow", "label": "t:options.narrow" } + ] + }] +} +{% endschema %} +``` + +#### Mobile layouts + +If you need to create a mobile layout and you want the merchant to be able to select one or two columns, use a select input: + +```liquid +{% schema %} +{ + "type": "select", + "id": "columns_mobile", + "label": "Columns on mobile", + "options": [ + { "value": 1, "label": "1" }, + { "value": "2", "label": "2" } + ] +} +{% endschema %} +``` + +### Validate the `{% schema %}` content + +Use this mafinest to validate schemas to validate + +``` +{ + "$schema": "manifest_schema.json", + "$comment": "Declares all the JSON schemas you need to validate themes", + "schemas": [ + { "fileMatch": ["locales/*.json"], "uri": "schemas/translations.json" }, + { "fileMatch": ["blocks/*.liquid"], "uri": "schemas/theme_block.json" }, + { "fileMatch": ["config/settings_schema.json"], "uri": "schemas/theme_settings.json" }, + { "fileMatch": ["sections/*.liquid"], "uri": "schemas/section.json" }, + { "uri": "schemas/settings.json" }, + { "uri": "schemas/setting.json" }, + { "uri": "schemas/default_setting_values.json" }, + { "uri": "schemas/app_block_entry.json" }, + { "uri": "schemas/theme_block_entry.json" }, + { "uri": "schemas/targetted_block_entry.json" }, + { "uri": "schemas/preset_blocks.json" }, + { "uri": "schemas/preset.json" }, + { "uri": "schemas/local_block_entry.json" } + ] +} +``` + + +**schemas/app_block_entry.json** +``` +{"$schema":"http://json-schema.org/draft-07/schema#","$comment":"An @app block entry","type":"object","additionalProperties":false,"properties":{"type":{"const":"@app","description":"The \"@app\" type is used to denote that this container accepts app blocks. App blocks enable app developers to create blocks for merchants to add app content to their theme without having to directly edit theme code.","markdownDescription":"The `@app` type is used to denote that this container accepts app blocks. [App blocks](https://shopify.dev/docs/themes/architecture/sections/app-blocks) enable app developers to create blocks for merchants to add app content to their theme without having to directly edit theme code.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/blocks/app-blocks#supporting-app-blocks)"}}} +``` + +**schemas/default_setting_values.json** +``` +{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","description":"A list of default values for any settings that you might want to populate. Each entry should include the setting name and the value.","additionalProperties":{"anyOf":[{"type":"number"},{"type":"boolean"},{"type":"string"},{"type":"array","items":{"type":"string"}}]}} +``` + +**schemas/local_block_entry.json** +``` +{"$schema":"http://json-schema.org/draft-07/schema#","$comment":"For block definitions that are local to the file.","type":"object","required":["type","name"],"additionalProperties":false,"properties":{"type":{"type":"string","description":"The block type. This is a free-form string that you can use as an identifier.","markdownDescription":"The block type. This is a free-form string that you can use as an identifier.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#blocks)"},"name":{"type":"string","description":"The block name, which will show as the block title in the theme editor."},"limit":{"type":"integer","description":"The number of blocks of this type that can be used."},"settings":{"$ref":"./settings.json"}}} +``` + +**schemas/preset_blocks.json** +``` +{"$schema":"http://json-schema.org/draft-07/schema#","title":"Shopify Liquid Preset Blocks Schema","definitions":{"blocksArray":{"type":"array","description":"A list of child blocks that you might want to include.","markdownDescription":"A list of child blocks that you might want to include.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#presets)","items":{"type":"object","allOf":[{"$ref":"#/definitions/commonBlockAttributes"}],"properties":{"static":true,"type":true,"name":true,"settings":true,"blocks":{"$ref":"#/definitions/blocksArray"},"id":{"type":"string","description":"A unique identifier for the block."}},"additionalProperties":false,"if":{"properties":{"static":{"const":true}},"required":["static"]},"then":{"required":["id"]}}},"blocksHash":{"type":"object","description":"A list of child blocks that you might want to include.","markdownDescription":"A list of child blocks that you might want to include.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#presets)","additionalProperties":{"allOf":[{"$ref":"#/definitions/commonBlockAttributes"}],"properties":{"static":true,"type":true,"name":true,"settings":true,"blocks":{"$ref":"#/definitions/blocksHash"},"block_order":{"type":"array","description":"The order of the blocks in the section."}},"additionalProperties":false}},"commonBlockAttributes":{"type":"object","required":["type"],"properties":{"type":{"type":"string","description":"The block type."},"name":{"type":"string","description":"The block name."},"settings":{"$ref":"./default_setting_values.json"},"static":{"type":"boolean","description":"If the block is rendered statically or not."}}}}} +``` + +**schemas/preset.json** +``` +{"$schema":"http://json-schema.org/draft-07/schema#","title":"Shopify Liquid Section or Block Preset Schema","oneOf":[{"$ref":"#/definitions/presetWithBlocksArray"},{"$ref":"#/definitions/presetWithBlocksHash"}],"definitions":{"presetBase":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"The preset name, which will show in the 'Add section' or 'Add block' picker of the theme editor.","markdownDescription":"The preset name, which will show in the 'Add section' or 'Add block' picker of the theme editor.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#presets)"},"category":{"type":"string","description":"The category of the preset, which will show in the 'Add section' or 'Add block' picker of the theme editor.","markdownDescription":"The category of the preset, which will show in the 'Add section' or 'Add block' picker of the theme editor.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#presets)"},"settings":{"$ref":"./default_setting_values.json"}}},"presetWithBlocksArray":{"type":"object","allOf":[{"$ref":"#/definitions/presetBase"}],"properties":{"name":true,"category":true,"settings":true,"blocks":{"$ref":"./preset_blocks.json#/definitions/blocksArray"}},"additionalProperties":false},"presetWithBlocksHash":{"type":"object","allOf":[{"$ref":"#/definitions/presetBase"}],"required":["blocks"],"properties":{"name":true,"settings":true,"blocks":{"$ref":"./preset_blocks.json#/definitions/blocksHash"},"block_order":{"type":"array","description":"The order of blocks in the preset.","markdownDescription":"The order of blocks in the preset."},"additionalProperties":false}}}} +``` + +**schemas/section.json** +``` +{"$schema":"http://json-schema.org/draft-07/schema#","title":"Shopify Liquid Theme Section Schema","type":"object","additionalProperties":false,"properties":{"name":{"type":"string","description":"The name attribute determines the section title that is shown in the theme editor.","markdownDescription":"The `name` attribute determines the section title that is shown in the theme editor.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#name)"},"tag":{"type":"string","description":"The HTML element that is used to wrap the section.","markdownDescription":"The HTML element that is used to wrap the section.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#tag)","enum":["article","aside","div","footer","header","section"]},"class":{"type":"string","description":"Additional CSS class for the section.","markdownDescription":"Additional CSS class for the section.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#class)"},"limit":{"type":"integer","description":"The number of times a section can be added to a template or section group.","markdownDescription":"The number of times a section can be added to a template or section group.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#limit)","minimum":1,"maximum":2},"settings":{"$ref":"./settings.json"},"max_blocks":{"type":"integer","description":"There's a limit of 50 blocks per section. You can specify a lower limit with the max_blocks attribute","markdownDescription":"There's a limit of 50 blocks per section. You can specify a lower limit with the `max_blocks` attribute.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#max_blocks)","minimum":1,"maximum":50},"blocks":{"type":"array","description":"Blocks are reusable modules of content that can be added, removed, and reordered within a section.","markdownDescription":"Blocks are reusable modules of content that can be added, removed, and reordered within a section.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#blocks)","properties":{"type":{"description":"The block type. Can be one of the following values: @theme, @app, or a custom block type.","markdownDescription":"The block type. Can be one of the following values: @theme, @app, or a custom block type.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#blocks)"}},"items":{"type":"object","required":["type"],"properties":{"type":{"type":"string","description":"The type of block that can be added to this block.","markdownDescription":"The type of block that can be added to this block.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/blocks/theme-blocks/schema#blocks)"}},"$comment":"The allOf rule here exists because that's how we do discriminated unions in JSON schemas. If a rule matches, that rule will be used to document the type property. Otherwise we fallback to the docs above.","allOf":[{"if":{"required":["type"],"properties":{"type":{"const":"@theme"}}},"then":{"$ref":"./theme_block_entry.json"}},{"if":{"required":["type"],"properties":{"type":{"const":"@app"}}},"then":{"$ref":"./app_block_entry.json"}},{"if":{"required":["type"],"properties":{"type":{"type":"string","not":{"enum":["@app","@theme"]}}}},"then":{"oneOf":[{"$ref":"./targetted_block_entry.json"},{"$ref":"./local_block_entry.json"}]}}]}},"presets":{"type":"array","description":"Presets are default configurations of sections that enable users to easily add a section to a JSON template through the theme editor.","markdownDescription":"Presets are default configurations of sections that enable users to easily add a section to a [JSON template](https://shopify.dev/docs/themes/architecture/templates/json-templates) through the theme editor.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#presets)","items":{"$ref":"./preset.json"}},"default":{"type":"object","description":"Default configuration for statically rendered sections.","markdownDescription":"Default configuration for statically rendered sections.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#default)","properties":{"settings":{"$ref":"./default_setting_values.json"},"blocks":{"type":"array","description":"Default blocks configurations to ship with this default.","markdownDescription":"Default blocks configurations to ship with this default.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#default)","items":{"type":"object","required":["type"],"properties":{"type":{"type":"string","description":"The block type."},"settings":{"$ref":"./default_setting_values.json"}}}}}},"locales":{"type":"object","description":"Sections can provide their own set of translated strings through the locales object. This is separate from the locales directory of the theme, which makes it a useful feature for sections that are meant to be installed on multiple themes or shops.","markdownDescription":"Sections can provide their own set of translated strings through the `locales` object. This is separate from the `locales` directory of the theme, which makes it a useful feature for sections that are meant to be installed on multiple themes or shops.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#locales)","additionalProperties":{"type":"object","additionalProperties":{"type":"string"}}},"enabled_on":{"description":"Restrict the section to certain template page types and section group types.","markdownDescription":"Restrict the section to certain template page types and section group types.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#enabled_on)","$ref":"#/definitions/sectionToggle"},"disabled_on":{"description":"Prevent the section from being used on certain template page types and section group types.","markdownDescription":"Prevent the section from being used on certain template page types and section group types.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#disabled_on)","$ref":"#/definitions/sectionToggle"}},"definitions":{"sectionToggle":{"type":"object","description":"Restrict the section to certain template page types and section group types.","additionalProperties":false,"properties":{"templates":{"type":"array","items":{"type":"string","enum":["*","404","article","blog","captcha","cart","collection","customers/account","customers/activate_account","customers/addresses","customers/login","customers/order","customers/register","customers/reset_password","gift_card","index","list-collections","metaobject","page","password","policy","product","search"]},"uniqueItems":true},"groups":{"type":"array","items":{"type":"string"},"uniqueItems":true}}}}} +``` + +**schemas/setting.json** +``` +{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"type":{"description":"The type of the input or sidebar setting. This value determines the type of field that gets rendered into the editor.","markdownDescription":"The type of the input or sidebar setting. This value determines the type of field that gets rendered into the editor.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings)","enum":["article","blog","checkbox","collection","collection_list","color","color_background","color_scheme","color_scheme_group","font_picker","header","html","image_picker","inline_richtext","link_list","liquid","metaobject","metaobject_list","number","page","paragraph","product","product_list","radio","range","richtext","select","style.layout_panel","style.size_panel","style.spacing_panel","text","text_alignment","textarea","url","video","video_url"]}},"$comment":"We're doing the weird allOf here so that we have better error messages when type is invalid. We're also doing the allOf so that we can use additionalProperties properly.","allOf":[{"if":{"required":["type"],"properties":{"type":{"const":"header"}}},"then":{"$ref":"#/definitions/header"}},{"if":{"required":["type"],"properties":{"type":{"const":"paragraph"}}},"then":{"$ref":"#/definitions/paragraph"}},{"if":{"required":["type"],"properties":{"type":{"const":"article"}}},"then":{"$ref":"#/definitions/article"}},{"if":{"required":["type"],"properties":{"type":{"const":"blog"}}},"then":{"$ref":"#/definitions/blog"}},{"if":{"required":["type"],"properties":{"type":{"const":"checkbox"}}},"then":{"$ref":"#/definitions/checkbox"}},{"if":{"required":["type"],"properties":{"type":{"const":"collection"}}},"then":{"$ref":"#/definitions/collection"}},{"if":{"required":["type"],"properties":{"type":{"const":"collection_list"}}},"then":{"$ref":"#/definitions/collection_list"}},{"if":{"required":["type"],"properties":{"type":{"const":"color"}}},"then":{"$ref":"#/definitions/color"}},{"if":{"required":["type"],"properties":{"type":{"const":"color_background"}}},"then":{"$ref":"#/definitions/color_background"}},{"if":{"required":["type"],"properties":{"type":{"const":"color_scheme"}}},"then":{"$ref":"#/definitions/color_scheme"}},{"if":{"required":["type"],"properties":{"type":{"const":"color_scheme_group"}}},"then":{"$ref":"#/definitions/color_scheme_group"}},{"if":{"required":["type"],"properties":{"type":{"const":"font_picker"}}},"then":{"$ref":"#/definitions/font_picker"}},{"if":{"required":["type"],"properties":{"type":{"const":"html"}}},"then":{"$ref":"#/definitions/html"}},{"if":{"required":["type"],"properties":{"type":{"const":"image_picker"}}},"then":{"$ref":"#/definitions/image_picker"}},{"if":{"required":["type"],"properties":{"type":{"const":"inline_richtext"}}},"then":{"$ref":"#/definitions/inline_richtext"}},{"if":{"required":["type"],"properties":{"type":{"const":"link_list"}}},"then":{"$ref":"#/definitions/link_list"}},{"if":{"required":["type"],"properties":{"type":{"const":"liquid"}}},"then":{"$ref":"#/definitions/liquid"}},{"if":{"required":["type"],"properties":{"type":{"const":"metaobject"}}},"then":{"$ref":"#/definitions/metaobject"}},{"if":{"required":["type"],"properties":{"type":{"const":"metaobject_list"}}},"then":{"$ref":"#/definitions/metaobject_list"}},{"if":{"required":["type"],"properties":{"type":{"const":"number"}}},"then":{"$ref":"#/definitions/number"}},{"if":{"required":["type"],"properties":{"type":{"const":"page"}}},"then":{"$ref":"#/definitions/page"}},{"if":{"required":["type"],"properties":{"type":{"const":"product"}}},"then":{"$ref":"#/definitions/product"}},{"if":{"required":["type"],"properties":{"type":{"const":"product_list"}}},"then":{"$ref":"#/definitions/product_list"}},{"if":{"required":["type"],"properties":{"type":{"const":"radio"}}},"then":{"$ref":"#/definitions/radio"}},{"if":{"required":["type"],"properties":{"type":{"const":"range"}}},"then":{"$ref":"#/definitions/range"}},{"if":{"required":["type"],"properties":{"type":{"const":"richtext"}}},"then":{"$ref":"#/definitions/richtext"}},{"if":{"required":["type"],"properties":{"type":{"const":"select"}}},"then":{"$ref":"#/definitions/select"}},{"if":{"required":["type"],"properties":{"type":{"const":"style.layout_panel"}}},"then":{"$ref":"#/definitions/style.layout_panel"}},{"if":{"required":["type"],"properties":{"type":{"const":"style.size_panel"}}},"then":{"$ref":"#/definitions/style.size_panel"}},{"if":{"required":["type"],"properties":{"type":{"const":"style.spacing_panel"}}},"then":{"$ref":"#/definitions/style.spacing_panel"}},{"if":{"required":["type"],"properties":{"type":{"const":"text"}}},"then":{"$ref":"#/definitions/text"}},{"if":{"required":["type"],"properties":{"type":{"const":"text_alignment"}}},"then":{"$ref":"#/definitions/text_alignment"}},{"if":{"required":["type"],"properties":{"type":{"const":"textarea"}}},"then":{"$ref":"#/definitions/textarea"}},{"if":{"required":["type"],"properties":{"type":{"const":"url"}}},"then":{"$ref":"#/definitions/url"}},{"if":{"required":["type"],"properties":{"type":{"const":"video"}}},"then":{"$ref":"#/definitions/video"}},{"if":{"required":["type"],"properties":{"type":{"const":"video_url"}}},"then":{"$ref":"#/definitions/video_url"}}],"definitions":{"article":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"}],"properties":{"type":{"const":"article","description":"A setting of type article outputs an article picker field that's automatically populated with the available articles for the store. You can use these fields to capture an article selection, such as the article to feature on the homepage.","markdownDescription":"A setting of type `article` outputs an article picker field that's automatically populated with the available articles for the store. You can use these fields to capture an article selection, such as the article to feature on the homepage.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#article)"},"default":true,"label":true,"info":true,"id":true},"additionalProperties":false},"blog":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"}],"properties":{"type":{"const":"blog","description":"A setting of type blog outputs a blog picker field that's automatically populated with the available blogs for the store. You can use these fields to capture a blog selection, such as the blog to feature in the sidebar.","markdownDescription":"A setting of type `blog` outputs a blog picker field that's automatically populated with the available blogs for the store. You can use these fields to capture a blog selection, such as the blog to feature in the sidebar.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#blog)"},"default":true,"label":true,"info":true,"id":true},"additionalProperties":false},"checkbox":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"},{"$ref":"#/definitions/conditionalSetting"}],"properties":{"type":{"const":"checkbox","description":"A setting of type checkbox outputs a checkbox field. These fields can be used for toggling features on and off, such as whether to show an announcement bar.","markdownDescription":"A setting of type `checkbox` outputs a checkbox field. These fields can be used for toggling features on and off, such as whether to show an announcement bar.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#checkbox)"},"default":{"type":"boolean"},"label":true,"info":true,"id":true,"visible_if":true},"additionalProperties":false},"collection":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"}],"properties":{"type":{"const":"collection","description":"A setting of type collection outputs a collection picker field that's automatically populated with the available collections for the store. You can use these fields to capture a collection selection, such as a collection for featuring products on the homepage.","markdownDescription":"A setting of type `collection` outputs a collection picker field that's automatically populated with the available collections for the store. You can use these fields to capture a collection selection, such as a collection for featuring products on the homepage.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#collection)"},"default":true,"label":true,"info":true,"id":true},"additionalProperties":false},"collection_list":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"}],"properties":{"type":{"const":"collection_list","description":"A setting of type collection_list outputs a collection picker field that's automatically populated with the available collections for the store. You can use these fields to capture multiple collections, such as a group of collections to feature on the homepage.","markdownDescription":"A setting of type `collection_list` outputs a collection picker field that's automatically populated with the available collections for the store. You can use these fields to capture multiple collections, such as a group of collections to feature on the homepage.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#collection_list)"},"limit":{"type":"integer","description":"The maximum number that the merchant can select. The default limit, and the maximum limit you can set, is 50."},"default":true,"label":true,"info":true,"id":true},"additionalProperties":false},"color":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"},{"$ref":"#/definitions/conditionalSetting"}],"properties":{"type":{"const":"color","description":"A setting of type color outputs a color picker field. You can use these fields to capture a color selection for various theme elements, such as the body text color.","markdownDescription":"A setting of type `color` outputs a color picker field. You can use these fields to capture a color selection for various theme elements, such as the body text color.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#color)"},"default":{"type":"string"},"label":true,"info":true,"id":true,"visible_if":true,"alpha":{"type":"boolean","description":"A boolean value that determines whether the color picker should include an alpha slider. The default value is true.","markdownDescription":"A boolean value that determines whether the color picker should include an alpha slider. The default value is true.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#color)"}},"additionalProperties":false},"color_background":{"type":"object","allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"},{"$ref":"#/definitions/conditionalSetting"}],"properties":{"type":{"const":"color_background","description":"A setting of type color_background outputs a text field for entering CSS background properties. You can use these fields to capture background settings for various theme elements, such as the store background.","markdownDescription":"A setting of type `color_background` outputs a text field for entering [CSS background](https://developer.mozilla.org/en-US/docs/Web/CSS/background) properties. You can use these fields to capture background settings for various theme elements, such as the store background.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#color_background)"},"default":{"type":"string"},"label":true,"info":true,"id":true,"visible_if":true},"additionalProperties":false},"color_scheme":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"},{"$ref":"#/definitions/conditionalSetting"}],"properties":{"type":{"const":"color_scheme","description":"A setting of type color_scheme outputs a picker with all of the available theme color schemes, and a preview of the selected color scheme.","markdownDescription":"A setting of type `color_scheme` outputs a picker with all of the available theme color schemes, and a preview of the selected color scheme.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#color_scheme)"},"default":{"type":"string"},"label":true,"info":true,"id":true,"visible_if":true},"additionalProperties":false},"color_scheme_group":{"allOf":[{"$ref":"#/definitions/colorSchemeGroupStandardAttributes"}],"properties":{"type":{"const":"color_scheme_group","description":"A setting of type color_scheme_group outputs a color scheme.","markdownDescription":"A setting of type `color_scheme_group` outputs a color scheme which is composed of the following input setting types:\n\n- `header`\n- `color`\n- `color_background`\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#color_scheme_group)"},"definition":{"description":"An array of header, color and color_background input settings.","markdownDescription":"An array of `header`, `color` and `color_background` input settings.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#color_scheme_group)","type":"array","items":{"type":"object","properties":{"type":{"enum":["header","color","color_background"]},"visible_if":{"not":{"type":"string"},"errorMessage":"The property visible_if is not allowed within color_scheme_group"}},"allOf":[{"if":{"required":["type"],"properties":{"type":{"const":"header"}}},"then":{"$ref":"#/definitions/header"}},{"if":{"required":["type"],"properties":{"type":{"const":"color"}}},"then":{"$ref":"#/definitions/color"}},{"if":{"required":["type"],"properties":{"type":{"const":"color_background"}}},"then":{"$ref":"#/definitions/color_background"}}]}},"role":{"type":"object","properties":{"background":{"description":"Renders the background color of the preview","oneOf":[{"type":"string"},{"$ref":"#/definitions/gradient"}]},"text":{"description":"Renders the text color of the preview","type":"string"},"primary_button":{"description":"Renders the first pill in the preview","oneOf":[{"type":"string"},{"$ref":"#/definitions/gradient"}]},"secondary_button":{"description":"Renders the second pill in the preview","oneOf":[{"type":"string"},{"$ref":"#/definitions/gradient"}]},"primary_button_border":{"description":"Render the first pills' border in the preview","type":"string"},"secondary_button_border":{"description":"Render the second pills' border in the preview","type":"string"},"on_primary_button":{"description":"Not used in the preview","type":"string"},"on_secondary_button":{"description":"Not used in the preview","type":"string"},"links":{"description":"Not used in the preview","type":"string"},"icons":{"description":"Not used in the preview","type":"string"}},"required":["background","text","primary_button","secondary_button","primary_button_border","secondary_button_border","on_primary_button","on_secondary_button","links","icons"]},"info":true,"id":true},"required":["definition","role"],"additionalProperties":false},"font_picker":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"},{"$ref":"#/definitions/conditionalSetting"}],"properties":{"type":{"const":"font_picker","description":"A setting of type font_picker outputs a font picker field that's automatically populated with fonts from the Shopify font library. This library includes web-safe fonts, a selection of Google Fonts, and fonts licensed by Monotype.","markdownDescription":"A setting of type `font_picker` outputs a font picker field that's automatically populated with fonts from the [Shopify font library](https://shopify.dev/docs/themes/architecture/settings/fonts#shopify-font-library). This library includes web-safe fonts, a selection of Google Fonts, and fonts licensed by Monotype.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#font_picker)"},"default":{"type":"string","description":"The default font from the Shopify font library.","markdownDescription":"The default font from the [Shopify font library](https://shopify.dev/docs/themes/architecture/settings/fonts#shopify-font-library)."},"label":true,"info":true,"id":true,"visible_if":true},"required":["default"],"additionalProperties":false},"html":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"},{"$ref":"#/definitions/conditionalSetting"}],"properties":{"type":{"const":"html","description":"A setting of type html outputs a multi-line text field that accepts HTML markup.","markdownDescription":"A setting of type `html` outputs a multi-line text field that accepts HTML markup.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#html)"},"default":{"type":"string"},"placeholder":{"type":"string","description":"A placeholder value for the input."},"label":true,"info":true,"id":true,"visible_if":true},"additionalProperties":false},"image_picker":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"},{"$ref":"#/definitions/conditionalSetting"}],"properties":{"type":{"const":"image_picker","description":"A setting of type image_picker outputs an image picker field that's automatically populated with the available images from the Files section of Shopify admin, and has the option to upload new images. Merchants also have an opportunity to enter alt text and select a focal point for their image.","markdownDescription":"A setting of type `image_picker` outputs an image picker field that's automatically populated with the available images from the [Files](https://help.shopify.com/manual/shopify-admin/productivity-tools/file-uploads) section of Shopify admin, and has the option to upload new images. Merchants also have an opportunity to enter alt text and select a [focal point](https://shopify.dev/docs/themes/architecture/settings/input-settings#image-focal-points) for their image.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#image_picker)"},"default":true,"label":true,"info":true,"id":true,"visible_if":true},"additionalProperties":false},"inline_richtext":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"},{"$ref":"#/definitions/conditionalSetting"}],"properties":{"type":{"const":"inline_richtext","description":"A setting of type inline_richtext outputs HTML markup that isn't wrapped in paragraph tags (

)","markdownDescription":"A setting of type `inline_richtext` outputs HTML markup that isn't wrapped in paragraph tags (`

`). The setting includes the following basic formatting options:\n\n- Bold\n- Italic\n- Link\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#inline_richtext)"},"default":{"type":"string"},"label":true,"info":true,"id":true,"visible_if":true},"additionalProperties":false},"link_list":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"},{"$ref":"#/definitions/conditionalSetting"}],"properties":{"type":{"const":"link_list","description":"A setting of type link_list outputs a menu picker field that's automatically populated with the available menus for the store. You can use these fields to capture a menu selection, such as the menu to use for footer links.","markdownDescription":"A setting of type `link_list` outputs a menu picker field that's automatically populated with the available menus for the store. You can use these fields to capture a menu selection, such as the menu to use for footer links.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#link_list)"},"default":true,"label":true,"info":true,"id":true,"visible_if":true},"additionalProperties":false},"liquid":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"},{"$ref":"#/definitions/conditionalSetting"}],"properties":{"type":{"const":"liquid","description":"A setting of type liquid outputs a multi-line text field that accepts HTML and limited Liquid markup. You can use these fields to capture custom blocks of HTML and Liquid content, such as a product-specific message. Merchants can also use a liquid setting to add the code needed to integrate certain types of apps into your theme.","markdownDescription":"A setting of type `liquid` outputs a multi-line text field that accepts HTML and [limited](https://shopify.dev/docs/themes/architecture/settings/input-settings#limitations) Liquid markup. You can use these fields to capture custom blocks of HTML and Liquid content, such as a product-specific message. Merchants can also use a liquid setting to add the code needed to integrate certain types of [apps](https://shopify.dev/docs/apps/online-store) into your theme.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#liquid)"},"default":{"type":"string"},"label":true,"info":true,"id":true,"visible_if":true},"additionalProperties":false},"metaobject":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"}],"properties":{"type":{"const":"metaobject","description":"A setting of type metaobject outputs a metaobject picker field that's automatically populated with the compatible metaobject entries for the store. You can use these fields to capture a metaobject entry selection for a known metaobject type.","markdownDescription":"A setting of type `metaobject` outputs a metaobject picker field that's automatically populated with the compatible metaobject entries for the store. You can use these fields to capture a metaobject entry selection for a known metaobject type. \n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#metaobject)"},"metaobject_type":{"type":"string","description":"The metaobject type allowed by the picker."},"default":true,"label":true,"info":true,"id":true},"required":["metaobject_type"],"additionalProperties":false},"metaobject_list":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"}],"properties":{"type":{"const":"metaobject_list","description":"A setting of type metaobject_list outputs a metaobject picker field that's automatically populated with the compatible metaobject entries for the store. You can use these fields to capture multiple metaobject entry selections for a known metaobject type.","markdownDescription":"A setting of type `metaobject_list` outputs a metaobject picker field that's automatically populated with the compatible metaobject entries for the store. You can use these fields to capture multiple metaobject entry selections for a known metaobject type.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#metaobject_list)"},"metaobject_type":{"type":"string","description":"The metaobject type allowed by the picker."},"limit":{"type":"integer","description":"The maximum number that the merchant can select. The default limit, and the maximum limit you can set, is 50."},"default":true,"label":true,"info":true,"id":true},"required":["metaobject_type"],"additionalProperties":false},"number":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"},{"$ref":"#/definitions/conditionalSetting"}],"properties":{"type":{"const":"number","description":"A setting of type number outputs a single number field.","markdownDescription":"A setting of type `number` outputs a single number field.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#number)"},"placeholder":{"type":"string","description":"A placeholder value for the input."},"default":{"type":"number"},"label":true,"info":true,"id":true,"visible_if":true},"additionalProperties":false},"page":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"}],"properties":{"type":{"const":"page","description":"A setting of type page outputs a page picker field that's automatically populated with the available pages for the store. You can use these fields to capture a page selection, such as the page to feature content for in a size-chart display.","markdownDescription":"A setting of type `page` outputs a page picker field that's automatically populated with the available pages for the store. You can use these fields to capture a page selection, such as the page to feature content for in a size-chart display.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#page)"},"default":true,"label":true,"info":true,"id":true},"additionalProperties":false},"product":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"}],"properties":{"type":{"const":"product","description":"A setting of type product outputs a product picker field that's automatically populated with the available products for the store. You can use these fields to capture a product selection, such as the product to feature on the homepage.","markdownDescription":"A setting of type `product` outputs a product picker field that's automatically populated with the available products for the store. You can use these fields to capture a product selection, such as the product to feature on the homepage.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#product)"},"default":true,"label":true,"info":true,"id":true},"additionalProperties":false},"product_list":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"}],"properties":{"type":{"const":"product_list","description":"A setting of type product_list outputs a product picker field that's automatically populated with the available products for the store. You can use these fields to capture multiple products, such as a group of products to feature on the homepage.","markdownDescription":"A setting of type `product_list` outputs a product picker field that's automatically populated with the available products for the store. You can use these fields to capture multiple products, such as a group of products to feature on the homepage.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#product_list)"},"limit":{"type":"integer","description":"The maximum number that the merchant can select. The default limit, and the maximum limit you can set, is 50."},"default":true,"label":true,"info":true,"id":true},"additionalProperties":false},"radio":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"},{"$ref":"#/definitions/conditionalSetting"}],"properties":{"type":{"const":"radio","description":"A setting of type radio outputs a radio option field.","markdownDescription":"A setting of type `radio` outputs a radio option field.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#radio)"},"default":{"type":"string","description":"The value of the default option"},"options":{"$ref":"#/definitions/options"},"label":true,"info":true,"id":true,"visible_if":true},"required":["options"],"additionalProperties":false},"range":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"},{"$ref":"#/definitions/conditionalSetting"}],"properties":{"type":{"const":"range","description":"A setting of type range outputs a range slider field with an input field.","markdownDescription":"A setting of type `range` outputs a range slider field with an input field.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#range)"},"default":{"type":"number"},"min":{"type":"number","description":"The minimum value of the input"},"max":{"type":"number","description":"The maximum value of the input"},"step":{"type":"number","multipleOf":0.1,"description":"The increment size between steps of the slider"},"unit":{"type":"string","description":"The unit for the input. For example, you can set \"px\" for a font-size slider","markdownDescription":"The unit for the input. For example, you can set `px` for a font-size slider"},"label":true,"info":true,"id":true,"visible_if":true},"required":["default","min","max"],"additionalProperties":false},"richtext":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"},{"$ref":"#/definitions/conditionalSetting"}],"properties":{"type":{"const":"richtext","description":"A setting of type richtext outputs a multi-line text field.","markdownDescription":"A setting of type `richtext` outputs a multi-line text field with the following basic formatting options:\n\n- Bold\n- Italic\n- Underline\n- Link\n- Paragraph\n- Unordered list\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#richtext)"},"default":{"type":"string"},"label":true,"info":true,"id":true,"visible_if":true},"additionalProperties":false},"select":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"},{"$ref":"#/definitions/conditionalSetting"}],"properties":{"type":{"const":"select","description":"A setting of type select outputs different selector fields, depending on certain criteria.","markdownDescription":"A setting of type `select` outputs [different selector fields](https://shopify.dev/docs/themes/architecture/settings/input-settings#selector-fields), depending on certain criteria.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#select)"},"default":{"type":"string","description":"The value of the default option"},"group":{"type":"string","description":"An optional attribute that you can add to each option to create option groups in the drop-down."},"options":{"$ref":"#/definitions/options"},"label":true,"info":true,"id":true,"visible_if":true},"required":["options"],"additionalProperties":false},"lengthPattern":{"type":"string","pattern":"^[0-9]+(px|%)$"},"lengthAutoPattern":{"type":"string","pattern":"^([0-9]+(px|%)|auto|fit-content)$"},"lengthNonePattern":{"type":"string","pattern":"^([0-9]+(px|%)|none|fit-content)$"},"negativeLengthPattern":{"type":"string","pattern":"^-?[0-9]+(px|%)$"},"numberPattern":{"type":"string","pattern":"^[0-9]+$"},"negativeNumberPattern":{"type":"string","pattern":"^-?[0-9]+$"},"style.layout_panel":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"},{"$ref":"#/definitions/conditionalSetting"}],"properties":{"type":{"const":"style.layout_panel","description":"A setting of type style.layout_panel outputs style settings for layout.","markdownDescription":"A setting of type `style.layout_panel` outputs style settings for layout.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#style.layout_panel)"},"default":{"allOf":[{"$ref":"#/definitions/style.flex_layout_properties"}],"properties":{"@media (--mobile)":{"type":"object","description":"Style settings for the mobile breakpoint","$ref":"#/definitions/style.flex_layout_properties","unevaluatedProperties":false}},"unevaluatedProperties":false},"label":true,"info":true,"id":true,"visible_if":true},"additionalProperties":false},"style.size_panel":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"},{"$ref":"#/definitions/conditionalSetting"}],"properties":{"type":{"const":"style.size_panel","description":"A setting of type style.size_panel outputs style settings for size.","markdownDescription":"A setting of type `style.size_panel` outputs style settings for size.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#style.size_panel)"},"default":{"allOf":[{"$ref":"#/definitions/style.size_properties"}],"properties":{"@media (--mobile)":{"type":"object","description":"Style settings for the mobile breakpoint","$ref":"#/definitions/style.size_properties","unevaluatedProperties":false}},"unevaluatedProperties":false},"label":true,"info":true,"id":true,"visible_if":true},"additionalProperties":false},"style.spacing_panel":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"},{"$ref":"#/definitions/conditionalSetting"}],"properties":{"type":{"const":"style.spacing_panel","description":"A setting of type style.spacing_panel outputs style settings for spacing.","markdownDescription":"A setting of type `style.spacing_panel` outputs style settings for spacing.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#style.spacing_panel)"},"default":{"allOf":[{"$ref":"#/definitions/style.spacing_properties"}],"properties":{"@media (--mobile)":{"type":"object","description":"Style settings for the mobile breakpoint","$ref":"#/definitions/style.spacing_properties","unevaluatedProperties":false}},"unevaluatedProperties":false},"label":true,"info":true,"id":true,"visible_if":true},"additionalProperties":false},"text":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"},{"$ref":"#/definitions/conditionalSetting"}],"properties":{"type":{"const":"text","description":"A setting of type text outputs a single-line text field.","markdownDescription":"A setting of type `text` outputs a single-line text field.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#text)"},"default":{"type":"string"},"placeholder":{"type":"string","description":"A placeholder value for the input."},"label":true,"info":true,"id":true,"visible_if":true},"additionalProperties":false},"text_alignment":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"},{"$ref":"#/definitions/conditionalSetting"}],"properties":{"type":{"const":"text_alignment","description":"A setting of type text_alignment outputs a SegmentedControl field with icons.","markdownDescription":"A setting of type `text_alignment` outputs a `SegmentedControl` field with icons.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#text_alignment)"},"default":{"type":"string","enum":["left","right","center"]},"label":true,"info":true,"id":true,"visible_if":true},"additionalProperties":false},"textarea":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"},{"$ref":"#/definitions/conditionalSetting"}],"properties":{"type":{"const":"textarea","description":"A setting of type textarea outputs a multi-line text field.","markdownDescription":"A setting of type `textarea` outputs a multi-line text field\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#textarea)"},"default":{"type":"string"},"placeholder":{"type":"string","description":"A placeholder value for the input."},"label":true,"info":true,"id":true,"visible_if":true},"additionalProperties":false},"url":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"},{"$ref":"#/definitions/conditionalSetting"}],"properties":{"type":{"const":"url","description":"A setting of type url outputs a URL entry field where you can manually enter external URLs and relative paths.","markdownDescription":"A setting of type `url` outputs a URL entry field where you can manually enter external URLs and relative paths.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#url)"},"default":{"type":"string"},"label":true,"info":true,"id":true,"visible_if":true},"additionalProperties":false},"video":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"},{"$ref":"#/definitions/conditionalSetting"}],"properties":{"type":{"const":"video","description":"A setting of type video outputs a video picker that's automatically populated with the available videos from the Files section of the Shopify admin. The merchant also has the option to upload new videos.","markdownDescription":"A setting of type `video` outputs a video picker that's automatically populated with the available videos from the [Files](https://help.shopify.com/en/manual/shopify-admin/productivity-tools/file-uploads) section of the Shopify admin. The merchant also has the option to upload new videos.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#video)"},"default":true,"label":true,"info":true,"id":true,"visible_if":true},"additionalProperties":false},"video_url":{"allOf":[{"$ref":"#/definitions/inputSettingsStandardAttributes"},{"$ref":"#/definitions/conditionalSetting"}],"properties":{"type":{"const":"video_url","description":"A setting of type video_url outputs a URL entry field.","markdownDescription":"A setting of type `video_url` outputs a URL entry field.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#video_url)"},"placeholder":{"type":"string","description":"A placeholder value for the input."},"accept":{"description":"Takes an array of accepted video providers. Valid values are youtube, vimeo, or both.","markdownDescription":"Takes an array of accepted video providers. Valid values are `youtube`, `vimeo`, or both.","type":"array","items":{"type":"string","enum":["youtube","vimeo"]}},"default":true,"label":true,"info":true,"id":true,"visible_if":true},"required":["accept"],"additionalProperties":false},"header":{"allOf":[{"$ref":"#/definitions/sidebarStandardSettings"},{"$ref":"#/definitions/conditionalSetting"}],"properties":{"type":{"const":"header","description":"A setting of type `header` outputs a header element to help you better organize your input settings.","markdownDescription":"A setting of type `header` outputs a header element to help you better organize your input settings.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/sidebar-settings#header)"},"info":{"$ref":"#/definitions/info"},"content":true,"visible_if":true},"additionalProperties":false},"paragraph":{"allOf":[{"$ref":"#/definitions/sidebarStandardSettings"},{"$ref":"#/definitions/conditionalSetting"}],"properties":{"type":{"const":"paragraph","description":"A setting of type paragraph outputs a text element to help you better describe your input settings.","markdownDescription":"A setting of type `paragraph` outputs a text element to help you better describe your input settings.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/sidebar-settings#paragraph)"},"content":true,"visible_if":true},"additionalProperties":false},"sidebarStandardSettings":{"$comment":"Sidebar standard settings","required":["type","content"],"properties":{"content":{"type":"string","description":"The setting content, which will show in the theme editor."}}},"conditionalSetting":{"$comment":"Conditional setting property","properties":{"visible_if":{"type":"string","description":"A liquid expression that determines whether the setting should be visible.","markdownDescription":"A liquid expression that determines whether the setting should be visible.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#visible_if)"}}},"inputSettingsStandardAttributes":{"required":["type","id","label"],"properties":{"id":{"type":"string","description":"The unique identifier for the setting, which is used to access the setting value.","markdownDescription":"The unique identifier for the setting, which is used to access the setting value.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#standard-attributes)"},"label":{"type":"string","description":"The label for the setting, which will show in the theme editor."},"default":{"description":"The default value for the setting."},"info":{"$ref":"#/definitions/info"}}},"colorSchemeGroupStandardAttributes":{"required":["type","id"],"properties":{"id":{"type":"string","description":"The unique identifier for the setting, which is used to access the setting value.","markdownDescription":"The unique identifier for the setting, which is used to access the setting value.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings/input-settings#standard-attributes)"}}},"info":{"type":"string","description":"An option for informational text about the setting."},"gradient":{"type":"object","required":["solid","gradient"],"properties":{"solid":{"type":"string"},"gradient":{"type":"string"}}},"style.flex_layout_properties":{"type":"object","properties":{"flex-direction":{"type":"string","description":"Determines how flex items are arranged within the flex container by specifying the main axis and direction. Valid values are row (default), row-reverse, column, and column-reverse.","enum":["row","row-reverse","column","column-reverse"]},"flex-wrap":{"type":"string","description":"Specifies whether flex items are confined to a single line or can flow onto multiple lines. If wrapping is allowed, it defines the stacking direction of the lines. Valid values are nowrap (default), wrap, and wrap-reverse.","enum":["nowrap","wrap","wrap-reverse"]},"row-gap":{"$ref":"#/definitions/lengthPattern","description":"Defines the size of the gap between the rows of a wrapped flex container."},"column-gap":{"$ref":"#/definitions/lengthPattern","description":"Defines the size of the gap between flex items."},"gap":{"type":"string","description":"Shorthand for row-gap and column-gap. Defines the gaps between flex items.","pattern":"^([0-9]+(px|%) ?){1,2}$"},"justify-content":{"type":"string","description":"Defines the alignment of flex items along the main axis of the flex container. Valid values are flex-start (default), flex-end, start, end, left, right, center, space-between, space-around and space-evenly.","enum":["flex-start","flex-end","start","end","left","right","center","space-between","space-around","space-evenly"]},"align-items":{"type":"string","description":"Defines the alignment of flex items along the cross axis of the flex container. Valid values are stretch (default), flex-start / start / self-start, flex-end / end / self-end, center and baseline.","enum":["stretch","flex-start","start","self-start","flex-end","end","self-end","center","baseline"]},"align-content":{"type":"string","description":"Defines the distribution of space between and around flex items along the cross axis. Valid values are stretch, flex-start / start, flex-end / end, center, space-between, space-around, and space-evenly.","enum":["stretch","flex-start","start","flex-end","end","center","space-between","space-around","space-evenly"]}}},"style.size_properties":{"type":"object","properties":{"flex-grow":{"$ref":"#/definitions/numberPattern","description":"Defines the flex grow factor of a flex item, determining how much of the available space in the flex container the item should occupy."},"flex-shrink":{"$ref":"#/definitions/numberPattern","description":"Defines the flex shrink factor of a flex item, determining how much the item should reduce in size compared to the other flex items when space is insufficient."},"flex-basis":{"$ref":"#/definitions/lengthAutoPattern","description":"Defines the initial main size of a flex item, determining the size of the content-box unless otherwise specified by box-sizing."},"width":{"$ref":"#/definitions/lengthAutoPattern","description":"Defines the width of an item."},"min-width":{"$ref":"#/definitions/lengthAutoPattern","description":"Defines the minimum width of an item."},"max-width":{"$ref":"#/definitions/lengthNonePattern","description":"Defines the maximum width of an item."},"height":{"$ref":"#/definitions/lengthAutoPattern","description":"Defines the height of an item."},"min-height":{"$ref":"#/definitions/lengthAutoPattern","description":"Defines the minimum height of an item."},"max-height":{"$ref":"#/definitions/lengthNonePattern","description":"Defines the maximum height of an item."}}},"style.spacing_properties":{"type":"object","properties":{"padding":{"description":"Shorthand that defines the padding on all four sides of an element.","pattern":"^([0-9]+(px|%) ?){1,4}$"},"padding-top":{"description":"Defines the padding on the top side of an element.","$ref":"#/definitions/lengthPattern"},"padding-right":{"description":"Defines the padding on the right side of an element.","$ref":"#/definitions/lengthPattern"},"padding-bottom":{"description":"Defines the padding on the bottom side of an element.","$ref":"#/definitions/lengthPattern"},"padding-left":{"description":"Defines the padding on the left side of an element.","$ref":"#/definitions/lengthPattern"},"padding-block-start":{"description":"Defines the logical block start padding of an element, translating to physical padding based on the element's writing mode, text direction, and text orientation.","$ref":"#/definitions/lengthPattern"},"padding-block-end":{"description":"Defines the logical block end padding of an element, translating to physical padding based on the element's writing mode, text direction, and text orientation.","$ref":"#/definitions/lengthPattern"},"padding-block":{"type":"string","description":"Shorthand for padding-block-start and padding-block end. Defines the logical block start and end padding of an element, translating to physical padding based on the element's writing mode, text direction, and text orientation.","pattern":"^([0-9]+(px|%) ?){1,2}$"},"padding-inline-start":{"description":"Defines the logical inline start padding of an element, translating to physical padding based on the element's writing mode, text direction, and text orientation.","$ref":"#/definitions/lengthPattern"},"padding-inline-end":{"description":"Defines the logical inline end padding of an element, translating to physical padding based on the element's writing mode, text direction, and text orientation.","$ref":"#/definitions/lengthPattern"},"padding-inline":{"type":"string","description":"Shorthand that defines the logical inline start and end padding of an element, translating to physical padding based on the element's writing mode, text direction, and text orientation.","pattern":"^([0-9]+(px|%) ?){1,2}$"},"margin":{"type":"string","description":"Shorthand that defines the margin on all four sides of an element.","pattern":"^(-?[0-9]+(px|%) ?){1,4}$"},"margin-top":{"description":"Defines the margin on the top side of an element.","$ref":"#/definitions/negativeLengthPattern"},"margin-right":{"description":"Defines the margin on the right side of an element.","$ref":"#/definitions/negativeLengthPattern"},"margin-bottom":{"description":"Defines the margin on the bottom side of an element.","$ref":"#/definitions/negativeLengthPattern"},"margin-left":{"description":"Defines the margin on the left side of an element.","$ref":"#/definitions/negativeLengthPattern"},"margin-block-start":{"description":"Defines the logical block start margin of an element, translating to physical margin based on the element's writing mode, text direction, and text orientation.","$ref":"#/definitions/negativeLengthPattern"},"margin-block-end":{"description":"Defines the logical block end margin of an element, translating to physical margin based on the element's writing mode, text direction, and text orientation.","$ref":"#/definitions/negativeLengthPattern"},"margin-block":{"type":"string","description":"Shorthand that defines the logical block start and end margins of an element, translating to physical margin based on the element's writing mode, text direction, and text orientation.","pattern":"^(-?[0-9]+(px|%) ?){1,2}$"},"margin-inline-start":{"description":"Defines the logical inline start margin of an element, translating to physical margin based on the element's writing mode, text direction, and text orientation.","$ref":"#/definitions/negativeLengthPattern"},"margin-inline-end":{"description":"Defines the logical inline end margin of an element, translating to physical margin based on the element's writing mode, text direction, and text orientation.","$ref":"#/definitions/negativeLengthPattern"},"margin-inline":{"type":"string","description":"Shorthand that defines both the logical inline start and end margins of an element, translating to physical margin based on the element's writing mode, text direction, and text orientation.","pattern":"^(-?[0-9]+(px|%) ?){1,2}$"}}},"options":{"description":"Takes an array of `value`/`label` definitions.","type":"array","items":{"type":"object","properties":{"value":{"description":"The value of the option.","type":"string"},"label":{"description":"The label of the option.","type":"string"}},"required":["value","label"]}}}} +``` + +**schemas/settings.json** +``` +{"$schema":"http://json-schema.org/draft-07/schema#","description":"Settings that merchants can configure through the theme editor.","markdownDescription":"Settings that merchants can configure through the [theme editor](https://help.shopify.com/en/manual/online-store/themes/customizing-themes#theme-editor)\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/settings).","type":"array","items":{"$ref":"./setting.json"},"minItems":0} +``` + +**schemas/targetted_block_entry.json** +``` +{"$schema":"http://json-schema.org/draft-07/schema#","$comment":"A targetted private or public theme block from the blocks/ folder.","type":"object","required":["type"],"additionalProperties":false,"properties":{"type":{"type":"string","pattern":"^[a-zA-Z0-9_-]+$","description":"The name of a theme block found in the blocks/ folder of the theme.","markdownDescription":"The name of a theme block found in the `blocks/` folder of the theme.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/blocks/theme-blocks/schema#blocks)"}}} +``` + +**schemas/theme_block_entry.json** +``` +{"$schema":"http://json-schema.org/draft-07/schema#","$comment":"A @theme block entry","type":"object","additionalProperties":false,"required":["type"],"properties":{"type":{"const":"@theme","description":"The \"@theme\" type denotes that this container accepts theme blocks that live in the blocks/ folder of the theme.","markdownDescription":"The `@theme` type denotes that this container accepts theme blocks that live in the `blocks/` folder of the theme.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/blocks/theme-blocks/schema#blocks)"}}} +``` + +**schemas/theme_block.json** +``` +{"$schema":"http://json-schema.org/draft-07/schema#","title":"Shopify Liquid Theme Block Schema","type":"object","additionalProperties":false,"properties":{"name":{"type":"string","description":"The name attribute determines the block title that's shown in the theme editor.","markdownDescription":"The `name` attribute determines the block title that's shown in the theme editor.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/blocks/theme-blocks/schema#name)"},"settings":{"$ref":"./settings.json"},"blocks":{"type":"array","description":"Theme blocks can accept other app and theme blocks as children using the blocks attribute of their schema.","markdownDescription":"Theme blocks can accept other app and theme blocks as children using the `blocks` attribute of their schema.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/blocks/theme-blocks/schema#blocks)","items":{"type":"object","additionalProperties":false,"required":["type"],"properties":{"type":{"type":"string","description":"The type of block that can be added to this block.","markdownDescription":"The type of block that can be added to this block.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/blocks/theme-blocks/schema#blocks)"}},"$comment":"The allOf rule here exists because that's how we do discriminated unions in JSON schemas. If a rule matches, that rule will be used to document the type property. Otherwise we fallback to the docs above.","allOf":[{"if":{"required":["type"],"properties":{"type":{"const":"@theme"}}},"then":{"$ref":"./theme_block_entry.json"}},{"if":{"required":["type"],"properties":{"type":{"const":"@app"}}},"then":{"$ref":"./app_block_entry.json"}},{"if":{"required":["type"],"properties":{"type":{"type":"string","not":{"enum":["@app","@theme"]}}}},"then":{"$ref":"./targetted_block_entry.json"}}]}},"presets":{"type":"array","description":"Presets are default configurations of blocks that enable merchants to easily add a block to a JSON template through the theme editor.","markdownDescription":"Presets are default configurations of blocks that enable merchants to easily add a block to a JSON template through the theme editor.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/blocks/theme-blocks/schema#presets)","items":{"$ref":"./preset.json"}},"tag":{"description":"The HTML element that is used to wrap the rendered block. Accepts any string up to 50 characters. Can be used to render custom HTML elements. Use null to render without a wrapping element.","markdownDescription":"The HTML element that is used to wrap the rendered block. Accepts any string up to 50 characters. Can be used to render custom HTML elements.\n\nUse `null` to render without a wrapping element.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/blocks/theme-blocks/schema#tag)","oneOf":[{"description":"If you don't want to use a

, then you can specify which kind of HTML element to use.","markdownDescription":"If you don't want to use a `
`, then you can specify which kind of HTML element to use.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/blocks/theme-blocks/schema#tag)","type":"string","maxLength":50},{"description":"Used to render the block without a wrapping element.","markdownDescription":"Used to render the block without a wrapping element.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/blocks/theme-blocks/schema#rendering-blocks-without-a-wrapper)","type":"null"}]},"class":{"type":"string","description":"When Shopify renders a block, it's wrapped in an HTML element with the shopify-block class. You can append other classes by using the class attribute.","markdownDescription":"When Shopify renders a block, it's wrapped in an HTML element with the `shopify-block` class. You can append other classes by using the class attribute.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/blocks/theme-blocks/schema#class)"}}} +``` + +**schemas/theme_settings.json** +``` +{"$schema":"http://json-schema.org/draft-07/schema#","title":"JSON schema for config/settings_schema.json files.","description":"The settings that merchants can configure in the theme editor.","markdownDescription":"The settings that merchants can configure in the theme editor.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/config/settings-schema-json)","type":"array","items":{"anyOf":[{"title":"Theme metadata","markdownDescription":"Additional metadata for your theme that shows up in the Theme actions menu of the theme editor.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/config/settings-schema-json#add-theme-metadata)","type":"object","properties":{"name":{"description":"You may use the 'theme_info' object for theme metadata.","markdownDescription":"[Shopify reference](https://shopify.dev/docs/themes/architecture/config/settings-schema-json#add-theme-metadata)","const":"theme_info"},"theme_name":{"type":"string","description":"The name of the theme."},"theme_author":{"type":"string","description":"The author of the theme."},"theme_version":{"type":"string","description":"The version number of the theme."},"theme_documentation_url":{"type":"string","format":"uri","description":"A URL where merchants can find documentation for the theme."},"theme_support_email":{"type":"string","format":"email","description":"An email address that merchants can contact for support for the theme."},"theme_support_url":{"type":"string","format":"uri","description":"A URL where merchants can find support for the theme."}},"required":["name","theme_name","theme_author","theme_version","theme_documentation_url"],"oneOf":[{"required":["theme_support_email"],"not":{"required":["theme_support_url"]}},{"required":["theme_support_url"],"not":{"required":["theme_support_email"]}}]},{"type":"object","properties":{"name":{"type":"string","description":"The name of the category of settings.","markdownDescription":"The name of the category of settings.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/config/settings-schema-json#schema)","not":{"const":"theme_info"}},"settings":{"$ref":"./settings.json"}}}]}} +``` + +**schemas/translations.json** +``` +{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"$ref":"#/definitions/pluralizedString"},{"$ref":"#"}]},"patternProperties":{".*_html$":{"oneOf":[{"type":"string","description":"A string that can contain HTML content, typically used for translations that include HTML tags."},{"$ref":"#/definitions/pluralizedString"}]}},"definitions":{"pluralizedString":{"type":"object","properties":{"one":{"type":"string","description":"Translation for the singular form"},"other":{"type":"string","description":"Translation for the plural form"},"few":{"type":"string","description":"Translation for 'few' form, used in some languages"},"many":{"type":"string","description":"Translation for 'many' form, used in some languages"},"two":{"type":"string","description":"Translation for 'two' form, used in some languages"},"zero":{"type":"string","description":"Translation for 'zero' form, used in some languages"}},"additionalProperties":false,"description":"An object representing a pluralized translation string"}}} +``` + + diff --git a/ai/cursor/rules/sections.mdc b/ai/cursor/rules/sections.mdc new file mode 100644 index 0000000..a6a1bcb --- /dev/null +++ b/ai/cursor/rules/sections.mdc @@ -0,0 +1,84 @@ +--- +description: Section coding standards and best practices guide +globs: sections/*.liquid +alwaysApply: false +--- +# Section development standards + +## Section requirements + +Every section must include: + +- `{% schema %}` tag with valid JSON +- Proper HTML semantic structure +- CSS scoping with section classes +- Translation keys for all text + +## Section patterns + +**Basic section structure:** + +```liquid +{% liquid + assign section_id = section.settings.custom_id | default: section.id + assign section_class = 'section-' | append: section.type +%} + +
+
+ {% content_for 'blocks %} +
+
+ +{% stylesheet %} +.{{ section_class }} { + padding-top: var(--section-padding-top, 40px); + padding-bottom: var(--section-padding-bottom, 40px); +} +{% endstylesheet %} + +{% schema %} +{ + "name": "t:names.section_name", + "tag": "section", + "class": "section-name", + "blocks": [ + {"type": "@theme"}, + {"type": "@app"} + ], + "settings": [ + { + "type": "range", + "id": "padding_top", + "label": "t:settings.padding", + "min": 0, + "max": 100, + "default": 40, + "unit": "px" + } + ], + "presets": [ + { + "name": "t:names.section_name" + } + ] +} +{% endschema %} +``` + +## Performance patterns + +- Use `{% liquid %}` for multiline logic +- Lazy load images with `loading="lazy"` +- Scope CSS variables to section +- Use `container-queries` for responsive behavior + +[section-example.liquid](mdc:.cursor/rules/examples/section-example.liquid) + diff --git a/ai/cursor/rules/settings-schema.mdc b/ai/cursor/rules/settings-schema.mdc new file mode 100644 index 0000000..409b897 --- /dev/null +++ b/ai/cursor/rules/settings-schema.mdc @@ -0,0 +1,51 @@ +--- +description: Guidelines and examples for organizing and structuring the Shopify theme settings schema. +globs: config/settings_schema.json +alwaysApply: false +--- +# Settings schema standards + +## Settings schema structure + +```json +{ + "name": "theme_info", + "theme_name": "Theme Name", + "theme_version": "1.0.0", + "theme_author": "Author Name", + "theme_documentation_url": "https://...", + "theme_support_url": "https://..." +}, +{ + "name": "Colors", + "settings": [ + { + "type": "header", + "content": "Brand Colors" + }, + { + "type": "color", + "id": "color_primary", + "label": "Primary", + "default": "#121212" + } + ] +} +``` + +## Setting categories + +**Typography:** +- `font_picker` for font selections +- `range` for font sizes (12-72px) +- `select` for font weights + +**Layout:** +- `range` for spacing (0-100px) +- `select` for layout options +- `checkbox` for feature toggles + +**Performance:** +- `checkbox` for lazy loading +- `select` for image quality +- `number` for pagination limits diff --git a/ai/cursor/rules/snippets.mdc b/ai/cursor/rules/snippets.mdc new file mode 100644 index 0000000..ccff752 --- /dev/null +++ b/ai/cursor/rules/snippets.mdc @@ -0,0 +1,119 @@ +--- +description: Snippets coding standards and best practices guide +globs: snippets/*.liquid +alwaysApply: false +--- + +# Snippet development standards + +## Snippet documentation + +Every snippet must include JSDoc-style comments using LiquidDoc: + +```liquid +{% doc %} + Product Card Component + + Renders a product card with customizable options. + + @param {product} product - product object (required) + @param {boolean} show_vendor - display vendor name (default: false) + @param {boolean} show_quick_add - show quick add button (default: false) + @param {string} image_ratio - image aspect ratio (default: 'adapt') + @param {boolean} lazy_load - enable lazy loading (default: true) + @param {string} card_class - Additional CSS classes + + @example + {% render 'product-card', + product: product, + show_vendor: true, + image_ratio: 'square' + %} +{% enddoc %} +``` + +## Parameter handling + +Always provide defaults and validate parameters: + +```liquid +{% liquid + # Parameter validation and defaults + assign product = product | default: empty + assign show_vendor = show_vendor | default: false + assign show_quick_add = show_quick_add | default: false + assign image_ratio = image_ratio | default: 'adapt' + assign lazy_load = lazy_load | default: true + assign card_class = card_class | default: '' + + # Early return if required parameters missing + unless product != empty + echo '' + break + endunless +%} +``` + +## Common snippet patterns + +**Icon snippet:** +```liquid +{% doc %} + @param {string} icon - Icon name (required) + @param {string} size - Icon size class (default: 'icon--medium') + @param {string} css_class - Additional CSS class +{% enddoc %} + +{% liquid + assign icon = icon | default: '' + assign size = size | default: 'icon--medium' + assign css_class = css_class | default: '' + + unless icon != blank + break + endunless +%} + + +``` + +**Price snippet:** +```liquid +{% doc %} + @param {product} product - Product object (required) + @param {boolean} show_compare_at - Show compare at price (default: true) + @param {boolean} show_unit_price - Show unit price (default: false) +{% enddoc %} + +{% liquid + assign show_compare_at = show_compare_at | default: true + assign show_unit_price = show_unit_price | default: false +%} + +
+
+ {{ product.price | money }} +
+ + {% if show_compare_at and product.compare_at_price > product.price %} +
+ {{ product.compare_at_price | money }} +
+ {% endif %} + + {% if show_unit_price and product.selected_or_first_available_variant.unit_price_measurement %} +
+ {{ product.selected_or_first_available_variant.unit_price | money }}/ + {%- if product.selected_or_first_available_variant.unit_price_measurement.reference_value != 1 -%} + {{ product.selected_or_first_available_variant.unit_price_measurement.reference_value }} + {%- endif -%} + {{ product.selected_or_first_available_variant.unit_price_measurement.reference_unit }} +
+ {% endif %} +
+``` + +[snippet-example.liquid](mdc:.cursor/rules/examples/snippet-example.liquid) + diff --git a/ai/github/copilot-instructions.md b/ai/github/copilot-instructions.md new file mode 100644 index 0000000..0960bbb --- /dev/null +++ b/ai/github/copilot-instructions.md @@ -0,0 +1,1475 @@ +🚨 MANDATORY: YOU MUST CALL "learn_shopify_api" ONCE WHEN WORKING WITH LIQUID THEMES. + +## Theme Architecture + +**Key principles: focus on generating snippets, blocks, and sections; users may create templates using the theme editor** + +### Directory structure + +``` +. +├── assets # Stores static assets (CSS, JS, images, fonts, etc.) +├── blocks # Reusable, nestable, customizable components +├── config # Global theme settings and customization options +├── layout # Top-level wrappers for pages (layout templates) +├── locales # Translation files for theme internationalization +├── sections # Modular full-width page components +├── snippets # Reusable Liquid code or HTML fragments +└── templates # Templates combining sections and blocks to define page structures +``` + +#### `sections` + +- Sections are `.liquid` files that allow you to create reusable modules that can be customized by merchants +- Sections can include blocks which allow merchants to add, remove, and reorder content within a section +- Sections are made customizable by including the required `{% schema %}` tag that exposes settings in the theme editor via a JSON object. Validate that JSON object using the `schemas/section.json` JSON schema +- Examples of sections: hero banners, product grids, testimonials, featured collections + +#### `blocks` + +- Blocks are `.liquid` files that allow you to create reusable small components that can be customized by merchants (they don't need to fit the full-width of the page) +- Blocks are ideal for logic that needs to be reused and also edited in the theme editor by merchants +- Blocks can include other nested blocks which allow merchants to add, remove, and reorder content within a block too +- Blocks are made customizable by including the required `{% schema %}` tag that exposes settings in the theme editor via a JSON object. Validate that JSON object using the `schemas/theme_block.json` JSON schema +- Blocks must have the `{% doc %}` tag as the header if you directly/staticly render them in other file via `{% content_for 'block', id: '42', type: 'block_name' %}` +- Examples of blocks: individual testimonials, slides in a carousel, feature items + +#### `snippets` + +- Snippets are reusable code fragments rendered in blocks, sections, and layouts files via the `render` tag +- Snippets are ideal for logic that needs to be reused but not directly edited in the theme editor by merchants +- Snippets accept parameters when rendered for dynamic behavior +- Snippets must have the `{% doc %}` tag as the header +- Examples of sections: buttons, meta-tags, css-variables, and form elements + +#### `layout` + +- Defines the overall HTML structure of the site, including `` and ``, and wraps other templates to provide a consistent frame +- Contains repeated global elements like navigation, cart drawer, footer, and usually includes CSS/JS assets and meta tags +- Must include `{{ content_for_header }}` to inject Shopify scripts in the `` and `{{ content_for_layout }}` to render the page content + +#### `config` + +- `config/settings_schema.json` is a JSON file that defines schema for global theme settings. Validate the shape shape of this JSON file using the `schemas/theme_settings.json` JSON schema +- `config/settings_data.json` is JSON file that holds the data for the settings defined by `config/settings_schema.json` + +#### `assets` + +- Contains static files like CSS, JavaScript, and images—including compiled and optimized assets—referenced in templates via the `asset_url` filter +- Keep it here only `critical.css` and static files necessary for every page, otherwise prefer the usage of the `{% stylesheet %}` and `{% javascript %}` tags + +#### `locales` + +- Stores translation files organized by language code (e.g., `en.default.json`, `fr.json`) to localize all user-facing theme content and editor strings +- Enables multi-language support by providing translations accessible via filters like `{{ 'key' | t }}` in Liquid for proper internationalization +- Validate `locales` JSON files using the `schemas/translations.json` JSON schema + +#### `templates` + +- JSON file that define the structure, ordering, and which sections and blocks appear on each page type, allowing merchants to customize layouts without code changes + +### CSS & JavaScript + +- Write CSS and JavaScript per components using the `{% stylesheet %}` and `{% javascript %}` tags +- Note: `{% stylesheet %}` and `{% javascript %}` are only supported in `snippets/`, `blocks/`, and `sections/` + +### LiquidDoc + +Snippets and blocks (when blocks are statically rendered) must include the LiquidDoc header that documents the purpose of the file and required parameters. Example: + +```liquid +{% doc %} + Renders a responsive image that might be wrapped in a link. + + @param {image} image - The image to be rendered + @param {string} [url] - An optional destination URL for the image + + @example + {% render 'image', image: product.featured_image %} +{% enddoc %} + +{{ image | image_url: width: 200, height: 200 | image_tag }} +``` + +## The `{% schema %}` tag on blocks and sections + +**Key principles: follow the "Good practices" and "Validate the `{% schema %}` content" using JSON schemas** + +### Good practices + +When defining the `{% schema %}` tag on sections and blocks, follow these guidelines to use the values: + +**Single property settings**: For settings that correspond to a single CSS property, use CSS variables: +```liquid +
+ Example +
+ +{% stylesheet %} + .collection { + gap: var(--gap); + } +{% endstylesheet %} + +{% schema %} +{ + "settings": [{ + "type": "range", + "label": "gap", + "id": "gap", + "min": 0, + "max": 100, + "unit": "px", + "default": 0, + }] +} +{% endschema %} +``` + +**Multiple property settings**: For settings that control multiple CSS properties, use CSS classes: +```liquid +
+ Example +
+ +{% stylesheet %} + .collection--full-width { + /* multiple styles */ + } + .collection--narrow { + /* multiple styles */ + } +{% endstylesheet %} + +{% schema %} +{ + "settings": [{ + "type": "select", + "id": "layout", + "label": "layout", + "values": [ + { "value": "collection--full-width", "label": "t:options.full" }, + { "value": "collection--narrow", "label": "t:options.narrow" } + ] + }] +} +{% endschema %} +``` + +#### Mobile layouts + +If you need to create a mobile layout and you want the merchant to be able to select one or two columns, use a select input: + +```liquid +{% schema %} +{ + "type": "select", + "id": "columns_mobile", + "label": "Columns on mobile", + "options": [ + { "value": 1, "label": "1" }, + { "value": "2", "label": "2" } + ] +} +{% endschema %} +``` + +## Liquid + +### Liquid delimiters + +- **`{{ ... }}`**: Output – prints a value. +- **`{{- ... -}}`**: Output, trims whitespace around the value. +- **`{% ... %}`**: Logic/control tag (if, for, assign, etc.), does not print anything, no whitespace trim. +- **`{%- ... -%}`**: Logic/control tag, trims whitespace around the tag. + +**Tip:** +Adding a dash (`-`) after `{%`/`{{` or before `%}`/`}}` trims spaces or newlines next to the tag. + +**Examples:** +- `{{- product.title -}}` → print value, remove surrounding spaces or lines. +- `{%- if available -%}In stock{%- endif -%}` → logic, removes extra spaces/lines. + +### Liquid operators + +**Comparison operators:** +- == +- != +- > +- < +- >= +- <= + +**Logical operators:** +- `or` +- `and` +- `contains` - checks if a string contains a substring, or if an array contains a string + +#### Comparison and comparison tags + +**Key condition principles:** +- For simplificity, ALWAYS use nested `if` conditions when the logic requires more than one logical operator +- Parentheses are not supported in Liquid +- Ternary conditionals are not supported in Liquid, so always use `{% if cond %}` + +**Basic comparison example:** +```liquid +{% if product.title == "Awesome Shoes" %} + These shoes are awesome! +{% endif %} +``` + +**Multiple Conditions:** +```liquid +{% if product.type == "Shirt" or product.type == "Shoes" %} + This is a shirt or a pair of shoes. +{% endif %} +``` + +**Contains Usage:** +- For strings: `{% if product.title contains "Pack" %}` +- For arrays: `{% if product.tags contains "Hello" %}` +- Note: `contains` only works with strings, not objects in arrays + +**{% elsif %} (used inside if/unless only)** +```liquid +{% if a %} + ... +{% elsif b %} + ... +{% endif %} +``` + +**{% unless %}** +```liquid +{% unless condition %} + ... +{% endunless %} +``` + +**{% case %}** +```liquid +{% case variable %} + {% when 'a' %} + a + {% when 'b' %} + b + {% else %} + other +{% endcase %} +``` + +**{% else %} (used inside if, unless, case, or for)** +```liquid +{% if product.available %} + In stock +{% else %} + Sold out +{% endif %} +``` +_or inside a for loop:_ +```liquid +{% for item in collection.products %} + {{ item.title }} +{% else %} + No products found. +{% endfor %} +``` + +#### Variables and variable tags + +```liquid +{% assign my_variable = 'value' %} + +{% capture my_variable %} + Contents of variable +{% endcapture %} + +{% increment counter %} +{% decrement counter %} +``` + +### Liquid filters + +You can chain filters in Liquid, passing the result of one filter as the input to the next. + +See these filters: + +- `upcase`: `{{ string | upcase }}` returns a **string** +- `split`: `{{ string | split: string }}` returns an **array** (as we may notice in the docs, `split` receives a string as its argument) +- `last`: `{{ array | last }}` returns **untyped** + +Each filter can pass its return value to the next filter as long as the types match. + +For example, `upcase` returns a string, which is suitable input for `split`, which then produces an array for `last` to use. + +Here's how the filters are executed step by step to eventually return `"WORLD"`: + +```liquid +{{ "hello world" | upcase | split: " " | last }} +``` + +- First, `"hello world"` is converted to uppercase: `"HELLO WORLD"`, which is a string +- Next, `split` can act on strings, so it splits the value by space into an array: `["HELLO", "WORLD"]` +- Finally, the `last` filter work with array, so `"WORLD"` is returned + +#### Array +- `compact`: `{{ array | compact }}` returns `array` +- `concat`: `{{ array | concat: array }}` returns `array` +- `find`: `{{ array | find: string, string }}` returns `untyped` +- `find_index`: `{{ array | find_index: string, string }}` returns `number` +- `first`: `{{ array | first }}` returns `untyped` +- `has`: `{{ array | has: string, string }}` returns `boolean` +- `join`: `{{ array | join }}` returns `string` +- `last`: `{{ array | last }}` returns `untyped` +- `map`: `{{ array | map: string }}` returns `array` +- `reject`: `{{ array | reject: string, string }}` returns `array` +- `reverse`: `{{ array | reverse }}` returns `array` +- `size`: `{{ variable | size }}` returns `number` +- `sort`: `{{ array | sort }}` returns `array` +- `sort_natural`: `{{ array | sort_natural }}` returns `array` +- `sum`: `{{ array | sum }}` returns `number` +- `uniq`: `{{ array | uniq }}` returns `array` +- `where`: `{{ array | where: string, string }}` returns `array` + +#### Cart +- `item_count_for_variant`: `{{ cart | item_count_for_variant: {variant_id} }}` returns `number` +- `line_items_for`: `{{ cart | line_items_for: object }}` returns `array` + +#### Collection +- `link_to_type`: `{{ string | link_to_type }}` returns `string` +- `link_to_vendor`: `{{ string | link_to_vendor }}` returns `string` +- `sort_by`: `{{ string | sort_by: string }}` returns `string` +- `url_for_type`: `{{ string | url_for_type }}` returns `string` +- `url_for_vendor`: `{{ string | url_for_vendor }}` returns `string` +- `within`: `{{ string | within: collection }}` returns `string` +- `highlight_active_tag`: `{{ string | highlight_active_tag }}` returns `string` + +#### Color +- `brightness_difference`: `{{ string | brightness_difference: string }}` returns `number` +- `color_brightness`: `{{ string | color_brightness }}` returns `number` +- `color_contrast`: `{{ string | color_contrast: string }}` returns `number` +- `color_darken`: `{{ string | color_darken: number }}` returns `string` +- `color_desaturate`: `{{ string | color_desaturate: number }}` returns `string` +- `color_difference`: `{{ string | color_difference: string }}` returns `number` +- `color_extract`: `{{ string | color_extract: string }}` returns `number` +- `color_lighten`: `{{ string | color_lighten: number }}` returns `string` +- `color_mix`: `{{ string | color_mix: string, number }}` returns `string` +- `color_modify`: `{{ string | color_modify: string, number }}` returns `string` +- `color_saturate`: `{{ string | color_saturate: number }}` returns `string` +- `color_to_hex`: `{{ string | color_to_hex }}` returns `string` +- `color_to_hsl`: `{{ string | color_to_hsl }}` returns `string` +- `color_to_oklch`: `{{ string | color_to_oklch }}` returns `string` +- `color_to_rgb`: `{{ string | color_to_rgb }}` returns `string` +- `hex_to_rgba`: `{{ string | hex_to_rgba }}` returns `string` + +#### Customer +- `customer_login_link`: `{{ string | customer_login_link }}` returns `string` +- `customer_logout_link`: `{{ string | customer_logout_link }}` returns `string` +- `customer_register_link`: `{{ string | customer_register_link }}` returns `string` +- `avatar`: `{{ customer | avatar }}` returns `string` +- `login_button`: `{{ shop | login_button }}` returns `string` + +#### Date +- `date`: `{{ date | date: string }}` returns `string` + +#### Default +- `default_errors`: `{{ string | default_errors }}` returns `string` +- `default`: `{{ variable | default: variable }}` returns `untyped` +- `default_pagination`: `{{ paginate | default_pagination }}` returns `string` + +#### Font +- `font_face`: `{{ font | font_face }}` returns `string` +- `font_modify`: `{{ font | font_modify: string, string }}` returns `font` +- `font_url`: `{{ font | font_url }}` returns `string` + +#### Format +- `date`: `{{ string | date: string }}` returns `string` +- `json`: `{{ variable | json }}` returns `string` +- `structured_data`: `{{ variable | structured_data }}` returns `string` +- `unit_price_with_measurement`: `{{ number | unit_price_with_measurement: unit_price_measurement }}` returns `string` +- `weight_with_unit`: `{{ number | weight_with_unit }}` returns `string` + +#### Hosted_file +- `asset_img_url`: `{{ string | asset_img_url }}` returns `string` +- `asset_url`: `{{ string | asset_url }}` returns `string` +- `file_img_url`: `{{ string | file_img_url }}` returns `string` +- `file_url`: `{{ string | file_url }}` returns `string` +- `global_asset_url`: `{{ string | global_asset_url }}` returns `string` +- `shopify_asset_url`: `{{ string | shopify_asset_url }}` returns `string` + +#### Html +- `class_list`: `{{ settings.layout | class_list }}` returns `string` +- `time_tag`: `{{ string | time_tag: string }}` returns `string` +- `inline_asset_content`: `{{ asset_name | inline_asset_content }}` returns `string` +- `highlight`: `{{ string | highlight: string }}` returns `string` +- `link_to`: `{{ string | link_to: string }}` returns `string` +- `placeholder_svg_tag`: `{{ string | placeholder_svg_tag }}` returns `string` +- `preload_tag`: `{{ string | preload_tag: as: string }}` returns `string` +- `script_tag`: `{{ string | script_tag }}` returns `string` +- `stylesheet_tag`: `{{ string | stylesheet_tag }}` returns `string` + +#### Localization +- `currency_selector`: `{{ form | currency_selector }}` returns `string` +- `translate`: `{{ string | t }}` returns `string` +- `format_address`: `{{ address | format_address }}` returns `string` + +#### Math +- `abs`: `{{ number | abs }}` returns `number` +- `at_least`: `{{ number | at_least }}` returns `number` +- `at_most`: `{{ number | at_most }}` returns `number` +- `ceil`: `{{ number | ceil }}` returns `number` +- `divided_by`: `{{ number | divided_by: number }}` returns `number` +- `floor`: `{{ number | floor }}` returns `number` +- `minus`: `{{ number | minus: number }}` returns `number` +- `modulo`: `{{ number | modulo: number }}` returns `number` +- `plus`: `{{ number | plus: number }}` returns `number` +- `round`: `{{ number | round }}` returns `number` +- `times`: `{{ number | times: number }}` returns `number` + +#### Media +- `external_video_tag`: `{{ variable | external_video_tag }}` returns `string` +- `external_video_url`: `{{ media | external_video_url: attribute: string }}` returns `string` +- `image_tag`: `{{ string | image_tag }}` returns `string` +- `media_tag`: `{{ media | media_tag }}` returns `string` +- `model_viewer_tag`: `{{ media | model_viewer_tag }}` returns `string` +- `video_tag`: `{{ media | video_tag }}` returns `string` +- `article_img_url`: `{{ variable | article_img_url }}` returns `string` +- `collection_img_url`: `{{ variable | collection_img_url }}` returns `string` +- `image_url`: `{{ variable | image_url: width: number, height: number }}` returns `string` +- `img_tag`: `{{ string | img_tag }}` returns `string` +- `img_url`: `{{ variable | img_url }}` returns `string` +- `product_img_url`: `{{ variable | product_img_url }}` returns `string` + +#### Metafield +- `metafield_tag`: `{{ metafield | metafield_tag }}` returns `string` +- `metafield_text`: `{{ metafield | metafield_text }}` returns `string` + +#### Money +- `money`: `{{ number | money }}` returns `string` +- `money_with_currency`: `{{ number | money_with_currency }}` returns `string` +- `money_without_currency`: `{{ number | money_without_currency }}` returns `string` +- `money_without_trailing_zeros`: `{{ number | money_without_trailing_zeros }}` returns `string` + +#### Payment +- `payment_button`: `{{ form | payment_button }}` returns `string` +- `payment_terms`: `{{ form | payment_terms }}` returns `string` +- `payment_type_img_url`: `{{ string | payment_type_img_url }}` returns `string` +- `payment_type_svg_tag`: `{{ string | payment_type_svg_tag }}` returns `string` + +#### String +- `hmac_sha1`: `{{ string | hmac_sha1: string }}` returns `string` +- `hmac_sha256`: `{{ string | hmac_sha256: string }}` returns `string` +- `md5`: `{{ string | md5 }}` returns `string` +- `sha1`: `{{ string | sha1: string }}` returns `string` +- `sha256`: `{{ string | sha256: string }}` returns `string` +- `append`: `{{ string | append: string }}` returns `string` +- `base64_decode`: `{{ string | base64_decode }}` returns `string` +- `base64_encode`: `{{ string | base64_encode }}` returns `string` +- `base64_url_safe_decode`: `{{ string | base64_url_safe_decode }}` returns `string` +- `base64_url_safe_encode`: `{{ string | base64_url_safe_encode }}` returns `string` +- `capitalize`: `{{ string | capitalize }}` returns `string` +- `downcase`: `{{ string | downcase }}` returns `string` +- `escape`: `{{ string | escape }}` returns `string` +- `escape_once`: `{{ string | escape_once }}` returns `string` +- `lstrip`: `{{ string | lstrip }}` returns `string` +- `newline_to_br`: `{{ string | newline_to_br }}` returns `string` +- `prepend`: `{{ string | prepend: string }}` returns `string` +- `remove`: `{{ string | remove: string }}` returns `string` +- `remove_first`: `{{ string | remove_first: string }}` returns `string` +- `remove_last`: `{{ string | remove_last: string }}` returns `string` +- `replace`: `{{ string | replace: string, string }}` returns `string` +- `replace_first`: `{{ string | replace_first: string, string }}` returns `string` +- `replace_last`: `{{ string | replace_last: string, string }}` returns `string` +- `rstrip`: `{{ string | rstrip }}` returns `string` +- `slice`: `{{ string | slice }}` returns `string` +- `split`: `{{ string | split: string }}` returns `array` +- `strip`: `{{ string | strip }}` returns `string` +- `strip_html`: `{{ string | strip_html }}` returns `string` +- `strip_newlines`: `{{ string | strip_newlines }}` returns `string` +- `truncate`: `{{ string | truncate: number }}` returns `string` +- `truncatewords`: `{{ string | truncatewords: number }}` returns `string` +- `upcase`: `{{ string | upcase }}` returns `string` +- `url_decode`: `{{ string | url_decode }}` returns `string` +- `url_encode`: `{{ string | url_encode }}` returns `string` +- `camelize`: `{{ string | camelize }}` returns `string` +- `handleize`: `{{ string | handleize }}` returns `string` +- `url_escape`: `{{ string | url_escape }}` returns `string` +- `url_param_escape`: `{{ string | url_param_escape }}` returns `string` +- `pluralize`: `{{ number | pluralize: string, string }}` returns `string` + +#### Tag +- `link_to_add_tag`: `{{ string | link_to_add_tag }}` returns `string` +- `link_to_remove_tag`: `{{ string | link_to_remove_tag }}` returns `string` +- `link_to_tag`: `{{ string | link_to_tag }}` returns `string` + +### Liquid objects + +#### Global objects +- `collections` +- `pages` +- `all_products` +- `articles` +- `blogs` +- `cart` +- `closest` +- `content_for_header` +- `customer` +- `images` +- `linklists` +- `localization` +- `metaobjects` +- `request` +- `routes` +- `shop` +- `theme` +- `settings` +- `template` +- `additional_checkout_buttons` +- `all_country_option_tags` +- `canonical_url` +- `content_for_additional_checkout_buttons` +- `content_for_index` +- `content_for_layout` +- `country_option_tags` +- `current_page` +- `handle` +- `page_description` +- `page_image` +- `page_title` +- `powered_by_link` +- `scripts` + +#### `/article` page +- `article` +- `blog` + +#### `/blog` page +- `blog` +- `current_tags` + +#### `/cart` page +- `cart` + +#### `/checkout` page +- `checkout` + +#### `/collection` page +- `collection` +- `current_tags` + +#### `/customers/account` page +- `customer` + +#### `/customers/addresses` page +- `customer` + +#### `/customers/order` page +- `customer` +- `order` + +#### `/gift_card.liquid` page +- `gift_card` +- `recipient` + +#### `/metaobject` page +- `metaobject` + +#### `/page` page +- `page` + +#### `/product` page +- `product` + +#### `/robots.txt.liquid` page +- `robots` + +#### `/search` page +- `search` +### Liquid tags + + +#### content_for +The `content_for` tag requires a type parameter to differentiate between rendering a number of theme blocks (`'blocks'`) and a single static block (`'block'`). + + +Syntax: +``` +{% content_for 'blocks' %} +{% content_for 'block', type: "slide", id: "slide-1" %} +``` + +#### form +Because there are many different form types available in Shopify themes, the `form` tag requires a type. Depending on the +form type, an additional parameter might be required. You can specify the following form types: + +- [`activate_customer_password`](https://shopify.dev/docs/api/liquid/tags/form#form-activate_customer_password) +- [`cart`](https://shopify.dev/docs/api/liquid/tags/form#form-cart) +- [`contact`](https://shopify.dev/docs/api/liquid/tags/form#form-contact) +- [`create_customer`](https://shopify.dev/docs/api/liquid/tags/form#form-create_customer) +- [`currency`](https://shopify.dev/docs/api/liquid/tags/form#form-currency) +- [`customer`](https://shopify.dev/docs/api/liquid/tags/form#form-customer) +- [`customer_address`](https://shopify.dev/docs/api/liquid/tags/form#form-customer_address) +- [`customer_login`](https://shopify.dev/docs/api/liquid/tags/form#form-customer_login) +- [`guest_login`](https://shopify.dev/docs/api/liquid/tags/form#form-guest_login) +- [`localization`](https://shopify.dev/docs/api/liquid/tags/form#form-localization) +- [`new_comment`](https://shopify.dev/docs/api/liquid/tags/form#form-new_comment) +- [`product`](https://shopify.dev/docs/api/liquid/tags/form#form-product) +- [`recover_customer_password`](https://shopify.dev/docs/api/liquid/tags/form#form-recover_customer_password) +- [`reset_customer_password`](https://shopify.dev/docs/api/liquid/tags/form#form-reset_customer_password) +- [`storefront_password`](https://shopify.dev/docs/api/liquid/tags/form#form-storefront_password) + + +Syntax: +``` +{% form 'form_type' %} + content +{% endform %} +``` + +#### layout + +Syntax: +``` +{% layout name %} +``` + +#### assign +You can create variables of any [basic type](https://shopify.dev/docs/api/liquid/basics#types), [object](https://shopify.dev/docs/api/liquid/objects), or object property. + + +Syntax: +``` +{% assign variable_name = value %} +``` + +#### break + +Syntax: +``` +{% break %} +``` + +#### capture +You can create complex strings with Liquid logic and variables. + + +Syntax: +``` +{% capture variable %} + value +{% endcapture %} +``` + +#### case + +Syntax: +``` +{% case variable %} + {% when first_value %} + first_expression + {% when second_value %} + second_expression + {% else %} + third_expression +{% endcase %} +``` + +#### comment +Any text inside `comment` tags won't be output, and any Liquid code will be parsed, but not executed. + + +Syntax: +``` +{% comment %} + content +{% endcomment %} +``` + +#### continue + +Syntax: +``` +{% continue %} +``` + +#### cycle +The `cycle` tag must be used inside a `for` loop. + +> Tip: +> Use the `cycle` tag to output text in a predictable pattern. For example, to apply odd/even classes to rows in a table. + + +Syntax: +``` +{% cycle string, string, ... %} +``` + +#### decrement +Variables that are declared with `decrement` are unique to the [layout](/themes/architecture/layouts), [template](/themes/architecture/templates), +or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across +[snippets](/themes/architecture/snippets) included in the file. + +Similarly, variables that are created with `decrement` are independent from those created with [`assign`](https://shopify.dev/docs/api/liquid/tags/assign) +and [`capture`](https://shopify.dev/docs/api/liquid/tags/capture). However, `decrement` and [`increment`](https://shopify.dev/docs/api/liquid/tags/increment) share +variables. + + +Syntax: +``` +{% decrement variable_name %} +``` + +#### doc +The `doc` tag allows developers to include documentation within Liquid +templates. Any content inside `doc` tags is not rendered or outputted. +Liquid code inside will be parsed but not executed. This facilitates +tooling support for features like code completion, linting, and inline +documentation. + +For detailed documentation syntax and examples, see the +[`LiquidDoc` reference](https://shopify.dev/docs/storefronts/themes/tools/liquid-doc). + + +Syntax: +``` +{% doc %} + Renders a message. + + @param {string} foo - A string value. + @param {string} [bar] - An optional string value. + + @example + {% render 'message', foo: 'Hello', bar: 'World' %} +{% enddoc %} +{{ foo }}, {{ bar }}! +``` + +#### echo +Using the `echo` tag is the same as wrapping an expression in curly brackets (`{{` and `}}`). However, unlike the curly +bracket method, you can use the `echo` tag inside [`liquid` tags](https://shopify.dev/docs/api/liquid/tags/liquid). + +> Tip: +> You can use [filters](https://shopify.dev/docs/api/liquid/filters) on expressions inside `echo` tags. + + +Syntax: +``` +{% liquid + echo expression +%} +``` + +#### for +You can do a maximum of 50 iterations with a `for` loop. If you need to iterate over more than 50 items, then use the +[`paginate` tag](https://shopify.dev/docs/api/liquid/tags/paginate) to split the items over multiple pages. + +> Tip: +> Every `for` loop has an associated [`forloop` object](https://shopify.dev/docs/api/liquid/objects/forloop) with information about the loop. + + +Syntax: +``` +{% for variable in array %} + expression +{% endfor %} +``` + +#### if + +Syntax: +``` +{% if condition %} + expression +{% endif %} +``` + +#### increment +Variables that are declared with `increment` are unique to the [layout](/themes/architecture/layouts), [template](/themes/architecture/templates), +or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across +[snippets](/themes/architecture/snippets) included in the file. + +Similarly, variables that are created with `increment` are independent from those created with [`assign`](https://shopify.dev/docs/api/liquid/tags/assign) +and [`capture`](https://shopify.dev/docs/api/liquid/tags/capture). However, `increment` and [`decrement`](https://shopify.dev/docs/api/liquid/tags/decrement) share +variables. + + +Syntax: +``` +{% increment variable_name %} +``` + +#### raw + +Syntax: +``` +{% raw %} + expression +{% endraw %} +``` + +#### render +Inside snippets and app blocks, you can't directly access variables that are [created](https://shopify.dev/docs/api/liquid/tags/variable-tags) outside +of the snippet or app block. However, you can [specify variables as parameters](https://shopify.dev/docs/api/liquid/tags/render#render-passing-variables-to-a-snippet) +to pass outside variables to snippets. + +While you can't directly access created variables, you can access global objects, as well as any objects that are +directly accessible outside the snippet or app block. For example, a snippet or app block inside the [product template](/themes/architecture/templates/product) +can access the [`product` object](https://shopify.dev/docs/api/liquid/objects/product), and a snippet or app block inside a [section](/themes/architecture/sections) +can access the [`section` object](https://shopify.dev/docs/api/liquid/objects/section). + +Outside a snippet or app block, you can't access variables created inside the snippet or app block. + +> Note: +> When you render a snippet using the `render` tag, you can't use the [`include` tag](https://shopify.dev/docs/api/liquid/tags/include) +> inside the snippet. + + +Syntax: +``` +{% render 'filename' %} +``` + +#### tablerow +The `tablerow` tag must be wrapped in HTML `` and `
` tags. + +> Tip: +> Every `tablerow` loop has an associated [`tablerowloop` object](https://shopify.dev/docs/api/liquid/objects/tablerowloop) with information about the loop. + + +Syntax: +``` +{% tablerow variable in array %} + expression +{% endtablerow %} +``` + +#### unless +> Tip: +> Similar to the [`if` tag](https://shopify.dev/docs/api/liquid/tags/if), you can use `elsif` to add more conditions to an `unless` tag. + + +Syntax: +``` +{% unless condition %} + expression +{% endunless %} +``` + +#### paginate +Because [`for` loops](https://shopify.dev/docs/api/liquid/tags/for) are limited to 50 iterations per page, you need to use the `paginate` tag to +iterate over an array that has more than 50 items. The following arrays can be paginated: + +- [`all_products`](https://shopify.dev/docs/api/liquid/objects/all_products) +- [`article.comments`](https://shopify.dev/docs/api/liquid/objects/article#article-comments) +- [`blog.articles`](https://shopify.dev/docs/api/liquid/objects/blog#blog-articles) +- [`collections`](https://shopify.dev/docs/api/liquid/objects/collections) +- [`collection.products`](https://shopify.dev/docs/api/liquid/objects/collection#collection-products) +- [`customer.addresses`](https://shopify.dev/docs/api/liquid/objects/customer#customer-addresses) +- [`customer.orders`](https://shopify.dev/docs/api/liquid/objects/customer#customer-orders) +- [`pages`](https://shopify.dev/docs/api/liquid/objects/pages) +- [`product.variants`](https://shopify.dev/docs/api/liquid/objects/product#variants) +- [`search.results`](https://shopify.dev/docs/api/liquid/objects/search#search-results) +- [`collection_list` settings](/themes/architecture/settings/input-settings#collection_list) +- [`product_list` settings](/themes/architecture/settings/input-settings#product_list) + +Within the `paginate` tag, you have access to the [`paginate` object](https://shopify.dev/docs/api/liquid/objects/paginate). You can use this +object, or the [`default_pagination` filter](https://shopify.dev/docs/api/liquid/filters/default_pagination), to build page navigation. + + +Syntax: +``` +{% paginate array by page_size %} + {% for item in array %} + forloop_content + {% endfor %} +{% endpaginate %} + +The `paginate` tag allows the user to paginate to the 25,000th item in the array and no further. To reach items further in +the array the array should be filtered further before paginating. See +[Pagination Limits](/themes/best-practices/performance/platform#pagination-limits) for more information. +``` + +#### javascript +Each section, block or snippet can have only one `{% javascript %}` tag. + +To learn more about how JavaScript that's defined between the `javascript` tags is loaded and run, refer to the documentation for [javascript tags](/storefronts/themes/best-practices/javascript-and-stylesheet-tags#javascript). +> Caution: +> Liquid isn't rendered inside of `{% javascript %}` tags. Including Liquid code can cause syntax errors. + + +Syntax: +``` +{% javascript %} + javascript_code +{% endjavascript %} +``` + +#### section +Rendering a section with the `section` tag renders a section statically. To learn more about sections and how to use +them in your theme, refer to [Render a section](/themes/architecture/sections#render-a-section). + + +Syntax: +``` +{% section 'name' %} +``` + +#### stylesheet +Each section, block or snippet can have only one `{% stylesheet %}` tag. + +To learn more about how CSS that's defined between the `stylesheet` tags is loaded and run, refer to the documentation for [stylesheet tags](/storefronts/themes/best-practices/javascript-and-stylesheet-tags#stylesheet). +> Caution: +> Liquid isn't rendered inside of `{% stylesheet %}` tags. Including Liquid code can cause syntax errors. + + +Syntax: +``` +{% stylesheet %} + css_styles +{% endstylesheet %} +``` + +#### sections +Use this tag to render section groups as part of the theme's [layout](/themes/architecture/layouts) content. Place the `sections` tag where you want to render it in the layout. + +To learn more about section groups and how to use them in your theme, refer to [Section groups](/themes/architecture/section-groups#usage). + + +Syntax: +``` +{% sections 'name' %} +``` + +#### style +> Note: +> If you reference [color settings](/themes/architecture/settings/input-settings#color) inside `style` tags, then +> the associated CSS rules will update as the setting is changed in the theme editor, without a page refresh. + + +Syntax: +``` +{% style %} + CSS_rules +{% endstyle %} +``` + +#### else +You can use the `else` tag with the following tags: + +- [`case`](https://shopify.dev/docs/api/liquid/tags/case) +- [`if`](https://shopify.dev/docs/api/liquid/tags/if) +- [`unless`](https://shopify.dev/docs/api/liquid/tags/unless) + + +Syntax: +``` +{% else %} + expression +``` + +#### else + +Syntax: +``` +{% for variable in array %} + first_expression +{% else %} + second_expression +{% endfor %} +``` + +#### liquid +Because the tags don't have delimeters, each tag needs to be on its own line. + +> Tip: +> Use the [`echo` tag](https://shopify.dev/docs/api/liquid/tags/echo) to output an expression inside `liquid` tags. + + +Syntax: +``` +{% liquid + expression +%} +``` + + +## Translation development standards + +### Translation requirements + +- **Every user-facing text** must use translation filters. +- **Update `locales/en.default.json`** with all new keys. +- **Use descriptive, hierarchical keys** for organization. +- **Only add English text**; translators handle other languages. + +### Translation filter usage + +**Use `{{ 'key' | t }}` for all text:** + +```liquid + +

{{ 'sections.featured_collection.title' | t }}

+

{{ 'sections.featured_collection.description' | t }}

+ + + +

Featured Collection

+

Check out our best products

+ +``` + +### Translation with variables + +**Use variables for interpolation:** + +```liquid + +

{{ 'products.price_range' | t: min: product.price_min | money, max: product.price_max | money }}

+

{{ 'general.pagination.page' | t: page: paginate.current_page, pages: paginate.pages }}

+``` + +**Corresponding keys in locale files:** + +```json +{ + "products": { + "price_range": "From {{ min }} to {{ max }}" + }, + "general": { + "pagination": { + "page": "Page {{ page }} of {{ pages }}" + } + } +} +``` + +### Best practices + +**Content guidelines:** +- Write clear, concise text. +- **Use sentence case** for all user-facing text, including titles, headings, and button labels (capitalize only the first word and proper nouns; e.g., `Featured collection` → `Featured collection`, not `Featured Collection`). +- Be consistent with terminology. +- Consider character limits for UI elements. + +**Variable usage:** +- Use interpolation rather than appending strings together. +- Prioritize clarity over brevity for variable naming. +- Escape variables unless they output HTML: `{{ variable | escape }}`. + + +## Localization standards + +Auto-attached when working in `locales/` directory. + +### File structure + +``` +locales/ +├── en.default.json # English (required) +├── en.default.schema.json # English (required) +├── es.json # Spanish +├── est.schema.json # Spanish +├── fr.json # French +├── frt.schema.json # French +└── pt-BR.json # Portuguese +└── pt-BR..schema.json # Portuguese +``` + +#### Locale files + +Locale files are JSON files containing translations for all the text strings used throughout a Shopify theme and its editor. They let merchants easily update and localize repeated words and phrases, making it possible to translate store content and settings into multiple languages for international customers. These files provide a centralized way to manage and edit translations. + +**Example:** +```json +{ + "general": { + "cart": "Cart", + "checkout": "Checkout" + }, + "products": { + "add_to_cart": "Add to Cart" + } +} +``` + +#### Schema locale files + +Schema locale files, saved with a .schema.json extension, store translation strings specifically for theme editor setting schemas. They follow a structured organization—category, group, and description—to give context to each translation, enabling accurate localization of editor content. Schema locale files must use the IETF language tag format in their naming, such as en-GB.schema.json for British English or fr-CA.schema.json for Canadian French. + +**Example:** +```json +{ + "products": { + "card": { + "description": "Product card layout" + } + } +} +``` + +### Key organization + +**Hierarchical structure:** +```json +{ + "general": { + "meta": { + "title": "{{ shop_name }}", + "description": "{{ shop_description }}" + }, + "accessibility": { + "skip_to_content": "Skip to content", + "close": "Close" + } + }, + "products": { + "add_to_cart": "Add to cart", + "quick_view": "Quick view", + "price": { + "regular": "Regular price", + "sale": "Sale price", + "unit": "Unit price" + } + } +} +``` +**Usage** +```liquid +{{ 'general.meta.title' | t: shop_name: shop.name }} +{{ 'general.meta.description' | t: shop_description: shop.description }} +``` + +### Translation guidelines + +**Key naming:** +- Use descriptive, hierarchical keys +- Maximum 3 levels deep +- Use snake_case for key names +- Group related translations + +**Content rules:** +- Keep text concise for UI elements +- Use variables for dynamic content +- Consider character limits +- Maintain consistent terminology + +## Examples per kind of asset + +### `snippet` + +```liquid +{% doc %} + Renders a responsive image that might be wrapped in a link. + + When `width`, `height` and `crop` are provided, the image will be rendered + with a fixed aspect ratio. + + Serves as an example of how to use the `image_url` filter and `image_tag` filter + as well as how you can use LiquidDoc to document your code. + + @param {image} image - The image to be rendered + @param {string} [url] - An optional destination URL for the image + @param {string} [css_class] - Optional class to be added to the image wrapper + @param {number} [width] - The highest resolution width of the image to be rendered + @param {number} [height] - The highest resolution height of the image to be rendered + @param {string} [crop] - The crop position of the image + + @example + {% render 'image', image: product.featured_image %} + {% render 'image', image: product.featured_image, url: product.url %} + {% render 'image', + css_class: 'product__image', + image: product.featured_image, + url: product.url, + width: 1200, + height: 800, + crop: 'center', + %} +{% enddoc %} + +{% liquid + unless height + assign width = width | default: image.width + endunless + + if url + assign wrapper = 'a' + else + assign wrapper = 'div' + endif +%} + +<{{ wrapper }} + class="image {{ css_class }}" + {% if url %} + href="{{ url }}" + {% endif %} +> + {{ image | image_url: width: width, height: height, crop: crop | image_tag }} + + +{% stylesheet %} + .image { + display: block; + position: relative; + overflow: hidden; + width: 100%; + height: auto; + } + + .image > img { + width: 100%; + height: auto; + } +{% endstylesheet %} + +{% javascript %} + function doSomething() { + // example + } + doSomething() +{% endjavascript %} + +``` + +### `block` + +#### Text + +```liquid +{% doc %} + Renders a text block. + + @example + {% content_for 'block', type: 'text', id: 'text' %} +{% enddoc %} + +
+ {{ block.settings.text }} +
+ +{% stylesheet %} + .text { + text-align: var(--text-align); + } + .text--title { + font-size: 2rem; + font-weight: 700; + } + .text--subtitle { + font-size: 1.5rem; + } +{% endstylesheet %} + +{% schema %} +{ + "name": "t:general.text", + "settings": [ + { + "type": "text", + "id": "text", + "label": "t:labels.text", + "default": "Text" + }, + { + "type": "select", + "id": "text_style", + "label": "t:labels.text_style", + "options": [ + { "value": "text--title", "label": "t:options.text_style.title" }, + { "value": "text--subtitle", "label": "t:options.text_style.subtitle" }, + { "value": "text--normal", "label": "t:options.text_style.normal" } + ], + "default": "text--title" + }, + { + "type": "text_alignment", + "id": "alignment", + "label": "t:labels.alignment", + "default": "left" + } + ], + "presets": [{ "name": "t:general.text" }] +} +{% endschema %} +``` + +#### Group + +```liquid +{% doc %} + Renders a group of blocks with configurable layout direction, gap and + alignment. + + All settings apply to only one dimension to reduce configuration complexity. + + This component is a wrapper concerned only with rendering its children in + the specified layout direction with appropriate padding and alignment. + + @example + {% content_for 'block', type: 'group', id: 'group' %} +{% enddoc %} + +
+ {% content_for 'blocks' %} +
+ +{% stylesheet %} + .group { + display: flex; + flex-wrap: nowrap; + overflow: hidden; + width: 100%; + } + .group--horizontal { + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 0 var(--padding); + } + .group--vertical { + flex-direction: column; + align-items: var(--alignment); + padding: var(--padding) 0; + } +{% endstylesheet %} + +{% schema %} +{ + "name": "t:general.group", + "blocks": [{ "type": "@theme" }], + "settings": [ + { + "type": "select", + "id": "layout_direction", + "label": "t:labels.layout_direction", + "default": "group--vertical", + "options": [ + { "value": "group--horizontal", "label": "t:options.direction.horizontal" }, + { "value": "group--vertical", "label": "t:options.direction.vertical" } + ] + }, + { + "visible_if": "{{ block.settings.layout_direction == 'group--vertical' }}", + "type": "select", + "id": "alignment", + "label": "t:labels.alignment", + "default": "flex-start", + "options": [ + { "value": "flex-start", "label": "t:options.alignment.left" }, + { "value": "center", "label": "t:options.alignment.center" }, + { "value": "flex-end", "label": "t:options.alignment.right" } + ] + }, + { + "type": "range", + "id": "padding", + "label": "t:labels.padding", + "default": 0, + "min": 0, + "max": 200, + "step": 2, + "unit": "px" + } + ], + "presets": [ + { + "name": "t:general.column", + "category": "t:general.layout", + "settings": { + "layout_direction": "group--vertical", + "alignment": "flex-start", + "padding": 0 + } + }, + { + "name": "t:general.row", + "category": "t:general.layout", + "settings": { + "layout_direction": "group--horizontal", + "padding": 0 + } + } + ] +} +{% endschema %} +``` + +### `section` + +```liquid +
+ {% if section.settings.background_image %} +
+ {{ section.settings.background_image | image_url: width: 2000 | image_tag }} +
+ {% endif %} + +
+ {% content_for 'blocks' %} +
+
+ +{% stylesheet %} + .example-section { + position: relative; + overflow: hidden; + width: 100%; + } + .example-section__background { + position: absolute; + width: 100%; + height: 100%; + z-index: -1; + overflow: hidden; + } + .example-section__background img { + position: absolute; + width: 100%; + height: auto; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + .example-section__content { + display: grid; + grid-template-columns: var(--content-grid); + } + + .example-section__content > * { + grid-column: 2; + } +{% endstylesheet %} + +{% schema %} +{ + "name": "t:general.custom_section", + "blocks": [{ "type": "@theme" }], + "settings": [ + { + "type": "image_picker", + "id": "background_image", + "label": "t:labels.background" + } + ], + "presets": [ + { + "name": "t:general.custom_section" + } + ] +} +{% endschema %} +``` +