Skip to content

How to add and reorder product tabs

Introduction

Product page tabs are built from layout XML blocks, so a child theme or feature module can add, reorder, or remove tabs without overriding any template.

The structure on the product page is:

product.info.tabs                      wrapper block (alias: tabs)
├── product.info.tabs.attributes      attribute-based tabs, first by default
│   └── product.info.tabs.attributes.tab
└── product.info.tabs.cms             global CMS block tabs
    └── product.info.tabs.cms.tab

Every tab renders through the shared partial Magento_Catalog::product/view/tabs/tab.phtml, which draws the collapsible accordion item. The .tab child blocks above define which template renders a single tab, so a project can replace the tab markup by re-pointing one template in layout XML.

All examples below go into the child theme's Magento_Catalog/layout/catalog_product_view.xml.

Reorder the tab groups

Move the global CMS tabs before the attribute tabs:

xml
<move element="product.info.tabs.cms" destination="product.info.tabs"
      before="product.info.tabs.attributes"/>

Add a static tab

The shared partial accepts its data through layout arguments, so a simple text tab needs no PHP at all:

xml
<referenceBlock name="product.info.tabs">
    <block name="product.info.tabs.size-guide"
           template="Magento_Catalog::product/view/tabs/tab.phtml"
           after="product.info.tabs.attributes">
        <arguments>
            <argument name="title" xsi:type="string" translate="true">Size Guide</argument>
            <argument name="content" xsi:type="string">Our sizes run true to fit.</argument>
            <argument name="collapsed_by_default" xsi:type="boolean">true</argument>
        </arguments>
    </block>
</referenceBlock>

The partial takes three arguments:

  • title - the accordion heading, escaped by the partial
  • content - the tab body HTML, printed as-is, so the caller is responsible for escaping
  • collapsed_by_default - whether the tab starts closed

A tab with empty content renders nothing, so a conditional tab can simply produce no output when it does not apply.

Add a tab with custom logic

For a tab that needs its own data source, add a block with your own template and build the content there. Render the item through the shared partial so the tab matches the rest of the accordion:

xml
<referenceBlock name="product.info.tabs">
    <block name="product.info.tabs.downloads"
           template="Vendor_Module::product/view/tabs/downloads.phtml"
           before="product.info.tabs.cms">
        <block name="product.info.tabs.downloads.tab" as="tab"
               template="Magento_Catalog::product/view/tabs/tab.phtml"/>
    </block>
</referenceBlock>
php
<?php $tabBlock = $block->getChildBlock('tab'); ?>
<?php if ($tabBlock instanceof \Magento\Framework\View\Element\Template): ?>
    <?= /** @noEscape */ $tabBlock->setData([
        'title' => __('Downloads'),
        'content' => $downloadListHtml,
        'collapsed_by_default' => true,
    ])->toHtml() ?>
<?php endif; ?>

Note

The accordion behaviour comes from the initProductTab Alpine component, which is registered once by Magento_Catalog::product/view/tabs.phtml. The partial only works when its block sits inside product.info.tabs.

Remove a tab group

A child theme that does not use global CMS tabs can remove the whole group:

xml
<referenceBlock name="product.info.tabs.cms" remove="true"/>
  • Attribute-based tabs are controlled per attribute in the admin. See Attributes.
  • Global CMS block tabs are managed under Stores > Configuration > Magebit > Theme > Product Tabs. See Product Tabs.