6# Shopify Liquid Themes
7
8## Theme Architecture
9
10```
11.
12├── sections/ # Full-width page modules with {% schema %} — hero, product grid, testimonials
13├── blocks/ # Nestable components with {% schema %} — slides, feature items, text blocks
14├── snippets/ # Reusable fragments via {% render %} — buttons, icons, image helpers
15├── layout/ # Page wrappers (must include {{ content_for_header }} and {{ content_for_layout }})
16├── templates/ # JSON files defining which sections appear on each page type
17├── config/ # Global theme settings (settings_schema.json, settings_data.json)
18├── locales/ # Translation files (en.default.json, fr.json, etc.)
19└── assets/ # Static CSS, JS, images (prefer {% stylesheet %}/{% javascript %} instead)
20```
21
22### When to use what
23
24| Need | Use | Why |
25|------|-----|-----|
26| Full-width customizable module | **Section** | Has {% schema %}, appears in editor, renders blocks |
27| Small nestable component with editor settings | **Block** | Has {% schema %}, can nest inside sections/blocks |
28| Reusable logic, not editable by merchant | **Snippet** | No schema, rendered via {% render %}, takes params |
29| Logic shared across blocks/snippets | **Snippet** | Blocks can't {% render %} other blocks |
30
31## Liquid Syntax
32
33### Delimiters
34
35- {{ ... }} — Output (prints a value)
36- {{- ... -}} — Output with whitespace trimming
37- {% ... %} — Logic tag (if, for, assign) — prints nothing
38- {%- ... -%} — Logic tag with whitespace trimming
39
40### Operators
41
42**Comparison:** ==, !=, >, <, >=, <=
43**Logical:** and, or, contains
44
45### Critical Gotchas
46
471. **No parentheses** in conditions — use nested {% if %} instead
482. **No ternary** — always use {% if cond %}value{% else %}other{% endif %}
493. **for loops max 50 iterations** — use {% paginate %} for larger arrays
504. **contains only works with strings** — can't check objects in arrays
515. **{% stylesheet %}/{% javascript %} don't render Liquid** — no Liquid inside them
526. **Snippets can't access outer-scope variables** — pass them as render params
537. **include is deprecated** — always use {% render 'snippet_name' %}
548. **{% liquid %} tag** — multi-line logic without delimiters; use echo for output
55
56### Variables
57
58```liquid
59{% assign my_var = 'value' %}
60{% capture my_var %}computed {{ value }}{% endcapture %}
61{% increment counter %}
62{% decrement counter %}
63```
64
65## Filter Quick Reference
66
67Filters are chained with |. Output type of one filter feeds input of next.
68
69**Array:** compact, concat, find, find_index, first, has, join, last, map, reject, reverse, size, sort, sort_natural, sum, uniq, where
70**String:** append, capitalize, downcase, escape, handleize, lstrip, newline_to_br, prepend, remove, replace, rstrip, slice, split, strip, strip_html, truncate, truncatewords, upcase, url_decode, url_encode
71**Math:** abs, at_least, at_most, ceil, divided_by, floor, minus, modulo, plus, round, times
72**Money:** money, money_with_currency, money_without_currency, money_without_trailing_zeros
73**Color:** color_brightness, color_darken, color_lighten, color_mix, color_modify, color_saturate, color_desaturate, color_to_hex, color_to_hsl, color_to_rgb
74**Media:** image_url, image_tag, video_tag, external_video_tag, media_tag, model_viewer_tag
75**URL:** asset_url, asset_img_url, file_url, shopify_asset_url
76**HTML:** link_to, script_tag, stylesheet_tag, time_tag, placeholder_svg_tag
77**Localization:** t (translate), format_address, currency_selector
78**Other:** date, default, json, structured_data, font_face, font_url, payment_button
79
80> Full details: [language filters](references/filters-language.md), [HTML/media filters](references/filters-html-media.md), [commerce filters](references/filters-commerce.md)
81
82## Tags Quick Reference
83
84| Category | Tags |
85|----------|------|
86| **Theme** | content_for, layout, section, sections, schema, stylesheet, javascript, style |
87| **Control** | if, elsif, else, unless, case, when |
88| **Iteration** | for, break, continue, cycle, tablerow, paginate |
89| **Variable** | assign, capture, increment, decrement, echo |
90| **HTML** | form, render, raw, comment, liquid |
91| **Documentation** | doc |
92
93> Full details with syntax and parameters: [references/tags.md](references/tags.md)
94
95## Objects Quick Reference
96
97### Global objects (available everywhere)
98
99cart, collections, customer, localization, pages, request, routes, settings, shop, template, theme, linklists, images, blogs, articles, all_products, metaobjects, canonical_url, content_for_header, content_for_layout, page_title, page_description, handle, current_page
100
101### Page-specific objects
102
103| Template | Objects |
104|----------|---------|
105| /product | product, remote_product |
106| /collection | collection, current_tags |
107| /cart | cart |
108| /article | article, blog |
109| /blog | blog, current_tags |
110| /page | page |
111| /search | search |
112| /customers/* | customer, order |
113
114> Full reference: [commerce objects](references/objects-commerce.md), [content objects](references/objects-content.md), [tier 2](references/objects-tier2.md), [tier 3](references/objects-tier3.md)
115
116## Schema Tag
117
118Sections and blocks require {% schema %} with a valid JSON object. Sections use section.settings.*, blocks use block.settings.*.
119
120### Section schema structure
121
122```json
123{
124 "name": "t:sections.hero.name",
125 "tag": "section",
126 "class": "hero-section",
127 "limit": 1,
128 "settings": [],
129 "max_blocks": 16,
130 "blocks": [{ "type": "@theme" }],
131 "presets": [{ "name": "t:sections.hero.name" }],
132 "enabled_on": { "templates": ["index"] },
133 "disabled_on": { "templates": ["password"] }
134}
135```
136
137### Block schema structure
138
139```json
140{
141 "name": "t:blocks.slide.name",
142 "tag": "div",
143 "class": "slide",
144 "settings": [],
145 "blocks": [{ "type": "@theme" }],
146 "presets": [{ "name": "t:blocks.slide.name" }]
147}
148```
149
150### Setting type decision table
151
152| Need | Setting Type | Key Fields |
153|------|-------------|------------|
154| On/off toggle | checkbox | default: true/false |
155| Short text | text | placeholder |
156| Long text | textarea | placeholder |
157| Rich text (with <p>) | richtext | — |
158| Inline rich text (no <p>) | inline_richtext | — |
159| Number input | number | placeholder |
160| Slider | range | min, max, default (all required), step, unit |
161| Dropdown/segmented | select | options: [{value, label}] |
162| Radio buttons | radio | options: [{value, label}] |
163| Text alignment | text_alignment | default: "left"/"center"/"right" |
164| Color picker | color | default: "#000000" |
165| Image upload | image_picker | — |
166| Video upload | video | — |
167| External video URL | video_url | accept: ["youtube", "vimeo"] |
168| Product picker | product | — |
169| Collection picker | collection | — |
170| Page picker | page | — |
171| Blog picker | blog | — |
172| Article picker | article | — |
173| URL entry | url | — |
174| Menu picker | link_list | — |
175| Font picker | font_picker | default (required) |
176| Editor header | header | content (no id needed) |
177| Editor description | paragraph | content (no id needed) |
178
179### visible_if pattern
180
181```json
182{
183 "visible_if": "{{ block.settings.layout == 'vertical' }}",
184 "type": "select",
185 "id": "alignment",
186 "label": "t:labels.alignment",
187 "options": [...]
188}
189```
190
191Conditionally shows/hides a setting in the editor based on other setting values.
192
193### Block entry types
194
195- { "type": "@theme" } — Accept any theme block
196- { "type": "@app" } — Accept app blocks
197- { "type": "slide" } — Accept only the slide block type
198
199> Full schema details and all 33 setting types: [references/schema-and-settings.md](references/schema-and-settings.md)
200
201## CSS & JavaScript
202
203### Per-component styles and scripts
204
205Use {% stylesheet %} and {% javascript %} in sections, blocks, and snippets:
206
207```liquid
208{% stylesheet %}
209 .my-component { display: flex; }
210{% endstylesheet %}
211
212{% javascript %}
213 console.log('loaded');
214{% endjavascript %}
215```
216
217- **One tag each per file** — multiple {% stylesheet %} tags will error
218- **No Liquid inside** — these tags don't process Liquid; use CSS variables or classes instead
219- Only supported in sections/, blocks/, and snippets/
220
221### {% style %} tag (Liquid-aware CSS)
222
223For dynamic CSS that needs Liquid (e.g., color settings that live-update in editor):
224
225```liquid
226{% style %}
227 .section-{{ section.id }} {
228 background: {{ section.settings.bg_color }};
229 }
230{% endstyle %}
231```
232
233### CSS patterns for settings
234
235**Single CSS property** — use CSS variables:
236```liquid
237<div style="--gap: {{ block.settings.gap }}px">
238```
239
240**Multiple CSS properties** — use CSS classes as select values:
241```liquid
242<div class="{{ block.settings.layout }}">
243```
244
245## LiquidDoc ({% doc %})
246
247**Required for:** snippets (always), blocks (when statically rendered via {% content_for 'block' %})
248
249```liquid
250{% doc %}
251 Brief description of what this file renders.
252
253 @param {type} name - Description of required parameter
254 @param {type} [name] - Description of optional parameter (brackets = optional)
255
256 @example
257 {% render 'snippet-name', name: value %}
258{% enddoc %}
259```
260
261**Param types:** string, number, boolean, image, object, array
262
263## Translations
264
265### Every user-facing string must use the t filter
266
267```liquid
268<!-- Correct -->
269<h2>{{ 'sections.hero.heading' | t }}</h2>
270<button>{{ 'products.add_to_cart' | t }}</button>
271
272<!-- Wrong — never hardcode strings -->
273<h2>Welcome to our store</h2>
274```
275
276### Variable interpolation
277
278```liquid
279{{ 'products.price_range' | t: min: product.price_min | money, max: product.price_max | money }}
280```
281
282Locale file:
283```json
284{
285 "products": {
286 "price_range": "From {{ min }} to {{ max }}"
287 }
288}
289```
290
291### Locale file structure
292
293```
294locales/
295├── en.default.json # English translations (required)
296├── en.default.schema.json # Editor setting translations (required)
297├── fr.json # French translations
298└── fr.schema.json # French editor translations
299```
300
301### Key naming conventions
302
303- Use **snake_case** and **hierarchical keys** (max 3 levels)
304- Use **sentence case** for all text (capitalize first word only)
305- Schema labels use t: prefix: "label": "t:labels.heading"
306- Group by component: sections.hero.heading, blocks.slide.title
307
308## References
309
310- Filters: [language](references/filters-language.md) (77), [HTML/media](references/filters-html-media.md) (45), [commerce](references/filters-commerce.md) (30)
311- [Tag reference (30 tags)](references/tags.md)
312- Objects: [commerce](references/objects-commerce.md) (5), [content](references/objects-content.md) (10), [tier 2](references/objects-tier2.md) (69), [tier 3](references/objects-tier3.md) (53)
313- [Schema & settings reference (33 types)](references/schema-and-settings.md)
314- [Complete examples (snippet, block, section)](references/examples.md)
315