Bagian 3 (21 Gambaran Arsitektur Theme sampai 35 Developer Preview tag {% block %} dan {% partial %}) ngasih kita kerangka theme. Mulai bab ini kita isi kerangkanya dengan fitur storefront yang dipakai pembeli tiap hari. Yang pertama dan paling dasar: navigasi.

Pembeli harus bisa nemuin konten toko dengan mudah. Shopify nyediain tiga cara di theme: navigasi (menu yang dibikin merchant, tampil di header dan footer), pencarian (buat yang udah tau mau cari apa), dan filtering (nyempitin koleksi dan hasil pencarian). Bab ini navigasi, dua lainnya di 37 Filtering dan 38 Search dan Predictive Search.

Dari Mana Menu Datang

Merchant bikin menu di admin: Online Store > Navigation. Tiap menu bisa bersarang buat bikin dropdown, maksimal tiga tingkat. Theme gak bikin menu, theme cuma nampilin menu yang udah dibikin merchant. Ini pemisahan yang penting: struktur navigasi itu urusan merchant, tampilannya urusan kamu.

Tiap toko baru punya dua menu bawaan: Main menu (handle main-menu) dan Footer menu (handle footer).

Tiga Object yang Dipakai

Udah dibahas propertinya di 15 Objects Konten > linklist dan link, ini peran masing-masing:

  1. linklists, object global berisi semua menu toko, diakses pakai handle: linklists.main-menu atau linklists['footer'].
  2. linklist, satu menu, punya title, handle, links, levels.
  3. link, satu item di menu, punya title, url, links (anak-anaknya), object (sumber daya tujuannya), type, dan empat penanda aktif current, active, child_current, child_active.

Cara Ngakses Menu

1. Handle Langsung

{% for link in linklists.main-menu.links %}
<!-- menu content -->
{% endfor %}

Simpel, tapi hardcode: merchant gak bisa ganti menu mana yang tampil di header tanpa ngedit kode.

Kasih setting bertipe link_list dari 30 Settings dan Input Settings > 9. link_list biar merchant milih sendiri. Nilai setting-nya langsung object linklist, jadi cara pakainya sama:

{% for link in section.settings.menu.links %}
<!-- menu content -->
{% endfor %}

Schema-nya:

{
  "type": "link_list",
  "id": "menu",
  "label": "Menu",
  "default": "main-menu"
}

❌ hardcode, merchant harus ngedit kode buat ganti menu

{% for link in linklists.main-menu.links %}

✅ merchant milih di theme editor

{% for link in section.settings.menu.links %}

Ingat, link_list itu satu-satunya setting sumber daya yang nerima default, tapi cuma main-menu dan footer.

Buat tiap link, kamu output judul dan URL, dan kalau punya anak, output anak-anaknya lewat link.links. Karena maksimal tiga tingkat, kamu bisa nulis tiga loop bersarang secara eksplisit. Contoh section header dengan setting menu:

<ul class="menu">
  {% for link in section.settings.menu.links %}
    <li class="menu-link">
      <a href="{{ link.url }}">{{ link.title }}</a>
      {% if link.links.size > 0 %}
        <ul class="menu dropdown-child">
          {% for child_link in link.links %}
            <li class="menu-link">
              <a href="{{ child_link.url }}">{{ child_link.title }}</a>
              {% if child_link.links.size > 0 %}
                <ul class="menu dropdown-grandchild">
                  {% for grandchild_link in child_link.links %}
                    <li class="menu-link">
                      <a href="{{ grandchild_link.url }}">{{ grandchild_link.title }}</a>
                    </li>
                  {% endfor %}
                </ul>
              {% endif %}
            </li>
          {% endfor %}
        </ul>
      {% endif %}
    </li>
  {% endfor %}
</ul>

Ini contoh buat ngilustrasiin iterasi, bukan fitur navigasi lengkap. Tapi polanya udah bener: cek link.links.size > 0 sebelum bikin <ul> anak, biar gak ada list kosong. Kode navigasi ditaruh di section header atau footer di section group dari 27 Section Groups dan Migrasinya. Implementasi lengkapnya bisa dilihat di header Dawn.

Kalau tiga loop bersarang kerasa berulang, kamu bisa bikin snippet rekursif yang manggil dirinya sendiri lewat render selama link.links.size > 0, tapi hati-hati: render gak bisa akses variabel luar, jadi semua data harus dikirim eksplisit, dan kedalamannya tetap dibatasi Shopify di tiga.

Pembeli perlu tau mereka lagi di mana. Object link nyediain empat boolean:

  1. current, URL sekarang sama persis dengan URL link (parameter URL diabaikan, /collections/x/products/y dianggap sama dengan /products/y).
  2. active, URL sekarang sama atau mengandung URL link. Buka artikel di blog “Potion notions”, link ke /blogs/potion-notions itu active.
  3. child_current, ada anak yang current.
  4. child_active, ada anak yang active.

Pakainya: aria-current="page" buat yang current (ini yang dibaca screen reader), class visual buat yang active atau child_active (nyorot kategori induk):

<li class="menu-link{% if link.active or link.child_active %} menu-link--active{% endif %}">
  <a href="{{ link.url }}"{% if link.current %} aria-current="page"{% endif %}>
    {{ link.title }}
  </a>
</li>

Kenapa active, bukan current, buat class? Karena waktu pembeli buka produk di dalam koleksi “Sepatu”, link “Sepatu” di menu harus tetap kelihatan aktif walaupun URL-nya bukan /collections/sepatu persis.

❌ cuma nyorot yang persis sama, kategori induk gak kelihatan aktif

<li class="{% if link.current %}menu-link--active{% endif %}">

