Skip to main content

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:

  1. Liquid template -- the HTML markup
  2. Schema block -- settings definition (JSON)
  3. 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.

PropertyRequiredDescription
nameYesDisplay name in the section picker
categoryNoGroup in section picker (hero, content, cta, nav, footer, etc.)
tagNoHTML wrapper tag (default: section)
site_scopedNoIf true, section data is shared across all pages (see Site-Scoped Sections)
regionsNoLayout regions this section can be placed in (e.g., ["header"])
settingsYesArray of setting definitions
blocksNoNested block definitions
blocks_presentationNo"list" (flat) or "tree" (nested hierarchy)
presetsNoDefault configurations for initial values
Presets are the source of truth for defaults

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

SectionWhy Site-Scoped
header/mastheadSite title/logo is the same on every page
nav/mainNavigation links are shared across the site
footer/footer_01Footer 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

  1. You write CSS with Liquid inside {% style %}...{% endstyle %} in your section .liquid file
  2. OQO extracts the block, compiles it with the section's settings, and saves it as a separate CSS file
  3. 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

DoDo Not
CSS variables (--section-*)Animations or keyframes
Simple property rulesComplex hover effects
Settings-driven values onlyStatic styles that never change
Use [data-section-wrapper][data-section-id="{{ section.id }}"] selectorUse 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).

Do not use inline styles for settings-driven CSS

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 }}">&laquo; 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 &raquo;</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 value
  • neq -- 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

PropertyDescription
typeUnique identifier for the block type
nameDisplay name in admin UI
acceptArray of child block types this block can contain
rootIf false, block cannot be added at root level (must have a parent)
limitMaximum number of this block type per section
settingsArray 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>&copy; {{ 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>
AttributePurpose
gz-sectionCommon class for all sections
gz-{category}-{name}Section-type class for CSS targeting
data-section-wrapperMarker for the editor and CSS scoping
data-section-idUnique instance ID for this section
data-section-typeSection template path

Parameters

ParameterDescription
First argumentHTML element: 'div', 'section', 'nav', 'header', 'footer', etc.
classCSS classes to add
idHTML id attribute
Any data-*Custom data attributes

Best Practices

  1. Always use section_wrapper -- it provides the data attributes needed for the editor, live preview, and CSS scoping.

  2. Use {% style %} blocks for settings-driven CSS -- they enable instant hot-reload of color and spacing changes.

  3. Keep {% style %} blocks lightweight -- only CSS variables and simple property rules. Put animations and complex styles in your theme stylesheet.

  4. Provide presets -- sections should look good out of the box. Define sensible initial values in presets.

  5. Group settings with headers -- use "type": "header" to organize related settings visually.

  6. Use show_if for conditional settings -- hide settings that are irrelevant based on other selections.

  7. Use color_alpha for section colors -- always with the custom_colors checkbox pattern and color_alpha_to_rgba filter.

  8. Use semantic CSS variable names -- prefix section variables with --section-* to distinguish from --theme-* global variables.