From abf9b68f7fb831c30545879589d6383b8021d9fb Mon Sep 17 00:00:00 2001 From: Guilherme Carreiro Date: Wed, 22 Jul 2026 15:57:39 -0400 Subject: [PATCH 1/2] Align Skeleton documentation conventions --- AGENTS.md | 8 ++------ README.md | 38 +++++++++++++++++++------------------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 977f67039..064bab573 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,7 +9,7 @@ file. The code is the source of truth. - **No sections:** no `sections/` folder, no `{% section %}`/`{% sections %}` tags, no JSON templates, no schema `presets`. - **No Liquid-embedded assets:** no `{% stylesheet %}`, no `{% javascript %}`. - All CSS lives in `assets/critical.css`. + All CSS and JavaScript live in `assets/`. - **Direct Liquid templates:** templates render page content from blocks, snippets, and inline markup. Don't introduce a section for markup that is used by only one page. @@ -92,10 +92,6 @@ Skeleton keeps `.theme-check.yml` as a pristine `extends: theme-check:recommended` with **zero overrides**. Fix Theme Check errors in the Liquid instead of adding configuration exceptions. -Blocks without a leading underscore can be rendered from any template or -block. A leading underscore, as in `_private`, marks a block that belongs to a -specific parent or context. - Current blocks: `container`, `hello-world`, `text`, `header`, `footer`, `liquid-tips`. @@ -106,5 +102,5 @@ blocks/ container, hello-world, text, header, footer, liquid-tips templates/ *.liquid page structure (no JSON templates) layout/ theme.liquid document shell with header/footer blocks snippets/ internal utilities (css-variables, image, meta-tags) -assets/critical.css all theme CSS +assets/ CSS, JavaScript, and other static assets ``` diff --git a/README.md b/README.md index 1c5df6e1e..9db124322 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ shopify theme dev ```bash . -├── assets # Static assets, including critical.css (all theme CSS) +├── assets # CSS, JavaScript, and other static assets ├── blocks # Reusable, nestable, customizable UI components ├── config # Global theme settings and customization options ├── layout # Top-level page wrappers @@ -65,24 +65,23 @@ To learn more, refer to the [theme architecture documentation](https://shopify.d Every page is composed from blocks. The composition flows in one direction: ``` -templates/*.liquid → {% block 'container' %} → blocks / snippets / inline markup +layout/*.liquid → {% block 'container' %} → content_for_layout → templates/*.liquid ``` ### Templates [Templates](https://shopify.dev/docs/storefronts/themes/architecture/templates#template-types) control what's rendered on each type of page. In this theme they are Liquid -files (`templates/*.liquid`), not JSON. Each template is a composition root: it -wraps its page content in the `container` block and composes the rest from -blocks and inline markup. +files (`templates/*.liquid`), not JSON. Each template is a composition root +that renders blocks and inline markup directly. The layout wraps +`content_for_layout` in a single `container` block, so templates do not add +their own containers. -For example, `templates/index.liquid` composes the `hello-world` block inside a -container: +For example, `templates/index.liquid` composes the `hello-world` block +directly: ```liquid -{% block 'container', tag: 'div' %} - {% block 'hello-world' %}{% endblock %} -{% endblock %} +{% block 'hello-world' %}{% endblock %} ``` ### Blocks @@ -94,9 +93,11 @@ a `{% doc %}` header describing its parameters, and ends with a `{% schema %}` `{{ block.content }}` and keeps `{{ block.shopify_attributes }}` on its root element for theme-editor support. -The `container` block owns each page's outer layout element; `blocks/hello-world.liquid` -is the theme's starter demo block. `layout/theme.liquid` composes the `header` -and `footer` blocks directly, keeping the layout a thin shell. +The `container` block owns each page's outer layout element. Skeleton +intentionally uses one layout-owned container around `content_for_layout`: +the theme is simple enough that per-template containers do not justify the +additional pattern. `blocks/hello-world.liquid` is the theme's starter demo +block. `layout/theme.liquid` composes the `header` and `footer` blocks directly. ## Non-negotiables @@ -104,19 +105,18 @@ This theme deliberately excludes the section-based model. When editing it: - No `sections/` directory, and no `{% section %}` / `{% sections %}` tags. - No JSON templates and no schema `presets`. -- No Liquid-embedded assets: keep CSS in `assets/critical.css` rather than +- No Liquid-embedded assets: keep all CSS and JavaScript in `assets/` rather `{% stylesheet %}` / `{% javascript %}` blocks. - Compose pages from blocks and inline markup, not single-use page sections. [`AGENTS.md`](./AGENTS.md) is the source of truth for the theme's dialect and the full set of rules coding agents follow. -## CSS +## CSS and JavaScript -All theme CSS lives in [`assets/critical.css`](./assets/critical.css), loaded -once from `layout/theme.liquid`. Keeping styles in one file — rather than -embedding them in Liquid — preserves the theme's minimalism and keeps blocks -markup-only. +All theme CSS and JavaScript live in [`assets/`](./assets/), rather than being +embedded in Liquid. This keeps blocks focused on markup without requiring +assets to live in a single file. ## Contributing From ef485d1ffb0666060088f259da99df6414dd42ce Mon Sep 17 00:00:00 2001 From: Guilherme Carreiro Date: Wed, 22 Jul 2026 17:11:43 -0400 Subject: [PATCH 2/2] Move container blocks from layout into templates --- AGENTS.md | 33 +++++------ README.md | 29 +++++----- layout/password.liquid | 4 +- layout/theme.liquid | 6 +- templates/404.liquid | 16 +++--- templates/article.liquid | 96 ++++++++++++++++--------------- templates/blog.liquid | 40 +++++++------ templates/cart.liquid | 44 +++++++------- templates/collection.liquid | 48 ++++++++-------- templates/index.liquid | 4 +- templates/list-collections.liquid | 46 ++++++++------- templates/page.liquid | 6 +- templates/password.liquid | 34 +++++------ templates/product.liquid | 62 ++++++++++---------- templates/search.liquid | 78 +++++++++++++------------ 15 files changed, 288 insertions(+), 258 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 064bab573..ee1071e57 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,12 +13,14 @@ file. The code is the source of truth. - **Direct Liquid templates:** templates render page content from blocks, snippets, and inline markup. Don't introduce a section for markup that is used by only one page. -- **Container-owned layout:** both `layout/theme.liquid` and - `layout/password.liquid` wrap `content_for_layout` in the `container` block, - which owns the outer layout element, so templates render their content - directly with no per-template container. The only exception is - `gift_card.liquid` (`{% layout none %}`): with no layout there is no - layout-owned container, so it manages its own structure. +- **Template-owned containers:** each `templates/*.liquid` file is the + composition root and wraps its page content in one or more `container` + blocks — one per vertical slice. `layout/theme.liquid` wraps only the + `header` and `footer` blocks in their own `container` blocks and renders + `content_for_layout` in a plain `
`; `layout/password.liquid` renders + `content_for_layout` in a plain `
`, and its template owns the + container. The exception is `gift_card.liquid` (`{% layout none %}`): it + manages its own document structure. - **Whitespace matters:** include whitespace between an HTML tag name and a following Liquid delimiter (`
  • content_for_layout + {% block 'container' %} (footer) +layout/password.liquid →
    content_for_layout +templates/*.liquid → {% block 'container' %} → blocks / snippets / inline HTML ``` -Both `layout/theme.liquid` and `layout/password.liquid` wrap -`content_for_layout` in the `container` block, so every template with a layout -renders inside one layout-owned container. In `theme.liquid` the `header` and -`footer` blocks are nested inside that container alongside `content_for_layout`. -Each template shows its page content directly — blocks, snippets, or HTML — -with no per-template container wrapper. +Neither layout wraps `content_for_layout` in a `container` block; each renders +it in a plain `
    `. In `theme.liquid` the `header` and `footer` blocks each +get their own `container` block. Every template is the composition root and +wraps its page content in one or more `container` blocks — a template may hold +any number of containers, one per vertical slice. ## The block tag @@ -100,7 +101,7 @@ Current blocks: `container`, `hello-world`, `text`, `header`, `footer`, ``` blocks/ container, hello-world, text, header, footer, liquid-tips templates/ *.liquid page structure (no JSON templates) -layout/ theme.liquid document shell with header/footer blocks +layout/ theme.liquid document shell: header/footer container blocks +
    snippets/ internal utilities (css-variables, image, meta-tags) assets/ CSS, JavaScript, and other static assets ``` diff --git a/README.md b/README.md index 9db124322..cba186f5f 100644 --- a/README.md +++ b/README.md @@ -65,23 +65,26 @@ To learn more, refer to the [theme architecture documentation](https://shopify.d Every page is composed from blocks. The composition flows in one direction: ``` -layout/*.liquid → {% block 'container' %} → content_for_layout → templates/*.liquid +templates/*.liquid → {% block 'container' %} → blocks / snippets / inline markup ``` ### Templates [Templates](https://shopify.dev/docs/storefronts/themes/architecture/templates#template-types) control what's rendered on each type of page. In this theme they are Liquid -files (`templates/*.liquid`), not JSON. Each template is a composition root -that renders blocks and inline markup directly. The layout wraps -`content_for_layout` in a single `container` block, so templates do not add -their own containers. +files (`templates/*.liquid`), not JSON. Each template is a composition root: +it wraps its page content in one or more `container` blocks — one per vertical +slice — and composes blocks, snippets, and inline markup inside them. The layout +renders `content_for_layout` in a plain `
    ` and reserves the `container` +block for the header and footer only. -For example, `templates/index.liquid` composes the `hello-world` block -directly: +For example, `templates/index.liquid` wraps the `hello-world` block in a +container: ```liquid -{% block 'hello-world' %}{% endblock %} +{% block 'container' %} + {% block 'hello-world' %}{% endblock %} +{% endblock %} ``` ### Blocks @@ -93,11 +96,11 @@ a `{% doc %}` header describing its parameters, and ends with a `{% schema %}` `{{ block.content }}` and keeps `{{ block.shopify_attributes }}` on its root element for theme-editor support. -The `container` block owns each page's outer layout element. Skeleton -intentionally uses one layout-owned container around `content_for_layout`: -the theme is simple enough that per-template containers do not justify the -additional pattern. `blocks/hello-world.liquid` is the theme's starter demo -block. `layout/theme.liquid` composes the `header` and `footer` blocks directly. +The `container` block owns a page region's outer layout element. Each template +wraps its content in one or more `container` blocks, and `layout/theme.liquid` +wraps the `header` and `footer` blocks in their own containers while rendering +`content_for_layout` in a plain `
    `. `blocks/hello-world.liquid` is the +theme's starter demo block. ## Non-negotiables diff --git a/layout/password.liquid b/layout/password.liquid index 168ae0426..db94f7a01 100644 --- a/layout/password.liquid +++ b/layout/password.liquid @@ -15,8 +15,8 @@ - {% block 'container', block.settings.alignment: 'center' %} +
    {{ content_for_layout }} - {% endblock %} +
    diff --git a/layout/theme.liquid b/layout/theme.liquid index be36ad84f..dbb6c77bd 100644 --- a/layout/theme.liquid +++ b/layout/theme.liquid @@ -28,11 +28,15 @@ - {% block 'container' %} + {% block 'container', tag: 'div' %} {% block 'header' -%}{%- endblock %} + {% endblock %} +
    {{ content_for_layout }} +
    + {% block 'container', tag: 'div' %} {% block 'footer' -%}{%- endblock %} {% endblock %} diff --git a/templates/404.liquid b/templates/404.liquid index 71e66ec67..621c1ba75 100644 --- a/templates/404.liquid +++ b/templates/404.liquid @@ -1,9 +1,11 @@ -

    {{ '404.title' | t }}

    +{% block 'container' %} +

    {{ '404.title' | t }}

    -

    - {{ '404.not_found' | t }} -

    +

    + {{ '404.not_found' | t }} +

    - - {{ '404.back_to_shopping' | t }} - + + {{ '404.back_to_shopping' | t }} + +{% endblock %} diff --git a/templates/article.liquid b/templates/article.liquid index 267e9191f..20f75de65 100644 --- a/templates/article.liquid +++ b/templates/article.liquid @@ -1,53 +1,55 @@ -{% if article.image %} - {{ article.image | image_url: width: 1000 | image_tag }} -{% endif %} - -

    {{ article.title }}

    - -{% assign date = article.published_at | time_tag: format: 'date' %} -

    {{ 'blog.article_metadata_html' | t: date: date, author: article.author }}

    - -{{ article.content }} - -{% if blog.comments_enabled? %} -

    {{ 'blog.article_comments' | t }}

    - -
    - {% paginate article.comments by 10 %} - {% for comment in article.comments %} -
    -

    {{ comment.author }}

    -

    - {{- comment.created_at | time_tag: format: 'date' -}} -

    -

    {{ comment.content }}

    -
    - {% endfor %} +{% block 'container' %} + {% if article.image %} + {{ article.image | image_url: width: 1000 | image_tag }} + {% endif %} + +

    {{ article.title }}

    + + {% assign date = article.published_at | time_tag: format: 'date' %} +

    {{ 'blog.article_metadata_html' | t: date: date, author: article.author }}

    + + {{ article.content }} + + {% if blog.comments_enabled? %} +

    {{ 'blog.article_comments' | t }}

    + +
    + {% paginate article.comments by 10 %} + {% for comment in article.comments %} +
    +

    {{ comment.author }}

    +

    + {{- comment.created_at | time_tag: format: 'date' -}} +

    +

    {{ comment.content }}

    +
    + {% endfor %} + + {{ paginate | default_pagination: anchor: 'comments' }} + {% endpaginate %} +
    - {{ paginate | default_pagination: anchor: 'comments' }} - {% endpaginate %} -
    + {% form 'new_comment', article %} +

    {{ 'blog.comment_form_title' | t }}

    - {% form 'new_comment', article %} -

    {{ 'blog.comment_form_title' | t }}

    + {{ form.errors | default_errors }} - {{ form.errors | default_errors }} +
    + + +
    -
    - - -
    +
    + + +
    -
    - - -
    - -
    - - -
    +
    + + +
    - - {% endform %} -{% endif %} + + {% endform %} + {% endif %} +{% endblock %} diff --git a/templates/blog.liquid b/templates/blog.liquid index aea96e14c..be7176e7e 100644 --- a/templates/blog.liquid +++ b/templates/blog.liquid @@ -1,21 +1,23 @@ -

    {{ blog.title }}

    +{% block 'container' %} +

    {{ blog.title }}

    -{% paginate blog.articles by 5 %} - {% for article in blog.articles %} -
    - {% if article.image %} - {{ article.image | image_url: width: 1000 | image_tag }} - {% endif %} -

    - {{ article.title | link_to: article.url }} -

    - {% assign date = article.published_at | time_tag: format: 'date' %} -

    {{ 'blog.article_metadata_html' | t: date: date, author: article.author }}

    -

    {{ article.excerpt }}

    -
    - {% endfor %} + {% paginate blog.articles by 5 %} + {% for article in blog.articles %} +
    + {% if article.image %} + {{ article.image | image_url: width: 1000 | image_tag }} + {% endif %} +

    + {{ article.title | link_to: article.url }} +

    + {% assign date = article.published_at | time_tag: format: 'date' %} +

    {{ 'blog.article_metadata_html' | t: date: date, author: article.author }}

    +

    {{ article.excerpt }}

    +
    + {% endfor %} - {%- if paginate.pages > 1 -%} - {{- paginate | default_pagination -}} - {%- endif -%} -{% endpaginate %} + {%- if paginate.pages > 1 -%} + {{- paginate | default_pagination -}} + {%- endif -%} + {% endpaginate %} +{% endblock %} diff --git a/templates/cart.liquid b/templates/cart.liquid index d998d27ae..65cd5f264 100644 --- a/templates/cart.liquid +++ b/templates/cart.liquid @@ -1,23 +1,25 @@ -

    {{ 'cart.title' | t }}

    +{% block 'container' %} +

    {{ 'cart.title' | t }}

    -
    - - {% for item in cart.items %} - - - - - - {% endfor %} -
    - {% render 'image', image: item.image, url: item.url %} - -

    {{ item.product.title }}

    - {{ 'cart.remove' | t | link_to: item.url_to_remove }} -
    - - -
    + + + {% for item in cart.items %} + + + + + + {% endfor %} +
    + {% render 'image', image: item.image, url: item.url %} + +

    {{ item.product.title }}

    + {{ 'cart.remove' | t | link_to: item.url_to_remove }} +
    + + +
    - -
    + + +{% endblock %} diff --git a/templates/collection.liquid b/templates/collection.liquid index 2d08c0d85..254007edd 100644 --- a/templates/collection.liquid +++ b/templates/collection.liquid @@ -1,26 +1,28 @@ -

    {{ collection.title }}

    +{% block 'container' %} +

    {{ collection.title }}

    -
    - {% paginate collection.products by 20 %} - {% for product in collection.products %} -
    - {% if product.featured_image %} - {% render 'image', - class: 'collection-product__image', - image: product.featured_image, - url: product.url, - width: 400, - height: 400, - crop: 'center' - %} - {% endif %} -
    -

    {{ product.title | escape | link_to: product.url }}

    -

    {{ product.price | money }}

    +
    + {% paginate collection.products by 20 %} + {% for product in collection.products %} +
    + {% if product.featured_image %} + {% render 'image', + class: 'collection-product__image', + image: product.featured_image, + url: product.url, + width: 400, + height: 400, + crop: 'center' + %} + {% endif %} +
    +

    {{ product.title | escape | link_to: product.url }}

    +

    {{ product.price | money }}

    +
    -
    - {% endfor %} + {% endfor %} - {{ paginate | default_pagination }} - {% endpaginate %} -
    + {{ paginate | default_pagination }} + {% endpaginate %} +
    +{% endblock %} diff --git a/templates/index.liquid b/templates/index.liquid index bc0552f01..9a5d81d21 100644 --- a/templates/index.liquid +++ b/templates/index.liquid @@ -1 +1,3 @@ -{% block 'hello-world' %}{% endblock %} +{% block 'container' %} + {% block 'hello-world' %}{% endblock %} +{% endblock %} diff --git a/templates/list-collections.liquid b/templates/list-collections.liquid index a467f0ee4..8acd78e81 100644 --- a/templates/list-collections.liquid +++ b/templates/list-collections.liquid @@ -1,25 +1,27 @@ -

    {{ 'collections.title' | t }}

    +{% block 'container' %} +

    {{ 'collections.title' | t }}

    - +{% endblock %} diff --git a/templates/page.liquid b/templates/page.liquid index af24238a6..56f217da2 100644 --- a/templates/page.liquid +++ b/templates/page.liquid @@ -1,3 +1,5 @@ -

    {{ page.title }}

    +{% block 'container' %} +

    {{ page.title }}

    -{{ page.content }} + {{ page.content }} +{% endblock %} diff --git a/templates/password.liquid b/templates/password.liquid index 039360196..38c77cc99 100644 --- a/templates/password.liquid +++ b/templates/password.liquid @@ -1,23 +1,25 @@ {% layout 'password' %} -

    {{ 'password.title' | t }}

    +{% block 'container', block.settings.alignment: 'center' %} +

    {{ 'password.title' | t }}

    -{% if shop.password_message %} -

    {{ shop.password_message }}

    -{% endif %} - -{% form 'storefront_password' %} - {% if form.errors %} - {{ form.errors | default_errors }} + {% if shop.password_message %} +

    {{ shop.password_message }}

    {% endif %} - + {% form 'storefront_password' %} + {% if form.errors %} + {{ form.errors | default_errors }} + {% endif %} + + - + - -{% endform %} + + {% endform %} +{% endblock %} diff --git a/templates/product.liquid b/templates/product.liquid index c4b34b2a7..422c33af3 100644 --- a/templates/product.liquid +++ b/templates/product.liquid @@ -1,35 +1,37 @@ -
    - {% for image in product.images %} - {% render 'image', class: 'product-image', image: image %} - {% endfor %} -
    +{% block 'container' %} +
    + {% for image in product.images %} + {% render 'image', class: 'product-image', image: image %} + {% endfor %} +
    -
    -

    {{ product.title }}

    -

    {{ product.price | money }}

    -

    {{ product.description }}

    -
    +
    +

    {{ product.title }}

    +

    {{ product.price | money }}

    +

    {{ product.description }}

    +
    -
    - {% form 'product', product %} - {% assign current_variant = product.selected_or_first_available_variant %} +
    + {% form 'product', product %} + {% assign current_variant = product.selected_or_first_available_variant %} - + - + - - {{ form | payment_button }} - {% endform %} -
    + + {{ form | payment_button }} + {% endform %} +
    +{% endblock %} diff --git a/templates/search.liquid b/templates/search.liquid index ed50714ca..52c351296 100644 --- a/templates/search.liquid +++ b/templates/search.liquid @@ -1,44 +1,46 @@ -

    {{ 'search.title' | t }}

    +{% block 'container' %} +

    {{ 'search.title' | t }}

    -
    - - -
    +
    + + +
    -{% if search.performed %} - {% if search.results_count == 0 %} -

    {{ 'search.no_results_html' | t: terms: search.terms }}

    - {% else %} -

    {{ 'search.results_for_html' | t: terms: search.terms, count: search.results_count }}

    + {% if search.performed %} + {% if search.results_count == 0 %} +

    {{ 'search.no_results_html' | t: terms: search.terms }}

    + {% else %} +

    {{ 'search.results_for_html' | t: terms: search.terms, count: search.results_count }}

    -
    - {% paginate search.results by 20 %} - {% # Search result items may be an article, a page, or a product. %} - {% for result in search.results %} -
    - {% assign featured_image = result.featured_image | default: result.image %} - {% if featured_image %} - {% render 'image', class: 'search-result__image', image: featured_image, url: result.url, width: 400 %} - {% endif %} -
    -

    - {{ result.title | link_to: result.url }} - {% if result.price %} - {{ result.price | money_with_currency }} - {% endif %} -

    +
    + {% paginate search.results by 20 %} + {% # Search result items may be an article, a page, or a product. %} + {% for result in search.results %} +
    + {% assign featured_image = result.featured_image | default: result.image %} + {% if featured_image %} + {% render 'image', class: 'search-result__image', image: featured_image, url: result.url, width: 400 %} + {% endif %} +
    +

    + {{ result.title | link_to: result.url }} + {% if result.price %} + {{ result.price | money_with_currency }} + {% endif %} +

    +
    -
    - {% endfor %} + {% endfor %} - {{ paginate | default_pagination }} - {% endpaginate %} -
    + {{ paginate | default_pagination }} + {% endpaginate %} +
    + {% endif %} {% endif %} -{% endif %} +{% endblock %}