Skip to main content

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:

TagPurpose
{% 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 %}
OptionDescription
tagWrapper HTML element. Allowed: div, header, footer, aside, nav, section, main, article
classCSS classes for the wrapper element
idHTML 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:

AspectZone SectionsPage Sections
Defined by{% zone %} tags in the layoutAdded to individual pages by the admin
ScopeShared across all pages using the layoutSpecific to one page
Rendered by{% zone 'name' %}{% content_for_layout %}
Typical useNavigation, footer, sidebarsPage-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.


Layout Inheritance

Layouts can extend other layouts using {% extends %}. This lets you define shared HTML boilerplate once in a base layout, then create variants that override specific zones.

Basic Inheritance

Parent layout (layouts/base.liquid):

<!DOCTYPE html>
<html>
<head>
{% theme_css %}
</head>
<body>
{% zone 'header' %}<header>Default Header</header>{% endzone %}

<main>
{% zone 'main' %}{{ content_for_layout }}{% endzone %}
</main>

{% zone 'footer' %}<footer>Default Footer</footer>{% endzone %}
{% theme_javascript %}
</body>
</html>

Child layout (layouts/landing.liquid):

{% extends 'base' %}

{% zone 'header' %}
<header class="landing-header">
<h1>Landing Page</h1>
</header>
{% endzone %}

{%- comment -%} main and footer zones are inherited from base {%- endcomment -%}
{% endextends %}

How It Works

  1. The child declares {% extends 'parent_name' %} at the top
  2. Inside the extends block, the child redefines any zones it wants to override
  3. The block closes with {% endextends %}
  4. Zones not redefined in the child inherit the parent's content
  5. The parent's full HTML structure (<html>, <head>, <body>) wraps everything

Using zone.super

Include the parent's original zone content within an override using {{ zone.super }}:

{% extends 'base' %}

{% zone 'header' %}
<div class="promo-banner">Limited Time Offer!</div>
{{ zone.super }}
{% endzone %}

{% endextends %}

This renders the promo banner above the parent's default header content, without replacing it.

Multi-Level Inheritance

Layouts can chain inheritance. For example, article.liquid extends default.liquid, which extends base.liquid:

layouts/base.liquid -- bare HTML skeleton:

<!DOCTYPE html>
<html>
<head>{% theme_css %}</head>
<body>
{% zone 'header' %}{% endzone %}
{% zone 'main' %}{{ content_for_layout }}{% endzone %}
{% zone 'footer' %}{% endzone %}
{% theme_javascript %}
</body>
</html>

layouts/default.liquid -- adds structured header zones:

{% extends 'base' %}

{% zone 'header' %}
{% zone 'topbar' %}{% endzone %}
{% zone 'masthead' %}{% endzone %}
{% zone 'navigation' %}{% endzone %}
{% endzone %}

{% endextends %}

layouts/article.liquid -- adds article-specific zones:

{% extends 'default' %}

{% zone 'main' %}
<article class="article-layout">
{% zone 'article_header' %}{% endzone %}
{{ content_for_layout }}
{% zone 'article_footer' %}{% endzone %}
</article>
{% endzone %}

{% endextends %}

Benefits of Inheritance

BenefitDescription
DRYHTML boilerplate and <head> setup defined once in base
MaintainabilityFix a bug in the base layout and it is fixed everywhere
VariantsCreate landing, article, or fullwidth layouts by overriding specific zones
ProgressiveAdd complexity progressively using zone.super

Common Inheritance Patterns

  • Landing page -- extends default, overrides the header zone with a minimal logo-only header
  • Article layout -- extends default, overrides the main zone to add author bio and related posts zones
  • Minimal layout -- extends base, overrides header with no content (empty zone)
  • Print layout -- extends base, removes all navigation zones

Creating a New Layout

  1. Create a new .liquid file in your theme's layouts/ directory
  2. Add the three required tags: {% theme_css %}, {% theme_javascript %}, and {% content_for_layout %}
  3. Add zones with {% zone 'name' %}{% endzone %} wherever you want configurable regions
  4. The admin will auto-discover the new layout and all its zones
{%- comment -%} layouts/fullscreen.liquid {%- endcomment -%}
<!DOCTYPE html>
<html>
<head>
{% theme_css %}
</head>
<body class="fullscreen-layout">
{% zone 'overlay_nav' %}{% endzone %}

<div class="fullscreen-content">
{% content_for_layout %}
</div>

{% theme_javascript %}
</body>
</html>

No additional configuration is needed. The admin editor will show "Fullscreen" as an available layout with an "Overlay Nav" zone.