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 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.id is unique for each placed instance. This makes it useful for IDs or anchor links.
  • richText fields expose a ready-to-render HTML companion, block.content_html. The block.content field itself is always true once the field exists. Test block.content_html instead, to check whether there is anything to render.
  • name is the block's identifier: lowercase and hyphenated. It is what you list in a template's allowed-blocks array.
  • label, singular, and plural are what editors see in the admin. If you omit them, they default to name. 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 blocks exist but no template lets editors place them. Open templates/page.liquid. Update its schema to list the block names in the blocks array:

{% 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 info

If 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.

On this page