home / skills / benjaminsehl / liquid-skills / shopify-liquid-themes

shopify-liquid-themes skill

/skills/shopify-liquid-themes

This skill generates Shopify Liquid theme code with sections, blocks, and snippets, ensuring proper schema, translations, and assets integration for theme

npx playbooks add skill benjaminsehl/liquid-skills --skill shopify-liquid-themes

Review the files below or copy the command above to add this skill to your agents.

Files (11)
SKILL.md
11.4 KB
---
name: shopify-liquid-themes
description: "Generate Shopify Liquid theme code (sections, blocks, snippets) with correct schema JSON, LiquidDoc headers, translation keys, and CSS/JS patterns. Use when creating or editing .liquid files for Shopify themes, working with schema, doc, stylesheet, javascript tags, or Shopify Liquid objects/filters/tags."
---

# Shopify Liquid Themes

## Theme Architecture

```
.
├── sections/    # Full-width page modules with {% schema %} — hero, product grid, testimonials
├── blocks/      # Nestable components with {% schema %} — slides, feature items, text blocks
├── snippets/    # Reusable fragments via {% render %} — buttons, icons, image helpers
├── layout/      # Page wrappers (must include {{ content_for_header }} and {{ content_for_layout }})
├── templates/   # JSON files defining which sections appear on each page type
├── config/      # Global theme settings (settings_schema.json, settings_data.json)
├── locales/     # Translation files (en.default.json, fr.json, etc.)
└── assets/      # Static CSS, JS, images (prefer {% stylesheet %}/{% javascript %} instead)
```

### When to use what

| Need | Use | Why |
|------|-----|-----|
| Full-width customizable module | **Section** | Has `{% schema %}`, appears in editor, renders blocks |
| Small nestable component with editor settings | **Block** | Has `{% schema %}`, can nest inside sections/blocks |
| Reusable logic, not editable by merchant | **Snippet** | No schema, rendered via `{% render %}`, takes params |
| Logic shared across blocks/snippets | **Snippet** | Blocks can't `{% render %}` other blocks |

## Liquid Syntax

### Delimiters

- `{{ ... }}` — Output (prints a value)
- `{{- ... -}}` — Output with whitespace trimming
- `{% ... %}` — Logic tag (if, for, assign) — prints nothing
- `{%- ... -%}` — Logic tag with whitespace trimming

### Operators

**Comparison:** `==`, `!=`, `>`, `<`, `>=`, `<=`
**Logical:** `and`, `or`, `contains`

### Critical Gotchas

1. **No parentheses** in conditions — use nested `{% if %}` instead
2. **No ternary** — always use `{% if cond %}value{% else %}other{% endif %}`
3. **`for` loops max 50 iterations** — use `{% paginate %}` for larger arrays
4. **`contains` only works with strings** — can't check objects in arrays
5. **`{% stylesheet %}`/`{% javascript %}` don't render Liquid** — no Liquid inside them
6. **Snippets can't access outer-scope variables** — pass them as render params
7. **`include` is deprecated** — always use `{% render 'snippet_name' %}`
8. **`{% liquid %}` tag** — multi-line logic without delimiters; use `echo` for output

### Variables

```liquid
{% assign my_var = 'value' %}
{% capture my_var %}computed {{ value }}{% endcapture %}
{% increment counter %}
{% decrement counter %}
```

## Filter Quick Reference

Filters are chained with `|`. Output type of one filter feeds input of next.