✅ kategori induk ikut aktif

<li class="{% if link.active or link.child_active %}menu-link--active{% endif %}">

Mega Menu Pakai link.object

link.object ngasih object tujuan link: collection, product, page, article, blog, policy, atau metaobject. Ini yang bikin kamu bisa nampilin gambar koleksi atau deskripsi di mega menu tanpa query tambahan:

{% for link in section.settings.menu.links %}
  <li class="mega-menu__item">
    <a href="{{ link.url }}">{{ link.title }}</a>
    {% if link.type == 'collection_link' and link.object.image %}
      {{ link.object.image | image_url: width: 400 | image_tag: loading: 'lazy' }}
    {% endif %}
  </li>
{% endfor %}

Cek link.type dulu, karena link.object buat http_link (link eksternal) itu nil, dan frontpage_link, search_link, catalog_link juga gak punya object. Daftar lengkap type ada di 15 Objects Konten > 2. link.

Tapi hati-hati performa: nampilin gambar buat semua item di mega menu bikin header berat. Pakai loading: 'lazy' dan batasi ke tingkat pertama. Bahasannya di 59 Performance Gambar dan LCP.

Dropdown yang cuma muncul waktu :hover gak bisa dibuka pakai keyboard atau layar sentuh. Pola yang dipakai Dawn dan Horizon: elemen <details> dan <summary> bawaan browser, yang otomatis bisa dibuka pakai Enter/Space dan punya state buka/tutup tanpa JavaScript:

{% for link in section.settings.menu.links %}
  <li>
    {% if link.links.size > 0 %}
      <details class="menu-dropdown">
        <summary aria-expanded="false">
          {{ link.title }}
        </summary>
        <ul class="menu-dropdown__list">
          {% for child_link in link.links %}
            <li>
              <a href="{{ child_link.url }}"{% if child_link.current %} aria-current="page"{% endif %}>
                {{ child_link.title }}
              </a>
            </li>
          {% endfor %}
        </ul>
      </details>
    {% else %}
      <a href="{{ link.url }}"{% if link.current %} aria-current="page"{% endif %}>{{ link.title }}</a>
    {% endif %}
  </li>
{% endfor %}

Perhatiin: link induk yang punya anak jadi <summary> (tombol buka), bukan <a>. Kalau induknya juga harus bisa diklik ke halamannya sendiri, tambahin link ke URL induk sebagai item pertama di dalam dropdown-nya. JavaScript kecil bisa ditambahin buat nutup dropdown lain waktu satu dibuka dan buat nyinkronin aria-expanded. Prinsip aksesibilitas navigasi lengkapnya di 55 Accessibility.

Sama persis, biasanya datar (satu tingkat) dan bisa lebih dari satu menu lewat block:

{% for block in section.blocks %}
  {% if block.type == 'link_list' and block.settings.menu != blank %}
    <div class="footer__menu" {{ block.shopify_attributes }}>
      <h2 class="footer__heading">{{ block.settings.heading | default: block.settings.menu.title }}</h2>
      <ul>
        {% for link in block.settings.menu.links %}
          <li>
            <a href="{{ link.url }}">
              {{ link.title }}{% if link.data_sharing_opt_out_icon %}{{ link.data_sharing_opt_out_icon }}{% endif %}
            </a>
          </li>
        {% endfor %}
      </ul>
    </div>
  {% endif %}
{% endfor %}

Dua hal di sini:

  1. block.settings.menu != blank, karena merchant bisa nambahin block tanpa milih menu, atau menu-nya udah dihapus.
  2. link.data_sharing_opt_out_icon, ikon “Your Privacy Choices” yang wajib tampil di sebelah link opt-out penjualan data (hukum privasi AS). Shopify otomatis nambahin link-nya ke menu kalau merchant nyalain fitur itu, kamu cuma perlu ngerender ikonnya kalau ada. Footer itu tempat link ini biasanya muncul.

Di layar kecil, menu yang sama biasanya ditampilin sebagai drawer (panel geser). Jangan bikin dua sumber data, loop linklist yang sama dua kali dengan markup berbeda, atau lebih baik satu markup dengan CSS responsif. Drawer-nya juga bisa pakai <details>, dengan <summary> jadi tombol hamburger. Yang penting: fokus keyboard terjebak di dalam drawer waktu terbuka, dan tombol tutup ada di awal.

Ringkasan

  1. Merchant bikin menu di Online Store > Navigation, bersarang maksimal tiga tingkat. Theme cuma nampilin.
  2. linklists semua menu, linklist satu menu, link satu item dengan title, url, links, object, type, dan penanda aktif.
  3. Pakai setting link_list, bukan handle hardcode, biar merchant milih menu. Nilainya langsung object linklist.
  4. Tiga loop bersarang buat tiga tingkat, selalu cek link.links.size > 0 sebelum bikin submenu.
  5. aria-current="page" buat current, class visual buat active/child_active biar kategori induk tetap kelihatan aktif.
  6. link.object buat mega menu (gambar koleksi, deskripsi) tanpa query tambahan, cek link.type dulu karena gak semua link punya object.
  7. Dropdown pakai <details>/<summary> biar bisa dibuka keyboard dan sentuh tanpa JavaScript.
  8. Footer: cek menu != blank, render data_sharing_opt_out_icon kalau ada.
  9. Mobile pakai data yang sama, cuma markup/CSS yang beda.

Bab selanjutnya kita bahas filtering: storefront filtering yang dikonfigurasi merchant lewat Search & Discovery (object filter, filter_value, tipe list, price_range, boolean), cara bikin form filter, sort, dan yang lama tag filtering.