Sections
Sections are the configurable building blocks of pages. Each section is a self-contained component with its own template, settings, and optional CSS.
Anatomy of a Section
Every section file has three parts:
- Liquid template -- the HTML markup
- Schema block -- settings definition (JSON)
- Style block -- per-section CSS variables (optional)
{%- comment -%} 1. Liquid Template {%- endcomment -%}
{% section_wrapper 'section' class: 'hero' %}
<div class="hero-content">
{% htmltag 'h1' section.settings.title class: 'hero-title' %}
{% htmltag 'p' section.settings.subtitle class: 'hero-subtitle' %}
{% if section.settings.button.href != blank %}
<a href="{{ section.settings.button | link_url }}"
{% if section.settings.button | link_new_tab? %}target="_blank" rel="noopener"{% endif %}
class="btn">
{{ section.settings.button | link_text }}
</a>
{% endif %}
</div>
{% endsection_wrapper %}
{%- comment -%} 2. Style Block (per-section CSS) {%- endcomment -%}
{% style %}
[data-section-wrapper][data-section-id="{{ section.id }}"] {
{% if section.settings.custom_colors and section.settings.bgcolor != blank %}
--section-bgcolor: {{ section.settings.bgcolor | color_alpha_to_rgba }};
{% else %}
--section-bgcolor: var(--theme-background-color);
{% endif %}
background-color: var(--section-bgcolor);
}
{% endstyle %}
{%- comment -%} 3. Schema Block {%- endcomment -%}
{% schema %}
{
"name": "Hero Banner",
"category": "hero",
"settings": [
{
"type": "text",
"id": "title",
"label": "Title"
},
{
"type": "text",
"id": "subtitle",
"label": "Subtitle",
"nb_rows": 2
},
{
"type": "checkbox",
"id": "custom_colors",
"label": "Custom Colors",
"info": "Override theme colors for this section"
},
{
"type": "color_alpha",
"id": "bgcolor",
"label": "Background Color",
"show_if": { "setting": "custom_colors", "eq": true }
},
{
"type": "link",
"id": "button",
"label": "Button",
"with_text": true
}
],
"presets": [
{
"name": "Default",
"settings": {
"title": "Welcome",
"subtitle": "Your story starts here"
}
}
]
}
{% endschema %}
File Organization
Group sections by category inside the sections/ directory:
sections/
├── hero/
│ ├── hero_01.liquid # Full-width hero with image
│ ├── hero_02.liquid # Split hero (text + image)
│ └── hero_03.liquid # Video background hero
├── content/
│ ├── post.liquid # Single post display
│ ├── posts.liquid # Post grid/list
│ └── text_block.liquid # Rich text content
├── cta/
│ ├── cta_01.liquid # Simple CTA
│ └── cta_02.liquid # CTA with image
├── nav/
│ └── main.liquid # Navigation bar
├── header/
│ └── masthead.liquid # Site header/masthead
└── footer/
└── footer_01.liquid # Site footer
Schema Properties
The {% schema %} block defines how your section appears in the editor and what settings are available.
| Property | Required | Description |
|---|---|---|
name | Yes | Display name in the section picker |
category | No | Group in section picker (hero, content, cta, nav, footer, etc.) |
tag | No | HTML wrapper tag (default: section) |
site_scoped | No | If true, section data is shared across all pages (see Site-Scoped Sections) |
regions | No | Layout regions this section can be placed in (e.g., ["header"]) |
settings | Yes | Array of setting definitions |
blocks | No | Nested block definitions |
blocks_presentation | No | "list" (flat) or "tree" (nested hierarchy) |
presets | No | Default configurations for initial values |
Section settings do NOT have a default field in the schema. All initial values are defined in presets. When a section is added to a page, the preset values are used.
Page-Scoped vs Site-Scoped Sections
OQO supports two scoping models for sections.
Page-Scoped Sections (Default)
Each page gets its own copy of the section data. Editing the section on one page does not affect other pages.
This is the default behavior -- no special configuration needed.
{% schema %}
{
"name": "Featured Posts",
"category": "content",
"settings": [
{ "type": "text", "id": "heading", "label": "Heading" }
]
}
{% endschema %}
Site-Scoped Sections
Site-scoped sections share their settings across all pages. Editing the section on any page updates it everywhere. This is ideal for navigation bars, site headers, and footers that should be consistent across the entire site.
To make a section site-scoped, add "site_scoped": true to the schema:
{% schema %}
{
"name": "Main Navigation",
"site_scoped": true,
"regions": ["header"],
"settings": [
{ "type": "image", "id": "logo", "label": "Logo" }
],
"blocks": [
{
"type": "link_item",
"name": "Menu Link",
"settings": [
{ "type": "link", "id": "link", "label": "Link" }
]
}
],
"blocks_presentation": "tree"
}
{% endschema %}
Common Site-Scoped Sections
| Section | Why Site-Scoped |
|---|---|
header/masthead | Site title/logo is the same on every page |
nav/main | Navigation links are shared across the site |
footer/footer_01 | Footer content is identical everywhere |
Section CSS with {% style %} Blocks
The {% style %} block lets you define per-section CSS that updates instantly via hot-reload when settings change in the editor. This is the recommended approach for any CSS that depends on section settings.
How It Works
- You write CSS with Liquid inside
{% style %}...{% endstyle %}in your section.liquidfile - OQO extracts the block, compiles it with the section's settings, and saves it as a separate CSS file
- When a user changes a color setting in the editor, only the CSS file is regenerated -- no full page reload needed
Basic Example
{% style %}
[data-section-wrapper][data-section-id="{{ section.id }}"] {
--section-bgcolor: {{ section.settings.bgcolor | default: '#1F2937' }};
--section-text-color: {{ section.settings.text_color | default: '#FFFFFF' }};
background-color: var(--section-bgcolor);
color: var(--section-text-color);
}
{% endstyle %}
The custom_colors Pattern
For sections that support optional color overrides, follow this standardized pattern. A custom_colors checkbox gates the color settings, and each CSS variable checks both the checkbox and the individual setting:
{% style %}
[data-section-wrapper][data-section-id="{{ section.id }}"] {
{% if section.settings.custom_colors and section.settings.bgcolor != blank %}
--section-bgcolor: {{ section.settings.bgcolor | color_alpha_to_rgba }};
{% else %}
--section-bgcolor: var(--theme-background-color);
{% endif %}
{% if section.settings.custom_colors and section.settings.heading_color != blank %}
--section-heading: {{ section.settings.heading_color | color_alpha_to_rgba }};
{% else %}
--section-heading: var(--theme-heading-color);
{% endif %}
{% if section.settings.custom_colors and section.settings.text_color != blank %}
--section-text: {{ section.settings.text_color | color_alpha_to_rgba }};
{% else %}
--section-text: var(--theme-text-color);
{% endif %}
background-color: var(--section-bgcolor);
h2 { color: var(--section-heading); }
p { color: var(--section-text); }
}
{% endstyle %}
The corresponding schema settings:
{
"type": "header",
"content": "Colors"
},
{
"type": "checkbox",
"id": "custom_colors",
"label": "Custom Colors",
"info": "Override theme colors for this section"
},
{
"type": "color_alpha",
"id": "bgcolor",
"label": "Background Color",
"show_if": { "setting": "custom_colors", "eq": true }
},
{
"type": "color_alpha",
"id": "heading_color",
"label": "Heading Color",
"show_if": { "setting": "custom_colors", "eq": true }
},
{
"type": "color_alpha",
"id": "text_color",
"label": "Text Color",
"show_if": { "setting": "custom_colors", "eq": true }
}
Background Image in Style Block
{% style %}
[data-section-wrapper][data-section-id="{{ section.id }}"] {
--section-bg-image: url('{{ section.settings.background_image | image_url: 'hero' }}');
--section-overlay: {{ section.settings.overlay_color | color_alpha_to_rgba }};
}
{% endstyle %}
Rules for {% style %} Blocks
| Do | Do Not |
|---|---|
CSS variables (--section-*) | Animations or keyframes |
| Simple property rules | Complex hover effects |
| Settings-driven values only | Static styles that never change |
Use [data-section-wrapper][data-section-id="{{ section.id }}"] selector | Use class-based selectors |
Why? Style blocks are re-rendered on every settings change in the editor. Keep them lightweight. Put animations, transitions, and complex component styles in your theme's _sections.css stylesheet instead, scoped under the section's type class (e.g., .gz-hero-hero_01).
Avoid {% capture section_style %} and inline style: attributes for settings. Inline styles cannot be hot-reloaded -- they require a full HTML re-render. Use {% style %} blocks instead.
Section with Posts
Use the global collections accessor to display posts in a section:
{% section_wrapper 'section' class: 'recent-posts' %}
{% htmltag 'h2' section.settings.title class: 'section-title' %}
<div class="posts-grid">
{%- assign collection_slug = section.settings.collection -%}
{% for post in collections[collection_slug] limit: section.settings.count %}
<article class="post-card">
{% if post.image %}
<img src="{{ post.image | image_url: 'medium' }}"
alt="{{ post.image.alt | default: post.title }}">
{% endif %}
<h3><a href="{{ post.url }}">{{ post.title }}</a></h3>
<p>{{ post.excerpt }}</p>
<span>{{ post.published_at | date_format: 'medium' }}</span>
</article>
{% endfor %}
</div>
{% endsection_wrapper %}
{% schema %}
{
"name": "Post Grid",
"category": "content",
"settings": [
{
"type": "text",
"id": "title",
"label": "Section Title"
},
{
"type": "collection",
"id": "collection",
"label": "Collection",
"placeholder": "Select collection...",
"url_mode_available": true
},
{
"type": "number",
"id": "count",
"label": "Number of Posts"
}
],
"presets": [
{
"name": "Default",
"settings": {
"title": "Latest Posts",
"count": 6
}
}
]
}
{% endschema %}
All Posts Shortcut
To show recent posts from across the site without a specific collection:
{% for post in collections.all_posts limit: section.settings.count %}
<article>
<h3><a href="{{ post.url }}">{{ post.title }}</a></h3>
<p>{{ post.excerpt }}</p>
</article>
{% endfor %}
With Pagination
{% paginate collections[collection_slug] by section.settings.count %}
{% for post in paginate.collection %}
<article>{{ post.title }}</article>
{% endfor %}
<nav class="pagination">
{% if paginate.previous %}
<a href="{{ paginate.previous.url }}">« Previous</a>
{% endif %}
{% for part in paginate.parts %}
{% if part.is_link %}
<a href="{{ part.url }}">{{ part.title }}</a>
{% else %}
<span class="current">{{ part.title }}</span>
{% endif %}
{% endfor %}
{% if paginate.next %}
<a href="{{ paginate.next.url }}">Next »</a>
{% endif %}
</nav>
{% endpaginate %}
Complete Schema Examples
All Setting Types
Here is a comprehensive schema demonstrating every common setting type:
{% schema %}
{
"name": "Full Example Section",
"category": "content",
"settings": [
{
"type": "header",
"content": "Content"
},
{
"type": "text",
"id": "title",
"label": "Title"
},
{
"type": "text",
"id": "body",
"label": "Body Text",
"nb_rows": 3
},
{
"type": "link",
"id": "button",
"label": "Button",
"with_text": true
},
{
"type": "image",
"id": "background_image",
"label": "Background Image"
},
{
"type": "icon",
"id": "section_icon",
"label": "Icon"
},
{
"type": "header",
"content": "Layout"
},
{
"type": "select",
"id": "columns",
"label": "Columns",
"options": [
{ "value": "2", "label": "2 Columns" },
{ "value": "3", "label": "3 Columns" },
{ "value": "4", "label": "4 Columns" }
]
},
{
"type": "number",
"id": "count",
"label": "Number of Items"
},
{
"type": "range",
"id": "spacing",
"label": "Section Spacing",
"min": 0,
"max": 200,
"step": 10,
"unit": "px"
},
{
"type": "checkbox",
"id": "show_date",
"label": "Show Date"
},
{
"type": "checkbox",
"id": "edge_to_edge",
"label": "Edge to Edge",
"info": "Extend section to viewport edges"
},
{
"type": "header",
"content": "Data Source"
},
{
"type": "collection",
"id": "source_collection",
"label": "Collection",
"placeholder": "Select collection...",
"url_mode_available": true
},
{
"type": "post",
"id": "featured_post",
"label": "Featured Post",
"placeholder": "Select a post..."
},
{
"type": "content",
"id": "source",
"label": "Content Source",
"url_mode_available": true
},
{
"type": "header",
"content": "Colors"
},
{
"type": "checkbox",
"id": "custom_colors",
"label": "Custom Colors",
"info": "Override theme colors for this section"
},
{
"type": "color_alpha",
"id": "bgcolor",
"label": "Background Color",
"show_if": { "setting": "custom_colors", "eq": true }
},
{
"type": "color_alpha",
"id": "heading_color",
"label": "Heading Color",
"show_if": { "setting": "custom_colors", "eq": true }
},
{
"type": "color_alpha",
"id": "text_color",
"label": "Text Color",
"show_if": { "setting": "custom_colors", "eq": true }
},
{
"type": "info",
"content": "Colors above only take effect when 'Custom Colors' is enabled"
}
],
"presets": [
{
"name": "Default",
"settings": {
"title": "Section Title",
"columns": "3",
"count": 6,
"spacing": 60,
"show_date": true,
"edge_to_edge": false,
"custom_colors": false
}
}
]
}
{% endschema %}
Conditional Settings with show_if
Use show_if to show or hide settings based on other setting values:
{
"type": "checkbox",
"id": "use_url_collection",
"label": "Use Collection from URL"
},
{
"type": "collection",
"id": "collection",
"label": "Collection",
"show_if": { "setting": "use_url_collection", "eq": false }
}
The show_if option supports:
eq-- show when the referenced setting equals this valueneq-- show when the referenced setting does NOT equal this value
{
"type": "select",
"id": "layout",
"label": "Layout",
"options": [
{ "value": "grid", "label": "Grid" },
{ "value": "list", "label": "List" }
]
},
{
"type": "range",
"id": "grid_columns",
"label": "Grid Columns",
"min": 2, "max": 4, "step": 1,
"show_if": { "setting": "layout", "neq": "list" }
}
Blocks
Blocks are repeatable content items within a section -- navigation links, feature cards, testimonials, FAQ items, etc.
Flat Blocks
For simple repeatable content, use flat blocks with "blocks_presentation": "list" (or omit it, as list is the default):
{% section_wrapper 'section' class: 'features' %}
<div class="features-grid">
{% for block in section.blocks %}
<div class="feature-card">
<i class="{{ block.settings.icon }}"></i>
<h3>{{ block.settings.title }}</h3>
<p>{{ block.settings.description }}</p>
</div>
{% endfor %}
</div>
{% endsection_wrapper %}
{% schema %}
{
"name": "Features",
"category": "content",
"settings": [
{ "type": "text", "id": "heading", "label": "Section Heading" }
],
"blocks": [
{
"type": "feature",
"name": "Feature",
"settings": [
{ "type": "icon", "id": "icon", "label": "Icon" },
{ "type": "text", "id": "title", "label": "Title" },
{ "type": "text", "id": "description", "label": "Description", "nb_rows": 2 }
]
}
]
}
{% endschema %}
Block Type Filtering
Access blocks of a specific type directly:
{%- comment -%} Only "feature" type blocks {%- endcomment -%}
{% for block in section.blocks.feature %}
<div class="feature-card">{{ block.settings.title }}</div>
{% endfor %}
{%- comment -%} Only "testimonial" type blocks {%- endcomment -%}
{% for block in section.blocks.testimonial %}
<blockquote>{{ block.settings.quote }}</blockquote>
{% endfor %}
Nested Blocks (Tree)
For hierarchical structures like navigation menus, use "blocks_presentation": "tree":
{% section_wrapper 'nav' class: 'main-nav' %}
{% for link_group in section.blocks.link_group %}
<nav>
{% for link in link_group.blocks %}
<a href="{{ link.settings.link | link_url }}">
{{ link.settings.link | link_text }}
</a>
{%- comment -%} Render children if any {%- endcomment -%}
{% if link.blocks.size > 0 %}
<ul class="submenu">
{% for sublink in link.blocks %}
<li>
<a href="{{ sublink.settings.link | link_url }}">
{{ sublink.settings.link | link_text }}
</a>
</li>
{% endfor %}
</ul>
{% endif %}
{% endfor %}
</nav>
{% endfor %}
{% endsection_wrapper %}
{% schema %}
{
"name": "Navigation",
"site_scoped": true,
"regions": ["header"],
"blocks": [
{
"type": "link_group",
"name": "Link Group",
"accept": ["link_item"],
"settings": []
},
{
"type": "link_item",
"name": "Link",
"root": false,
"settings": [
{ "type": "link", "id": "link", "label": "Link" }
]
}
],
"blocks_presentation": "tree"
}
{% endschema %}
Block Schema Properties
| Property | Description |
|---|---|
type | Unique identifier for the block type |
name | Display name in admin UI |
accept | Array of child block types this block can contain |
root | If false, block cannot be added at root level (must have a parent) |
limit | Maximum number of this block type per section |
settings | Array of settings for the block |
Section Wrapper
Always use {% section_wrapper %} to wrap your section content. It adds the data attributes needed for the editor and CSS scoping.
{%- comment -%} Default: renders a <div> {%- endcomment -%}
{% section_wrapper class: 'my-section' %}
<h2>Content</h2>
{% endsection_wrapper %}
{%- comment -%} Specify element type {%- endcomment -%}
{% section_wrapper 'nav' class: 'main-nav' %}
<a href="/">Home</a>
{% endsection_wrapper %}
{%- comment -%} Footer {%- endcomment -%}
{% section_wrapper 'footer' class: 'site-footer' %}
<p>© {{ site.name }}</p>
{% endsection_wrapper %}
Output
<div class="gz-section gz-content-my_section my-section"
data-section-wrapper="true"
data-section-id="S8vQP-jI"
data-section-type="content/my_section">
<h2>Content</h2>
</div>
| Attribute | Purpose |
|---|---|
gz-section | Common class for all sections |
gz-{category}-{name} | Section-type class for CSS targeting |
data-section-wrapper | Marker for the editor and CSS scoping |
data-section-id | Unique instance ID for this section |
data-section-type | Section template path |
Parameters
| Parameter | Description |
|---|---|
| First argument | HTML element: 'div', 'section', 'nav', 'header', 'footer', etc. |
class | CSS classes to add |
id | HTML id attribute |
Any data-* | Custom data attributes |
Best Practices
-
Always use
section_wrapper-- it provides the data attributes needed for the editor, live preview, and CSS scoping. -
Use
{% style %}blocks for settings-driven CSS -- they enable instant hot-reload of color and spacing changes. -
Keep
{% style %}blocks lightweight -- only CSS variables and simple property rules. Put animations and complex styles in your theme stylesheet. -
Provide presets -- sections should look good out of the box. Define sensible initial values in
presets. -
Group settings with headers -- use
"type": "header"to organize related settings visually. -
Use
show_iffor conditional settings -- hide settings that are irrelevant based on other selections. -
Use
color_alphafor section colors -- always with thecustom_colorscheckbox pattern andcolor_alpha_to_rgbafilter. -
Use semantic CSS variable names -- prefix section variables with
--section-*to distinguish from--theme-*global variables.
Related Documentation
- Settings System -- Complete reference for all setting types
- Liquid Tags -- Tags like
{% section_wrapper %},{% paginate %}, and{% render %} - Liquid Objects -- Objects available in templates (
section,post,collection, etc.) - CSS Architecture -- Theme CSS system and compilation
- Live Preview -- How the editor preview works