**Array:** `compact`, `concat`, `find`, `find_index`, `first`, `has`, `join`, `last`, `map`, `reject`, `reverse`, `size`, `sort`, `sort_natural`, `sum`, `uniq`, `where`
**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`
**Math:** `abs`, `at_least`, `at_most`, `ceil`, `divided_by`, `floor`, `minus`, `modulo`, `plus`, `round`, `times`
**Money:** `money`, `money_with_currency`, `money_without_currency`, `money_without_trailing_zeros`
**Color:** `color_brightness`, `color_darken`, `color_lighten`, `color_mix`, `color_modify`, `color_saturate`, `color_desaturate`, `color_to_hex`, `color_to_hsl`, `color_to_rgb`
**Media:** `image_url`, `image_tag`, `video_tag`, `external_video_tag`, `media_tag`, `model_viewer_tag`
**URL:** `asset_url`, `asset_img_url`, `file_url`, `shopify_asset_url`
**HTML:** `link_to`, `script_tag`, `stylesheet_tag`, `time_tag`, `placeholder_svg_tag`
**Localization:** `t` (translate), `format_address`, `currency_selector`
**Other:** `date`, `default`, `json`, `structured_data`, `font_face`, `font_url`, `payment_button`

> Full details: [language filters](references/filters-language.md), [HTML/media filters](references/filters-html-media.md), [commerce filters](references/filters-commerce.md)

## Tags Quick Reference

| Category | Tags |
|----------|------|
| **Theme** | `content_for`, `layout`, `section`, `sections`, `schema`, `stylesheet`, `javascript`, `style` |
| **Control** | `if`, `elsif`, `else`, `unless`, `case`, `when` |
| **Iteration** | `for`, `break`, `continue`, `cycle`, `tablerow`, `paginate` |
| **Variable** | `assign`, `capture`, `increment`, `decrement`, `echo` |
| **HTML** | `form`, `render`, `raw`, `comment`, `liquid` |
| **Documentation** | `doc` |

> Full details with syntax and parameters: [references/tags.md](references/tags.md)

## Objects Quick Reference

### Global objects (available everywhere)

`cart`, `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`

### Page-specific objects

| Template | Objects |
|----------|---------|
| `/product` | `product`, `remote_product` |
| `/collection` | `collection`, `current_tags` |
| `/cart` | `cart` |
| `/article` | `article`, `blog` |
| `/blog` | `blog`, `current_tags` |
| `/page` | `page` |
| `/search` | `search` |
| `/customers/*` | `customer`, `order` |

> 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)

## Schema Tag

Sections and blocks require `{% schema %}` with a valid JSON object. Sections use `section.settings.*`, blocks use `block.settings.*`.

### Section schema structure

```json
{
  "name": "t:sections.hero.name",
  "tag": "section",
  "class": "hero-section",
  "limit": 1,
  "settings": [],
  "max_blocks": 16,
  "blocks": [{ "type": "@theme" }],
  "presets": [{ "name": "t:sections.hero.name" }],
  "enabled_on": { "templates": ["index"] },
  "disabled_on": { "templates": ["password"] }
}
```

### Block schema structure

```json
{
  "name": "t:blocks.slide.name",
  "tag": "div",
  "class": "slide",
  "settings": [],
  "blocks": [{ "type": "@theme" }],
  "presets": [{ "name": "t:blocks.slide.name" }]
}
```

### Setting type decision table

| Need | Setting Type | Key Fields |
|------|-------------|------------|
| On/off toggle | `checkbox` | `default: true/false` |
| Short text | `text` | `placeholder` |
| Long text | `textarea` | `placeholder` |
| Rich text (with `<p>`) | `richtext` | — |
| Inline rich text (no `<p>`) | `inline_richtext` | — |
| Number input | `number` | `placeholder` |
| Slider | `range` | `min`, `max`, `default` (all required), `step`, `unit` |
| Dropdown/segmented | `select` | `options: [{value, label}]` |
| Radio buttons | `radio` | `options: [{value, label}]` |
| Text alignment | `text_alignment` | `default: "left"/"center"/"right"` |
| Color picker | `color` | `default: "#000000"` |
| Image upload | `image_picker` | — |
| Video upload | `video` | — |
| External video URL | `video_url` | `accept: ["youtube", "vimeo"]` |
| Product picker | `product` | — |
| Collection picker | `collection` | — |
| Page picker | `page` | — |
| Blog picker | `blog` | — |
| Article picker | `article` | — |
| URL entry | `url` | — |
| Menu picker | `link_list` | — |
| Font picker | `font_picker` | `default` (required) |
| Editor header | `header` | `content` (no `id` needed) |
| Editor description | `paragraph` | `content` (no `id` needed) |

### `visible_if` pattern

```json
{
  "visible_if": "{{ block.settings.layout == 'vertical' }}",
  "type": "select",
  "id": "alignment",
  "label": "t:labels.alignment",
  "options": [...]
}
```

Conditionally shows/hides a setting in the editor based on other setting values.

### Block entry types

- `{ "type": "@theme" }` — Accept any theme block
- `{ "type": "@app" }` — Accept app blocks
- `{ "type": "slide" }` — Accept only the `slide` block type

> Full schema details and all 33 setting types: [references/schema-and-settings.md](references/schema-and-settings.md)

## CSS & JavaScript

### Per-component styles and scripts

Use `{% stylesheet %}` and `{% javascript %}` in sections, blocks, and snippets:

```liquid
{% stylesheet %}
  .my-component { display: flex; }
{% endstylesheet %}

{% javascript %}
  console.log('loaded');
{% endjavascript %}
```

- **One tag each per file** — multiple `{% stylesheet %}` tags will error
- **No Liquid inside** — these tags don't process Liquid; use CSS variables or classes instead
- Only supported in `sections/`, `blocks/`, and `snippets/`

### `{% style %}` tag (Liquid-aware CSS)

For dynamic CSS that needs Liquid (e.g., color settings that live-update in editor):

```liquid
{% style %}
  .section-{{ section.id }} {
    background: {{ section.settings.bg_color }};
  }
{% endstyle %}
```

### CSS patterns for settings

**Single CSS property** — use CSS variables:
```liquid
<div style="--gap: {{ block.settings.gap }}px">
```

**Multiple CSS properties** — use CSS classes as select values:
```liquid
<div class="{{ block.settings.layout }}">
```

## LiquidDoc (`{% doc %}`)

**Required for:** snippets (always), blocks (when statically rendered via `{% content_for 'block' %}`)

```liquid
{% doc %}
  Brief description of what this file renders.

  @param {type} name - Description of required parameter
  @param {type} [name] - Description of optional parameter (brackets = optional)

  @example
  {% render 'snippet-name', name: value %}
{% enddoc %}
```

**Param types:** `string`, `number`, `boolean`, `image`, `object`, `array`

## Translations

### Every user-facing string must use the `t` filter

```liquid
<!-- Correct -->
<h2>{{ 'sections.hero.heading' | t }}</h2>
<button>{{ 'products.add_to_cart' | t }}</button>

<!-- Wrong — never hardcode strings -->
<h2>Welcome to our store</h2>
```

### Variable interpolation

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

Locale file:
```json
{
  "products": {
    "price_range": "From {{ min }} to {{ max }}"
  }
}
```

### Locale file structure

```
locales/
├── en.default.json          # English translations (required)
├── en.default.schema.json   # Editor setting translations (required)
├── fr.json                  # French translations
└── fr.schema.json           # French editor translations
```

### Key naming conventions

- Use **snake_case** and **hierarchical keys** (max 3 levels)
- Use **sentence case** for all text (capitalize first word only)
- Schema labels use `t:` prefix: `"label": "t:labels.heading"`
- Group by component: `sections.hero.heading`, `blocks.slide.title`

## References

- Filters: [language](references/filters-language.md) (77), [HTML/media](references/filters-html-media.md) (45), [commerce](references/filters-commerce.md) (30)
- [Tag reference (30 tags)](references/tags.md)
- 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)
- [Schema & settings reference (33 types)](references/schema-and-settings.md)
- [Complete examples (snippet, block, section)](references/examples.md)

Overview

This skill generates production-ready Shopify Liquid theme code for sections, blocks, and snippets with correct schema JSON, LiquidDoc headers, translation keys, and per-component CSS/JS patterns. I produce valid {% schema %} JSON, editor-friendly labels and visible_if logic, locale keys, and compliant stylesheet/javascript/style usage. Use this skill when creating or editing .liquid files to avoid common Liquid and schema mistakes.

How this skill works

Given a description of the component you want, I output a .liquid-ready file that includes a LiquidDoc block (when required), the Liquid markup, a complete {% schema %} JSON object (section or block), and example translation keys. I follow Shopify constraints: one stylesheet/javascript tag per file, no Liquid inside {% stylesheet %}/{% javascript %}, proper use of section.settings/block.settings, and visible_if patterns. I also suggest CSS variable or class patterns and passable render params for snippets.

When to use it

  • Creating a new section that merchants can add and configure in the editor
  • Building a block type meant to nest inside a section with editor settings
  • Writing a reusable snippet that requires LiquidDoc and param documentation
  • Converting hardcoded strings into translation keys for locales
  • Adding per-component CSS/JS while respecting Shopify tag constraints

Best practices

  • Always wrap user-facing text with t translations and define locale keys
  • Include a {% doc %} block for snippets and for blocks rendered statically
  • Use CSS variables for single dynamic properties and classes for multiple
  • Limit for-loops to expected sizes and use paginate for large collections
  • Pass outer-scope values to snippets via {% render 'name', var: value %}

Example use cases

  • Generate a hero section with image_picker, heading, CTA button, and preset
  • Create a carousel block schema that accepts slides and a slide block type
  • Produce a snippet for a product price helper with LiquidDoc and params
  • Add editor-visible settings using visible_if to surface conditional options
  • Output per-component {% style %} that uses section.settings color values

FAQ

Do you include locale file entries?

Yes — I provide suggested translation keys and example locale JSON entries for en.default.json and schema labels with t: prefixes.

Can generated CSS/JS contain Liquid?

No — I put Liquid-aware dynamic styles in {% style %} and keep {% stylesheet %}/{% javascript %} free of Liquid per Shopify rules.