Skip to content

Powered by Grav CMS + Helios Theme

Theme blocks

This page is for theme developers. It explains how a theme changes a storefront page without copying it: every page KahunaCart draws is split into named regions, and a theme overrides the regions it wants different and inherits the rest, including whatever a later release adds to the page.

For colors, radius and type, see Theming, which is still the cheapest way to make the shop look like your site. Reach for blocks when you need different markup, and for a template copy only when blocks can't do it.

Extend, don't copy

The plugin's templates are on Twig's path twice. Once by name, so a file in your theme with the same name replaces the plugin's. Once under the @kahunacart namespace, so that file can extend the plugin's page instead of replacing it.

A theme's checkout that adds a list of what's being bought above the totals is the whole file:

TWIG
{# user/themes/my-theme/templates/kahunacart-checkout.html.twig #}
{% extends '@kahunacart/kahunacart-checkout.html.twig' %}

{% block kahunacart_checkout_summary_items %}
<ul class="my-summary-items">
    {% for item in items %}<li>{{ item.title }} × {{ item.qty }}</li>{% endfor %}
</ul>
{% endblock %}

Everything else on the page is the plugin's, so the next release's checkout changes reach your store without you doing anything. A copied page stays frozen at the version you copied. The drift report (bin/plugin kahunacart templates:drift) can only tell you it has fallen behind.

Add-ons do the same under their own slug: @kahunacart-bookings, @kahunacart-affiliates, and so on.

Four ways to use a block

Replace it. Define the block in your file and your markup is drawn instead.

Wrap it. {{ parent() }} draws the plugin's version inside yours:

TWIG
{% block kahunacart_processing_body %}
<div class="my-card">{{ parent() }}</div>
{% endblock %}

Wrapping is the safest change there is. The processing page's body holds four states (declined, held for review, waiting, timed out). A wrap keeps all four, plus any a later release adds.

Add a class. Every wrapper the scripts read has an empty _class block inside its class attribute. Fill it with a leading space:

TWIG
{% block kahunacart_checkout_payment_step_class %} my-step my-step--pay{% endblock %}

Rearrange regions. Each big page has a _layout block holding its regions, and each checkout step has an inner block. Override one and draw the regions where you want them with {{ block('…') }}. Each call draws your version of that region if you have one, and the plugin's if not:

TWIG
{% block kahunacart_checkout_payment %}
<div class="my-columns">
    <div>
        {{ block('kahunacart_checkout_payment_methods') }}
        {{ block('kahunacart_checkout_order_fields') }}
        {{ block('kahunacart_checkout_terms') }}
    </div>
    <aside>{{ block('kahunacart_checkout_summary') }}</aside>
</div>
{% endblock %}

A region you leave out isn't drawn. That's how you drop one on purpose, and it's also how one gets lost by accident. When you override a _layout block, check the block reference and call every region you mean to keep.

What you can't lose

A few things sit outside every block, so no theme can drop them by mistake:

  • The step wrappers on checkout (id="kahunacart-checkout-address", -shipping and -payment, each with its data-kc-step), which are outside the inner blocks you override. A _step block includes its wrapper, so override the inner block (kahunacart_checkout_address, kahunacart_checkout_payment) rather than the _step around it.
  • The forms the cart's remove buttons post.
  • The checkout form itself, with its nonce and data-kc-step-strings.
  • The product and listing pages' structured data, and the flash messages on the checkout, cart, order, product, listing, category and search pages.

The scripts read some elements inside the regions: #kahunacart-checkout-summary, data-kc-place-order, data-kc-catalog-results and the rest. Keep them when you replace a region that holds one. For the checkout, see Payment totals in a checkout override.

Repeated parts are partials

One product card, one cart line and one receipt line are partials, not blocks. Override the partial and every instance changes, on every page that draws one, including the filter swap.

Partial Draws Gets
partials/kahunacart-product-card.html.twig One card in a product grid: the listing, a category, search, a filter swap product, plus the page's own variables (theme, config, page)
partials/kahunacart-cart-line.html.twig One cart line, on its own or as part of a set row (row.item is the line, row.set its set or null), line_notes, order
partials/kahunacart-cart-set-row.html.twig The heading row of a set in the cart row (row.set), order
partials/kahunacart-order-line.html.twig One line of the receipt item, order
partials/kahunacart-shipping-method.html.twig One delivery method at checkout offer, chosen_method, kc_first, order
partials/kahunacart-checkout-field.html.twig One checkout control field

A theme building its own row of products draws the same card the shop does:

TWIG
{% for product in kahunacart_products(4) %}
    {% include 'partials/kahunacart-product-card.html.twig' with {product: product} %}
{% endfor %}

Blocks in partials

Some regions are partials because the plugin also returns them on their own: the catalog results to a filter change, the shipping step to a change of address. Reach their blocks by extending the partial, at the same path in your theme:

TWIG
{# user/themes/my-theme/templates/partials/kahunacart-catalog-results.html.twig #}
{% extends '@kahunacart/partials/kahunacart-catalog-results.html.twig' %}

{% block kahunacart_catalog_results_summary %}
<p class="my-count">{{ products|length }} products</p>
{% endblock %}

The swap fetches the same file, so your version is what comes back on every filter click too.

Forms that a theme wires its own script to (htmx and the like) have an empty _attrs block inside the opening tag:

TWIG
{% extends '@kahunacart/partials/kahunacart-estimate.html.twig' %}
{% block kahunacart_estimate_form_attrs %} hx-post="{{ kahunacart_url('/cart') }}" hx-target="#kahunacart-estimate" hx-swap="outerHTML"{% endblock %}

Block reference

Indented names sit inside the one above them. Blocks marked empty draw nothing in the plugin. They are places for a theme to add something.

Checkout

Template: kahunacart-checkout.html.twig.

  • kahunacart_checkout_class: classes on the page's <section>
  • kahunacart_checkout_head: the heading
  • kahunacart_checkout_notices: what the store has to say about this order, and the bundle suggestion
  • kahunacart_checkout_empty: what an empty cart sees instead of the form
  • kahunacart_checkout_form_class: classes on the <form>
  • kahunacart_checkout_layout: every region inside the form
    • kahunacart_checkout_express: the express-pay zone
    • kahunacart_checkout_address_check: the address suggestion panel
    • kahunacart_checkout_address_step: the address step, wrapper included
    • kahunacart_checkout_address_step_class: classes on the step wrapper
    • kahunacart_checkout_address: inside the step
      • kahunacart_checkout_fields: the contact, billing and delivery fieldsets
      • kahunacart_checkout_address_actions: the Continue button and its "still needed" line
    • kahunacart_checkout_shipping: the delivery step (see the shipping step partial below)
    • kahunacart_checkout_payment_step: the payment step, wrapper included
    • kahunacart_checkout_payment_step_class: classes on the step wrapper
    • kahunacart_checkout_payment: inside the step
      • kahunacart_checkout_payment_methods: the payment choice
      • kahunacart_checkout_order_fields: the order section's fields (notes and anything declared beside them)
      • kahunacart_checkout_terms: the terms box
      • kahunacart_checkout_coupon_note: "Code applied"
      • kahunacart_checkout_line_notes: line notes and backorder lines
      • kahunacart_checkout_summary: the order summary, #kahunacart-checkout-summary included
      • kahunacart_checkout_summary_class: classes on the summary
      • kahunacart_checkout_summary_items: empty, above the totals
      • kahunacart_checkout_totals: the totals
      • kahunacart_checkout_place_order: Place order and its "still needed" line
  • kahunacart_checkout_gift_card: store credit and gift cards, outside the form

Shipping step

Template: partials/kahunacart-checkout-shipping-step.html.twig.

  • kahunacart_checkout_shipping_step_class: classes on #kahunacart-checkout-shipping
  • kahunacart_checkout_shipping_offers: the delivery options
  • kahunacart_checkout_shipping_actions: Continue to payment and its "still needed" line

Delivery options

Template: partials/kahunacart-shipping-offers.html.twig.

  • kahunacart_shipping_pickup: the collection points
  • kahunacart_shipping_methods: the delivery methods. One method is partials/kahunacart-shipping-method.html.twig.

While a collection point is chosen, the delivery list is hidden disabled and an empty shipping_method_id is posted before it. An override of kahunacart_shipping_methods has to keep both, or a collected order is charged for delivery.

Cart

Template: kahunacart-cart.html.twig.

  • kahunacart_cart_class: classes on the page's <section>
  • kahunacart_cart_head: the heading
  • kahunacart_cart_payment_open: "a payment page is still open" and its cancel button
  • kahunacart_cart_limit_notices: the quantity and order limit notices
  • kahunacart_cart_bundle: the bundle suggestion
  • kahunacart_cart_empty: what an empty cart sees
  • kahunacart_cart_layout: every region of a cart with something in it
    • kahunacart_cart_lines: the update form and its table
    • kahunacart_cart_table_class: classes on the table
    • kahunacart_cart_table_head: the column headings
    • kahunacart_cart_rows: the rows. One row is a partial (see above)
    • kahunacart_cart_actions: Update cart
    • kahunacart_cart_sale_tiers: where each quantity ladder stands
    • kahunacart_cart_summary: the summary column
    • kahunacart_cart_summary_class: classes on it
    • kahunacart_cart_coupon: the discount code box
    • kahunacart_cart_totals: the totals
    • kahunacart_cart_estimate: the shipping and tax estimate
    • kahunacart_cart_checkout_link: the Checkout button
    • kahunacart_cart_express: the express-pay zone
    • kahunacart_cart_cross_sells: "Goes well with your order"
  • kahunacart_cart_account_link: the link to the account, for a signed-in customer

Product

Template: kahunacart-product.html.twig.

  • kahunacart_product_class: classes on the page's <article>
  • kahunacart_product_layout: every region between the messages and the structured data
    • kahunacart_product_breadcrumbs
    • kahunacart_product_title
    • kahunacart_product_media: the merchant's badge and the gallery
    • kahunacart_product_summary
    • kahunacart_product_description
    • kahunacart_product_samples: the preview block
    • kahunacart_product_up_sells
    • kahunacart_product_lead: empty, above the buy box, for the price, stock or labels
    • kahunacart_product_buy: the buy button and option picker
    • kahunacart_product_wishlist: Save for later
    • kahunacart_product_cross_sells
    • kahunacart_product_attributes: the specification table
    • kahunacart_product_custom_fields
    • kahunacart_product_safety: the EU product safety section
    • kahunacart_product_tags
    • kahunacart_product_reviews
    • kahunacart_product_related: "You might also like"
    • kahunacart_product_back: Back to shop

Order and receipt

Template: kahunacart-order.html.twig.

  • kahunacart_order_class: classes on the page's <section>
  • kahunacart_order_head: the heading
  • kahunacart_order_layout: every region from the notices to the links. Add-ons draw license keys, bookings and the like in kahunacart_order_panels, so keep it when you rearrange
    • kahunacart_order_notices
    • kahunacart_order_continue: back to the article a subscriber was reading
    • kahunacart_order_meta: order number, payment reference, statuses
    • kahunacart_order_offline: bank transfer instructions
    • kahunacart_order_quote: Accept and Decline on a quote
    • kahunacart_order_pickup: where to collect it
    • kahunacart_order_items: the item table. One line is a partial (see above)
    • kahunacart_order_totals
    • kahunacart_order_tax: the customer's tax number and reverse-charge line
    • kahunacart_order_fields: answers to the checkout's own fields
    • kahunacart_order_downloads
    • kahunacart_order_refund: asking for a refund
    • kahunacart_order_history
    • kahunacart_order_panels: add-on panels
    • kahunacart_order_account_offer: a guest's way into an account
    • kahunacart_order_links: invoice, PDF, orders, back to shop

Templates: kahunacart-products.html.twig, kahunacart-category.html.twig and kahunacart-search.html.twig.

  • kahunacart_products_class, kahunacart_category_class, kahunacart_search_class: classes on the page's <section>
  • kahunacart_products_head: the heading and the shop page's own text
  • kahunacart_products_search: the search box
  • kahunacart_products_layout: the sidebar and the results
    • kahunacart_products_nav: the sidebar
    • kahunacart_products_results: the results region
  • kahunacart_category_breadcrumbs
  • kahunacart_category_head: the heading
  • kahunacart_category_banner: the category's picture
  • kahunacart_category_description
  • kahunacart_category_layout: the sidebar and the results
    • kahunacart_category_nav
    • kahunacart_category_results
  • kahunacart_category_back: Back to shop
  • kahunacart_search_head: the heading
  • kahunacart_search_form: the search box
  • kahunacart_search_layout: the sidebar and the results
    • kahunacart_search_nav
  • kahunacart_search_back: Back to shop

Results

Template: partials/kahunacart-catalog-results.html.twig.

  • kahunacart_catalog_results_class: classes on #kahunacart-catalog-results
  • kahunacart_catalog_results_summary: empty, under the filter chips, for a result count or a sort control
  • kahunacart_catalog_results_empty: what's said when nothing matches
  • kahunacart_catalog_results_grid: the product grid. One card is a partial (see above)

The grid itself (partials/kahunacart-product-grid.html.twig) has kahunacart_product_grid_class, for classes on its <ul>.

Account overview

Template: kahunacart-account.html.twig.

  • kahunacart_account_class: classes on the page's <section>
  • kahunacart_account_head: the heading
  • kahunacart_account_summary: empty, above the panels, for a row of figures. The page has order_count, download_count and credit_balance_minor (store credit in the currency being browsed, null when credit is off)

Small pages

  • kahunacart_processing_class, kahunacart_processing_body: the payment return page, with one block per state inside the body: kahunacart_processing_declined, kahunacart_processing_held, kahunacart_processing_waiting and kahunacart_processing_timeout
  • kahunacart_closed_class, kahunacart_closed_body: the cart and checkout while the store is paused or coming soon
  • kahunacart_denied_class, kahunacart_denied_body: a download that can't be given
  • kahunacart_currency_class, kahunacart_currency_body: the question before a currency switch empties the cart, with kahunacart_currency_question inside the body around the sentence and the list of what would go (the form stays the plugin's)
  • kahunacart_paywall_class, kahunacart_paywall_body: a paywalled article

Form attributes

  • kahunacart_search_form_attrs: in partials/kahunacart-search.html.twig
  • kahunacart_estimate_form_attrs: in partials/kahunacart-estimate.html.twig
  • kahunacart_bundle_swap_form_attrs, kahunacart_bundle_keep_form_attrs: in partials/kahunacart-bundle-suggestions.html.twig

Block names are an interface

A block name is a promise to every theme that uses it. Blocks are added freely, in any release, because wrapping existing markup in a new block changes nothing for a theme that doesn't use it. A block is only renamed or removed in a major release, after a minor release that keeps the old name working beside the new one. The plugin's test suite fails if a block exists in a template without being listed on this page, or is listed here without existing.

  • Theming: the color, radius and type tokens, the class reference, and the notes for a checkout override
  • Storefront: which template draws which route
  • Extension zones: where add-ons draw on a page, as opposed to where themes do