Skip to content

Recipes

The pages on a storefront are built from the same small set of building blocks: tokens that print a value, loops that repeat markup, and conditionals that choose what to show. This guide collects complete, copy-ready patterns that combine them. Drop a recipe into a page template, a reusable block, or a system template and adjust the classes to match the design.

Each recipe is self-contained. The tokens it uses are only populated when the matching data is available, so wrap a section in a conditional when the data can be missing.

Brand header with logo fallback

Use the brand media keys to decide whether a logo is available, and fall back to the store name as text when it is not. This keeps the header correct before and after a logo is uploaded.

html
<header class="border-b border-[var(--border)] bg-white">
  <div class="mx-auto flex max-w-7xl items-center justify-between px-4 py-4 sm:px-6 lg:px-8">
    <a href="/" class="flex items-center gap-2">
      {if brand.media.desktop_light_logo || brand.media.desktop_dark_logo || brand.media.mobile_light_logo || brand.media.mobile_dark_logo || brand.media.square_logo}
        <img src="{core.project.logo}" alt="{brand.store_name}" class="h-8 w-auto">
      {else}
        <span class="text-lg font-semibold tracking-tight">{brand.store_name}</span>
      {/if}
    </a>
  </div>
</header>

Navigation links come from the storefront navigation settings. Loop over nav_links and read label, url, and the external flag to build a menu that keeps working as links are added or removed.

html
<nav aria-label="Main navigation" class="hidden items-center gap-6 md:flex">
  <ul class="flex items-center gap-6">
    {each nav_links as=link}
      <li>
        <a href="{link.url}"{if link.external} target="_blank" rel="noopener"{/if} class="text-sm font-medium text-[var(--text)] hover:text-[var(--primary)]">
          {link.label}
        </a>
      </li>
    {/each}
  </ul>
</nav>

Use limit to cap the number of links shown, for example {each nav_links as=link limit=5}. The footer area exposes the same loop as nav_links_footer.

Catalog list

The template catalog exposes its entries through the templates.items loop. Read template.name, template.description, template.preview_image_url, and template.link to build a list that stays current as entries are published.

html
<section class="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
  <h2 class="mb-6 text-2xl font-bold text-[var(--text)]">Templates</h2>
  <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
    {each templates.items as=template limit=6}
      <a href="{template.link}" class="group block rounded-xl border border-[var(--border)] bg-white p-3 transition hover:border-[var(--custom-border-hover)]">
        <img src="{template.preview_image_url}" alt="{template.name}" class="aspect-[4/3] w-full rounded-lg bg-[var(--custom-fadedbackground)] object-cover">
        <h3 class="mt-3 text-sm font-semibold text-[var(--text)]">{template.name}</h3>
        <p class="mt-1 text-sm text-[var(--custom-fadedtext)]">{template.description}</p>
        {if template.tags}
          <ul class="mt-2 flex flex-wrap gap-1">
            {each template.tags as=tag}
              <li class="rounded-full bg-[var(--custom-fadedbackground)] px-2 py-0.5 text-xs text-[var(--custom-fadedtext)]">{tag}</li>
            {/each template.tags}
          </ul>
        {/if}
      </a>
    {/each templates.items}
  </div>
</section>

Empty states

A collection token is empty when the collection has no items, so an {if} around the section can show a helpful message instead of an empty heading. Use the same pattern for any collection token.

html
{if templates.items}
  <section class="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
    <h2 class="mb-6 text-2xl font-bold text-[var(--text)]">Templates</h2>
    <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
      {each templates.items as=template limit=6}
        <a href="{template.link}" class="group block rounded-xl border border-[var(--border)] bg-white p-3">
          <img src="{template.preview_image_url}" alt="{template.name}" class="aspect-[4/3] w-full rounded-lg object-cover">
          <h3 class="mt-3 text-sm font-semibold text-[var(--text)]">{template.name}</h3>
          <p class="mt-1 text-sm text-[var(--custom-fadedtext)]">{template.description}</p>
        </a>
      {/each templates.items}
    </div>
  </section>
{else}
  <p class="mx-auto max-w-7xl px-4 py-12 text-sm text-[var(--custom-fadedtext)] sm:px-6 lg:px-8">
    No templates are available yet. Check back after more are published.
  </p>
{/if}

The loop itself renders nothing when the collection is empty, so pair it with an {if} guard whenever the collection can have no items. The same pattern applies to navigation, forms, and any extension collection.

Full page header

This recipe combines the brand fallback, the navigation loop, and an in-loop conditional into one header. It is a good starting point for a reusable header block.

html
<header class="border-b border-[var(--border)] bg-white">
  <div class="mx-auto flex max-w-7xl items-center justify-between gap-6 px-4 py-4 sm:px-6 lg:px-8">
    <a href="/" class="flex items-center gap-2">
      {if brand.media.desktop_light_logo || brand.media.desktop_dark_logo || brand.media.mobile_light_logo || brand.media.mobile_dark_logo || brand.media.square_logo}
        <img src="{core.project.logo}" alt="{brand.store_name}" class="h-8 w-auto">
      {else}
        <span class="text-lg font-semibold tracking-tight">{brand.store_name}</span>
      {/if}
    </a>

    <nav aria-label="Main navigation" class="hidden items-center gap-6 md:flex">
      <ul class="flex items-center gap-6">
        {each nav_links as=link}
          <li>
            <a href="{link.url}"{if link.external} target="_blank" rel="noopener"{/if} class="text-sm font-medium text-[var(--text)] hover:text-[var(--primary)]">
              {link.label}
            </a>
          </li>
        {/each}
      </ul>
    </nav>
  </div>
</header>

Extension recipes

Recipes for extension data live with each extension. See Commerce extension templating, Portfolio extension templating, and Blog extension templating for cart, checkout, product, subscription, portfolio, and blog patterns.

Next steps