Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 19 additions & 22 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,18 @@ 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.
- **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 `<main>`; `layout/password.liquid` renders
`content_for_layout` in a plain `<main>`, 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 (`<li {% ... %}`, not `<li{% ... %}`).
- **Translated UI only:** every user-facing string uses a literal
Expand All @@ -29,17 +31,16 @@ file. The code is the source of truth.
## Page structure

```
layout/theme.liquid → {% block 'container' %} header, content_for_layout, footer
layout/password.liquid → {% block 'container' %} → content_for_layout
templates/*.liquid → blocks / snippets / inline HTML (rendered in the container)
layout/theme.liquid → {% block 'container' %} (header) + <main> content_for_layout + {% block 'container' %} (footer)
layout/password.liquid → <main> 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 `<main>`. 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

Expand Down Expand Up @@ -92,10 +93,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`.

Expand All @@ -104,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 + <main>
snippets/ internal utilities (css-variables, image, meta-tags)
assets/critical.css all theme CSS
assets/ CSS, JavaScript, and other static assets
```
33 changes: 18 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -72,15 +72,17 @@ templates/*.liquid → {% block 'container' %} → blocks / snippets / inline ma

[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:
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 `<main>` and reserves the `container`
block for the header and footer only.

For example, `templates/index.liquid` composes the `hello-world` block inside a
For example, `templates/index.liquid` wraps the `hello-world` block in a
container:

```liquid
{% block 'container', tag: 'div' %}
{% block 'container' %}
{% block 'hello-world' %}{% endblock %}
{% endblock %}
```
Expand All @@ -94,29 +96,30 @@ 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 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 `<main>`. `blocks/hello-world.liquid` is the
theme's starter demo block.

## Non-negotiables

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
Comment thread
karreiro marked this conversation as resolved.
`{% 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

Expand Down
4 changes: 2 additions & 2 deletions layout/password.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
</head>

<body>
{% block 'container', block.settings.alignment: 'center' %}
<main>
{{ content_for_layout }}
{% endblock %}
</main>
</body>
</html>
6 changes: 5 additions & 1 deletion layout/theme.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,15 @@
</head>

<body>
{% block 'container' %}
{% block 'container', tag: 'div' %}
{% block 'header' -%}{%- endblock %}
{% endblock %}

<main>
{{ content_for_layout }}
</main>

{% block 'container', tag: 'div' %}
{% block 'footer' -%}{%- endblock %}
{% endblock %}
</body>
Expand Down
16 changes: 9 additions & 7 deletions templates/404.liquid
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<h1>{{ '404.title' | t }}</h1>
{% block 'container' %}
<h1>{{ '404.title' | t }}</h1>

<p>
{{ '404.not_found' | t }}
</p>
<p>
{{ '404.not_found' | t }}
</p>

<a href="{{ routes.all_products_collection_url }}">
{{ '404.back_to_shopping' | t }}
</a>
<a href="{{ routes.all_products_collection_url }}">
{{ '404.back_to_shopping' | t }}
</a>
{% endblock %}
96 changes: 49 additions & 47 deletions templates/article.liquid
Original file line number Diff line number Diff line change
@@ -1,53 +1,55 @@
{% if article.image %}
{{ article.image | image_url: width: 1000 | image_tag }}
{% endif %}

<h1>{{ article.title }}</h1>

{% assign date = article.published_at | time_tag: format: 'date' %}
<p>{{ 'blog.article_metadata_html' | t: date: date, author: article.author }}</p>

{{ article.content }}

{% if blog.comments_enabled? %}
<h2>{{ 'blog.article_comments' | t }}</h2>

<div id="comments">
{% paginate article.comments by 10 %}
{% for comment in article.comments %}
<div>
<p>{{ comment.author }}</p>
<p>
{{- comment.created_at | time_tag: format: 'date' -}}
</p>
<p>{{ comment.content }}</p>
</div>
{% endfor %}
{% block 'container' %}
{% if article.image %}
{{ article.image | image_url: width: 1000 | image_tag }}
{% endif %}

<h1>{{ article.title }}</h1>

{% assign date = article.published_at | time_tag: format: 'date' %}
<p>{{ 'blog.article_metadata_html' | t: date: date, author: article.author }}</p>

{{ article.content }}

{% if blog.comments_enabled? %}
<h2>{{ 'blog.article_comments' | t }}</h2>

<div id="comments">
{% paginate article.comments by 10 %}
{% for comment in article.comments %}
<div>
<p>{{ comment.author }}</p>
<p>
{{- comment.created_at | time_tag: format: 'date' -}}
</p>
<p>{{ comment.content }}</p>
</div>
{% endfor %}

{{ paginate | default_pagination: anchor: 'comments' }}
{% endpaginate %}
</div>

{{ paginate | default_pagination: anchor: 'comments' }}
{% endpaginate %}
</div>
{% form 'new_comment', article %}
<h2>{{ 'blog.comment_form_title' | t }}</h2>

{% form 'new_comment', article %}
<h2>{{ 'blog.comment_form_title' | t }}</h2>
{{ form.errors | default_errors }}

{{ form.errors | default_errors }}
<div>
<label for="author">{{ 'blog.comment_form_name' | t }}</label>
<input type="text" name="comment[author]" id="author" value="{{ form.author }}" required>
</div>

<div>
<label for="author">{{ 'blog.comment_form_name' | t }}</label>
<input type="text" name="comment[author]" id="author" value="{{ form.author }}" required>
</div>
<div>
<label for="email">{{ 'blog.comment_form_email' | t }}</label>
<input type="email" name="comment[email]" id="email" value="{{ form.email }}" required>
</div>

<div>
<label for="email">{{ 'blog.comment_form_email' | t }}</label>
<input type="email" name="comment[email]" id="email" value="{{ form.email }}" required>
</div>

<div>
<label for="body">{{ 'blog.comment_form_body' | t }}</label>
<textarea name="comment[body]" id="body" required>{{ form.body }}</textarea>
</div>
<div>
<label for="body">{{ 'blog.comment_form_body' | t }}</label>
<textarea name="comment[body]" id="body" required>{{ form.body }}</textarea>
</div>

<input type="submit" value="{{ 'blog.comment_form_submit' | t }}">
{% endform %}
{% endif %}
<input type="submit" value="{{ 'blog.comment_form_submit' | t }}">
{% endform %}
{% endif %}
{% endblock %}
40 changes: 21 additions & 19 deletions templates/blog.liquid
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
<h1>{{ blog.title }}</h1>
{% block 'container' %}
<h1>{{ blog.title }}</h1>

{% paginate blog.articles by 5 %}
{% for article in blog.articles %}
<div>
{% if article.image %}
{{ article.image | image_url: width: 1000 | image_tag }}
{% endif %}
<h2>
{{ article.title | link_to: article.url }}
</h2>
{% assign date = article.published_at | time_tag: format: 'date' %}
<p>{{ 'blog.article_metadata_html' | t: date: date, author: article.author }}</p>
<p>{{ article.excerpt }}</p>
</div>
{% endfor %}
{% paginate blog.articles by 5 %}
{% for article in blog.articles %}
<div>
{% if article.image %}
{{ article.image | image_url: width: 1000 | image_tag }}
{% endif %}
<h2>
{{ article.title | link_to: article.url }}
</h2>
{% assign date = article.published_at | time_tag: format: 'date' %}
<p>{{ 'blog.article_metadata_html' | t: date: date, author: article.author }}</p>
<p>{{ article.excerpt }}</p>
</div>
{% endfor %}

{%- if paginate.pages > 1 -%}
{{- paginate | default_pagination -}}
{%- endif -%}
{% endpaginate %}
{%- if paginate.pages > 1 -%}
{{- paginate | default_pagination -}}
{%- endif -%}
{% endpaginate %}
{% endblock %}
44 changes: 23 additions & 21 deletions templates/cart.liquid
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
<h1>{{ 'cart.title' | t }}</h1>
{% block 'container' %}
<h1>{{ 'cart.title' | t }}</h1>

<form action="{{ routes.cart_url }}" method="post">
<table>
{% for item in cart.items %}
<tr>
<td>
{% render 'image', image: item.image, url: item.url %}
</td>
<td>
<p>{{ item.product.title }}</p>
{{ 'cart.remove' | t | link_to: item.url_to_remove }}
</td>
<td>
<input type="text" name="updates[]" value="{{ item.quantity }}">
<input type="submit" value="{{ 'cart.update' | t }}">
</td>
</tr>
{% endfor %}
</table>
<form action="{{ routes.cart_url }}" method="post">
<table>
{% for item in cart.items %}
<tr>
<td>
{% render 'image', image: item.image, url: item.url %}
</td>
<td>
<p>{{ item.product.title }}</p>
{{ 'cart.remove' | t | link_to: item.url_to_remove }}
</td>
<td>
<input type="text" name="updates[]" value="{{ item.quantity }}">
<input type="submit" value="{{ 'cart.update' | t }}">
</td>
</tr>
{% endfor %}
</table>

<input type="submit" name="checkout" value="{{ 'cart.checkout' | t }}">
</form>
<input type="submit" name="checkout" value="{{ 'cart.checkout' | t }}">
</form>
{% endblock %}
Loading
Loading