Build reusable blocks
Three composable blocks an editor can drop into any page: a rich-text section, an image feature, and a feature grid
Blocks are the units editors use to compose pages. Each block is one Liquid file in blocks/. It contains the markup plus a {% schema %} tag that describes the fields editors complete. Once the block exists and appears in a template's allowed-blocks array, an editor can add it to a page. The editor can then position it and set its content.
You will build three blocks: a rich-text section, an image feature, and a feature grid. Together they cover most of the field types you use every day.
Step 1: A rich-text block
The simplest useful block is a styled prose section.
Create blocks/text-section.liquid:
<section id="text-section-{{ block.id }}" class="block-text-section s-prose">
{{ block.content_html }}
</section>
{% schema %}
{
"name": "text-section",
"label": "Text section",
"singular": "Text section",
"plural": "Text sections",
"settings": [
{
"type": "richText",
"name": "content",
"label": "Content"
}
]
}
{% endschema %}A few notes:
block.idis unique for each placed instance. This makes it useful for IDs or anchor links.richTextfields expose a ready-to-render HTML companion,block.content_html. Theblock.contentfield itself is alwaystrueonce the field exists. Testblock.content_htmlinstead, to check whether there is anything to render.nameis the block's identifier: lowercase and hyphenated. It is what you list in a template's allowed-blocks array.label,singular, andpluralare what editors see in the admin. If you omit them, they default toname. The upload validator also warns you about this when you upload the theme.
Step 2: An image feature
This block uses three field types: an image upload, a text caption, and a checkbox toggle.
Create blocks/image-feature.liquid:
<section id="image-feature-{{ block.id }}"
class="block-image-feature {% if block.fullWidth %}block-image-feature--full{% endif %}">
{% if block.image %}
<img src="{{ block.image.url }}" alt="{{ block.image.alt | default: block.caption }}">
{% endif %}
{% if block.caption %}
<figcaption>{{ block.caption }}</figcaption>
{% endif %}
</section>
{% schema %}
{
"name": "image-feature",
"label": "Image feature",
"singular": "Image feature",
"plural": "Image features",
"settings": [
{
"type": "upload",
"name": "image",
"label": "Image",
"relationTo": "media"
},
{
"type": "text",
"name": "caption",
"label": "Caption"
},
{
"type": "checkbox",
"name": "fullWidth",
"label": "Full-bleed",
"defaultValue": false
}
]
}
{% endschema %}upload fields with "relationTo": "media" give editors the standard media-library picker. The selected file's url, alt, width, and height are all available on block.image.
Step 3: A feature grid (array with nested fields)
This block lets the editor add any number of items, each with its own fields. This is the array field type with a fields sub-array that describes the per-item schema.
Create blocks/feature-grid.liquid:
<section id="feature-grid-{{ block.id }}" class="block-feature-grid">
{% if block.title %}
<h2 class="block-feature-grid__title">{{ block.title }}</h2>
{% endif %}
<ul class="block-feature-grid__items">
{% for item in block.items %}
<li class="feature">
{% if item.icon %}
<img class="feature__icon" src="{{ item.icon.url }}" alt="">
{% endif %}
<h3 class="feature__title">{{ item.title }}</h3>
<p class="feature__description">{{ item.description }}</p>
</li>
{% endfor %}
</ul>
</section>
{% schema %}
{
"name": "feature-grid",
"label": "Feature grid",
"singular": "Feature grid",
"plural": "Feature grids",
"settings": [
{
"type": "text",
"name": "title",
"label": "Section title"
},
{
"type": "array",
"name": "items",
"label": "Features",
"fields": [
{
"type": "upload",
"name": "icon",
"label": "Icon",
"relationTo": "media"
},
{
"type": "text",
"name": "title",
"label": "Title"
},
{
"type": "text",
"name": "description",
"label": "Description"
}
]
}
]
}
{% endschema %}Inside the {% for item in block.items %} loop, each item is a drop with the fields defined under fields. Group and array nesting work the same way recursively. The validator emits a warning if you nest deeper than four levels.
Step 4: Allow the blocks on your templates
Right now the template's blocks array is empty. An empty list does not restrict anything, so editors can already add every block in the theme. List the blocks you intend for this template instead. Open templates/page.liquid and update its schema:
{% schema %}
{
"settings": [],
"blocks": ["text-section", "image-feature", "feature-grid"]
}
{% endschema %}If you want the homepage to support the same blocks, do the same for templates/page.homepage.liquid. Each entry must match a block file's name exactly. Typos produce a warning during validation.
Step 5: Run the local theme checker
Press c in the dev-server terminal to run the checker against your local files. If you added a schema to templates/page.homepage.liquid in Step 4, you should see something like:
Theme check: 0 errors, 0 warnings, 0 infoIf you skipped that optional edit, page.homepage.liquid is still schema-less and the checker reports one warning instead:
Theme check: 0 errors, 1 warnings, 0 info
- [warning] templates/page.homepage.liquid: Template is missing a schema tag. (template-missing-schema)The checker reports the file and line for three kinds of problems. A block schema might have no name. A block schema might have invalid JSON. A template might reference a block that does not exist.
Step 6: Add a block to a page in the admin
Open any page in your Basker admin. Look for the blocks panel. Your three new blocks should appear in the picker. Add a "Text section" block. Paste some content into it. Save the page. Switch back to the dev-server browser tab. The block renders below the page body, in the spot reserved by {% stageblocks page %}.
What's next
Variants and theme settings: turn the homepage into a richer variant template and expose theme-wide options through the admin.
Going deeper
- Blocks: block lifecycle, drop semantics, when to use snippets instead of blocks.
- Block schema: the full schema reference.
- Field types: every field type available in
settings.