Layouts
Layouts are theme-level templates that define the overall HTML structure of your pages. They wrap page content and declare zones -- named regions where site-wide sections (navigation, footer, sidebars) can be placed by the admin.
Every page uses one layout. The layout controls the <html>, <head>, and <body> structure, while page-specific content is injected via {% content_for_layout %}.
+-------------------------------------------+
| Layout (default.liquid) |
| |
| {% zone 'header' %} |
| [nav section] [banner section] |
| {% endzone %} |
| |
| <main> |
| {% content_for_layout %} |
| [page section 1] |
| [page section 2] |
| [page section 3] |
| </main> |
| |
| {% zone 'footer' %} |
| [footer section] |
| {% endzone %} |
| |
+-------------------------------------------+
Layout Files
Layouts live in the layouts/ directory of your theme:
my_theme/
└── layouts/
├── default.liquid # Standard layout (header + footer)
├── sidebar.liquid # Layout with sidebar zone
├── landing.liquid # Full-width landing page
└── minimal.liquid # No header or footer
Each page selects which layout to use. Sections placed in layout zones appear on all pages using that layout.
Required Tags
Every layout must include these three tags:
| Tag | Purpose |
|---|---|
{% theme_css %} | Loads compiled theme CSS plus section CSS. Place in <head>. |
{% theme_javascript %} | Loads theme JS and the visual editor overlay. Place before </body>. |
{% content_for_layout %} | Outputs the page-specific sections. |
Without these tags, your theme will not render correctly.
Minimal Layout Example
The simplest possible layout:
<!DOCTYPE html>
<html lang="{{ request.locale | default: 'en' }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ page.title | default: settings.site_name }}</title>
{% theme_css %}
</head>
<body>
<main>
{% content_for_layout %}
</main>
{% theme_javascript %}
</body>
</html>
This layout has no zones -- page sections render directly with no site-wide header or footer.
Zones
Zones are named placeholders in a layout where the admin can place sections. They are the key to having site-wide elements like navigation and footers that appear on every page using that layout.
Declaring Zones
Use the {% zone %} block tag to declare a zone:
{% zone 'header' %}{% endzone %}
<main>
{% content_for_layout %}
</main>
{% zone 'footer' %}{% endzone %}
Zone Names
Use any name you want. Names become human-readable labels in the admin UI automatically:
{% zone 'topbar' %}{% endzone %} {%- comment -%} Shows as "Topbar" {%- endcomment -%}
{% zone 'navigation' %}{% endzone %} {%- comment -%} Shows as "Navigation" {%- endcomment -%}
{% zone 'left_sidebar' %}{% endzone %} {%- comment -%} Shows as "Left Sidebar" {%- endcomment -%}
{% zone 'above_fold' %}{% endzone %} {%- comment -%} Shows as "Above Fold" {%- endcomment -%}
Zone Auto-Discovery
The admin UI automatically discovers zones by parsing your layout template. When you add a new {% zone %} tag to a layout file, it appears in the admin editor without any configuration.
Default Content
Zones can include default content that renders when no sections have been assigned:
{% zone 'sidebar' %}
<aside class="default-sidebar">
<p>Add sections to customize this sidebar.</p>
</aside>
{% endzone %}
Once an admin assigns sections to the zone, the default content is replaced.
Zone Wrapper Options
Add HTML wrapper attributes to zones using options:
{% zone 'header' tag: 'header' class: 'site-header' %}{% endzone %}
{% zone 'footer' tag: 'footer' id: 'site-footer' %}{% endzone %}
{% zone 'sidebar' tag: 'aside' class: 'sidebar-area' %}{% endzone %}
| Option | Description |
|---|---|
tag | Wrapper HTML element. Allowed: div, header, footer, aside, nav, section, main, article |
class | CSS classes for the wrapper element |
id | HTML id attribute for the wrapper element |
When any wrapper option is provided, zone content is wrapped in that element. Without options, section HTML is output directly with no wrapper.
content_for_layout
The {% content_for_layout %} tag outputs sections that belong to the current page (as opposed to zone sections which belong to the layout).
Basic Usage
<main>
{% content_for_layout %}
</main>
This renders all page sections in order.
Filtering by Section Setting
You can split page sections across multiple layout columns by filtering on a section setting value:
<div class="grid-2col">
<div class="col-main">
{% content_for_layout column: 'main' %}
</div>
<div class="col-right">
{% content_for_layout column: 'right' %}
</div>
</div>
Each {% content_for_layout column: 'main' %} call renders only sections whose column setting equals 'main'. Sections without the specified setting are excluded.
For this to work, sections need a matching setting in their schema:
{
"settings": [
{
"type": "select",
"id": "column",
"label": "Column",
"options": [
{ "value": "main", "label": "Main Content" },
{ "value": "right", "label": "Right Sidebar" }
]
}
],
"presets": [
{
"name": "Default",
"settings": {
"column": "main"
}
}
]
}
You can filter by any setting key, not just column:
{% content_for_layout featured: true %}
Zones vs Page Sections
Understanding the difference between zone sections and page sections is essential:
| Aspect | Zone Sections | Page Sections |
|---|---|---|
| Defined by | {% zone %} tags in the layout | Added to individual pages by the admin |
| Scope | Shared across all pages using the layout | Specific to one page |
| Rendered by | {% zone 'name' %} | {% content_for_layout %} |
| Typical use | Navigation, footer, sidebars | Page-specific content (hero, articles, etc.) |
Complete Layout: Default
A standard layout with header and footer zones:
<!DOCTYPE html>
<html lang="{{ request.locale | default: 'en' }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ page.title | default: settings.site_name }}</title>
{% if page.meta_description %}
<meta name="description" content="{{ page.meta_description }}">
{% endif %}
{% theme_css %}
</head>
<body>
{% zone 'header' %}{% endzone %}
<main>
{% content_for_layout %}
</main>
{% zone 'footer' %}{% endzone %}
{% theme_javascript %}
</body>
</html>
Complete Layout: Sidebar
A layout that adds a sidebar zone alongside the main content:
<!DOCTYPE html>
<html lang="{{ request.locale | default: 'en' }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ page.title | default: settings.site_name }}</title>
{% theme_css %}
</head>
<body>
{% zone 'header' %}{% endzone %}
<div class="layout-with-sidebar">
<main class="layout-main">
{% content_for_layout %}
</main>
<aside class="layout-aside">
{% zone 'sidebar' %}{% endzone %}
</aside>
</div>
{% zone 'footer' %}{% endzone %}
{% theme_javascript %}
</body>
</html>
This layout has three zones (header, sidebar, footer), all auto-discovered by the admin.
Complete Layout: Multi-Column with Filtered Content
A newspaper-style layout that splits page sections across a 3-column grid using content_for_layout filtering:
<!DOCTYPE html>
<html lang="{{ request.locale | default: 'en' }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ page.title | default: settings.site_name }}</title>
{% theme_css %}
</head>
<body>
{% zone 'header' %}{% endzone %}
<main class="container">
<div class="grid-3col">
<div class="col col--left">
{% content_for_layout column: 'left' %}
</div>
<div class="col col--center">
{% content_for_layout column: 'center' %}
</div>
<div class="col col--right">
{% content_for_layout column: 'right' %}
</div>
</div>
</main>
{% zone 'footer' %}{% endzone %}
{% theme_javascript %}
</body>
</html>
Admins assign each section to a column via the section's column setting. The layout renders each column by filtering on the setting value.