Basker Docs

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 compose pages from. Each block is one Liquid file in blocks/ containing the markup plus a {% schema %} describing the fields editors fill in. Once the block exists and is listed on a template's allowed-blocks array, an editor can add it, position it, and configure its content.

You'll build three: a rich-text section, an image feature, and a feature grid. Together they cover most of the field types you'll use day-to-day.

Step 1 — A rich-text block

The simplest useful block: a styled prose section.

Create blocks/text-section.liquid:

<section id="text-section-{{ block.id }}" class="block-text-section s-prose">
  {{ block.content_html | raw }}
</section>

{% schema %}
{
  "name": "text-section",
  "label": "Text section",
  "singular": "Text section",
  "plural": "Text sections",
  "settings": [
    {
      "type": "richText",
      "name": "content",
      "label": "Content"
    }
  ]
}
{% endschema %}

Things worth pointing out:

  • block.id is unique per placed instance — useful for IDs or anchor links.
  • richText fields automatically expose a <setting>_html companion containing the rendered HTML. The raw value is in block.content, the rendered HTML is in block.content_html. Use | raw so Liquid doesn't escape it.
  • name is the block's identifier. Lowercase, hyphenated. It's what you'll list in template allowed-blocks arrays.
  • label, singular, and plural are what editors see in the admin. If you omit them, they default to name (and the theme checker warns).

Step 2 — An image feature

A block with 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",
      "default": 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)

A block where the editor adds an arbitrary number of items, each with its own fields. This is the array field type with a fields sub-array describing 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, though the validator emits a warning if you nest deeper than four levels.

Step 4 — Allow the blocks on your templates

Right now the blocks exist but no template lets editors place them. Open templates/page.liquid and update its schema to list the block names in the blocks array:

{% schema %}
{
  "settings": [],
  "blocks": ["text-section", "image-feature", "feature-grid"]
}
{% endschema %}

Do the same for templates/index.liquid if you want the homepage to support the same blocks. 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. You should see something like:

Theme check: 0 errors, 0 warnings, 0 info

If a block schema is missing a name, has invalid JSON, or a template references a block that doesn't exist, the checker tells you which file and line.

Step 6 — Add a block to a page in the admin

Open any page in your Basker admin and look for the blocks panel. Your three new blocks should appear in the picker. Add a "Text section", paste in some content, and save. 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 reach for snippets vs blocks.
  • Block schema — the full schema reference.
  • Field types — every field type available in settings.

On this page