Skip to content

Blocks overview

Signal’s product page has no hard-coded buy column. Every line of it — the type and SKU, the title, the price, the size run, the add-to-bag button, the shipping promises, the accordions — is a theme block that you add, remove and drag into whatever order your catalogue wants.

That’s the whole model. The Product section contains exactly one instruction where the buy column should be:

<div class="pdp-buy">
{% content_for 'blocks' %}
</div>

Everything else is yours to arrange.

A spec-led page for a hard-shell jacket and a size-led page for a tee want different things first. With a fixed buy column you get one order for the whole catalogue. With blocks you lead with whatever each product actually needs — move the spec list above the button on technical gear, drop the size run entirely on a one-size cap, put the run counter directly under the price on a drop that’s nearly gone.

It also means a product page can be changed without a developer. Nothing in the list below needs a code edit to reorder.

The Product section is the one section in Signal that accepts theme blocks. Its schema declares:

"blocks": [{ "type": "@theme" }, { "type": "@app" }]

@theme means any block in the theme, and @app means any block an installed app provides — so app blocks (reviews, size recommenders, upsell widgets) drop into the buy column alongside Signal’s own blocks and reorder with them.

The one other place blocks nest is inside the Group block, which can hold blocks of its own. A group can hold any theme block, including another group.

  1. In the theme editor, open a product page and click the buy column in the preview.
  2. The left panel lists the blocks currently in the column. Drag any row to reorder it.
  3. Add block offers every theme block plus anything your apps provide.
  4. Click a block to open its settings. The eye icon hides a block without deleting it; the bin removes it.

Changes apply to the template, so they affect every product using that template. To give one product a different arrangement, create a second product template and assign it to that product.

Blocks don’t receive the product as a parameter. They read it from closest.product:

{%- liquid
assign p = closest.product
if request.visual_preview_mode and p == blank
assign p = collections.all.products.first
endif
-%}

closest.product resolves to whatever product the block is being rendered inside — so the same block file works on a product page, inside a group, and anywhere else a product is in scope, with no wiring. The visual_preview_mode fallback exists so the editor’s automatic block thumbnails have something to draw; it never affects a live storefront.

Two consequences worth knowing:

  • A block can’t read section.settings. This is why the size-guide controls live on the Variant picker block rather than on the Product section, and why the run size lives on the Stock counter block.
  • A block that finds no product renders nothing. Most blocks check for a product first, so they fail quietly rather than printing an empty shell.

Three blocks are never placed by hand. Buy buttons renders them itself, at fixed positions inside the product form:

{% content_for 'block', type: 'quantity', id: 'quantity' %}
{% content_for 'block', type: 'add-to-cart', id: 'add-to-cart', atc_label: block.settings.atc_label %}
{% content_for 'block', type: 'accelerated-checkout', id: 'accelerated-checkout', form_obj: form %}

They’re separate files so each is individually addressable and styleable, but they have to be inside that one <form> element for the browser to submit them with the order. Rendering them statically is how both things stay true at once.

Shopify wraps every theme block in its own <div class="shopify-block shopify-block--…">. You never write that div; it’s added around your block’s output so the editor can attach drag handles and highlighting.

It changes how CSS has to be written. Sibling selectors like :first-of-type match each block inside its own wrapper, so they’re always true. Signal’s spacing rules therefore target the wrappers:

.shopify-block:has(> .pdp-perk) + .shopify-block:has(> .pdp-perk) > .pdp-perk {
margin-top: 0;
border-top: 0;
}

That’s what makes a run of Perk rows read as one list rather than three separate boxes — and what lets a merchant split them into two groups simply by dropping another block between them. The same pattern joins consecutive accordion rows.

The Product section ships with this order, which suits most apparel and gear:

  1. Product meta
  2. Product title
  3. Price
  4. Product description (short)
  5. Variant picker
  6. Buy buttons
  7. Back in stock
  8. Stock counter
  9. Pickup availability
  10. Perk — shipping
  11. Perk — returns
  12. Perk — no restock
  13. Share
  14. Details accordion
  15. Spec list

Treat it as a starting point, not a recommendation to keep. The arrangement is the part of the product page you should be tuning.