How to add Tab in Form Ui Component

Unfortunately, I don't think any of the other answers will work. One (the link to a tutorial) isn't even a UI Component. It is possible to add tabs with a UI Component. My example is for Magento 2.2 +, but I suspect it will work in earlier versions if adjusted for the old UI Component syntax.

Step #1

The following are the necessary requirements for making a UI Component with tabs work (replace all references to "referral" with your UI Component name):

<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <argument name="data" xsi:type="array">
        <item name="label" xsi:type="string" translate="true">Referral</item>
        <item name="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">referral_form.referral_form_data_source</item>
        </item>
    </argument>
    <settings>
        <layout>
            <!-- I assume there are other options; set the layout handle accordingly -->
            <navContainerName>left</navContainerName>
            <type>tabs</type>
        </layout>
        <deps>
            <dep>referral_form.referral_form_data_source</dep>
        </deps>
    </settings>
    <dataSource name="referral_form_data_source">
        <!-- ... -->
    </dataSource>
    <fieldset name="referral_fieldset">
        <settings>
            <label translate="true">General Info</label>
        </settings>

        <!-- Each fieldset is a new tab -->
    </fieldset>
</form>

Note: most of your example UI Component code is correct, but you need to add the following to the <settings> node:

<navContainerName>left</navContainerName>
<type>tabs</type>

Step #2

Set the layout on the page in the layout XML file: <page layout="admin-2columns-left"/>


Magento_Customer/view/base/ui_component/customer_form.xml is a good example in the core.


Replace below code in your roaming_form.xml file.

<?xml version="1.0" encoding="UTF-8"?>
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <argument name="data" xsi:type="array">
        <item name="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">roaming_form.roaming_form_data_source</item>
        </item>
        <item name="label" xsi:type="string" translate="true">Roaming Details</item>
        <item name="template" xsi:type="string">templates/form/collapsible</item>
    </argument>
    <settings>
        <buttons>
            <button name="delete" class="Vendor\Module\Block\Adminhtml\Roaming\Edit\Buttons\Delete"/>
            <button name="save" class="Vendor\Module\Block\Adminhtml\Roaming\Edit\Buttons\Save"/>
            <button name="back">
                <url path="*/*/"/>
                <class>back</class>
                <label translate="true">Back</label>
            </button>
        </buttons>
        <namespace>roaming_form</namespace>
        <dataScope>data</dataScope>
        <deps>
            <dep>roaming_form.roaming_form_data_source</dep>
        </deps>
    </settings>

    <dataSource name="roaming_form_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Vendor\Module\Ui\DataProvider\Form\RoamingDataProvider</argument>
            <argument name="name" xsi:type="string">roaming_form_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">entity_id</argument>
            <argument name="requestFieldName" xsi:type="string">id</argument>
        </argument>
        <argument name="data" xsi:type="array">
            <item name="js_config" xsi:type="array">
                <item name="component" xsi:type="string">Magento_Ui/js/form/provider</item>
            </item>
        </argument>
        <settings>
            <submitUrl path="mobifone/roaming/save"/>
        </settings>
    </dataSource>

    <fieldset name="roaming_details">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="collapsible" xsi:type="boolean">true</item>
                <item name="label" xsi:type="string" translate="true">Roaming Details</item>
            </item>
        </argument>
        <field name="entity_id">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="visible" xsi:type="boolean">false</item>
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="formElement" xsi:type="string">input</item>
                    <item name="source" xsi:type="string">current_roaming</item>
                </item>
            </argument>
        </field>
        <!--<field name="product_id">-->
            <!--<argument name="data" xsi:type="array">-->
                <!--<item name="config" xsi:type="array">-->
                    <!--<item name="label" xsi:type="string">Product ID</item>-->
                    <!--<item name="visible" xsi:type="boolean">true</item>-->
                    <!--<item name="dataType" xsi:type="string">text</item>-->
                    <!--<item name="formElement" xsi:type="string">input</item>-->
                    <!--<item name="source" xsi:type="string">current_roaming</item>-->
                <!--</item>-->
            <!--</argument>-->
        <!--</field>-->
        <field name="roaming_name">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="label" xsi:type="string">Roaming Name</item>
                    <item name="visible" xsi:type="boolean">true</item>
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="formElement" xsi:type="string">input</item>
                    <item name="source" xsi:type="string">current_roaming</item>
                </item>
            </argument>
        </field>
        <field name="roaming_image">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="dataType" xsi:type="string">string</item>
                    <item name="source" xsi:type="string">current_roaming</item>
                    <item name="label" xsi:type="string" translate="true">Roaming Image</item>
                    <item name="visible" xsi:type="boolean">true</item>
                    <item name="formElement" xsi:type="string">fileUploader</item>
                    <item name="elementTmpl" xsi:type="string">ui/form/element/uploader/uploader</item>
                    <item name="previewTmpl" xsi:type="string">Magento_Catalog/image-preview</item>
                    <item name="dataScope" xsi:type="string">image</item>
                    <item name="required" xsi:type="boolean">false</item>
                    <item name="sortOrder" xsi:type="number">13</item>
                    <item name="uploaderConfig" xsi:type="array">
                        <item name="url" xsi:type="url" path="mobifone/roaming_image/upload"/>
                    </item>
                </item>
            </argument>
        </field>
        <field name="description">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="validation" xsi:type="array">
                        <item name="required-entry" xsi:type="boolean">true</item>
                    </item>
                    <item name="label" xsi:type="string">Description</item>
                    <item name="visible" xsi:type="boolean">true</item>
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="formElement" xsi:type="string">wysiwyg</item>
                    <item name="template" xsi:type="string">ui/form/field</item>
                    <item name="wysiwyg" xsi:type="boolean">true</item>
                    <item name="source" xsi:type="string">current_roaming</item>
                </item>
            </argument>
        </field>
    </fieldset>
    <fieldset name="list_product">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="collapsible" xsi:type="boolean">true</item>
                <item name="label" xsi:type="string" translate="true">List Products</item>
            </item>
        </argument>
        <insertListing name="products">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="source" xsi:type="string">products</item>
                </item>
            </argument>
            <settings>
                <externalData>id</externalData>
                <externalProvider>${ $.ns }.product_listing_roaming</externalProvider>
                <selectionsProvider>${ $.ns }.${ $.ns }.columns.ids</selectionsProvider>
                <autoRender>true</autoRender>
                <dataScope>product_listing_roaming</dataScope>
                <ns>product_listing_roaming</ns>
            </settings>
        </insertListing>
    </fieldset>
</form>

You have to just add below line to make any tab collapsible.

<item name="collapsible" xsi:type="boolean">true</item>

Replace below code in your roaming_form.xml file

  <?xml version="1.0" encoding="UTF-8"?>
  <form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
 <argument name="data" xsi:type="array">
     <item name="js_config" xsi:type="array">
         <item name="provider" xsi:type="string">roaming_form.roaming_form_data_source</item>
     </item>
     <item name="label" xsi:type="string" translate="true">Roaming Details</item>
     <!--item name="template" xsi:type="string">templates/form/collapsible</item-->
 </argument>
 <settings>
     <buttons>
         <button name="delete" class="Vendor\Module\Block\Adminhtml\Roaming\Edit\Buttons\Delete"/>
         <button name="save" class="Vendor\Module\Block\Adminhtml\Roaming\Edit\Buttons\Save"/>
         <button name="back">
             <url path="*/*/"/>
             <class>back</class>
             <label translate="true">Back</label>
         </button>
     </buttons>
     <namespace>roaming_form</namespace>
     <dataScope>data</dataScope>
     <deps>
         <dep>roaming_form.roaming_form_data_source</dep>
     </deps>

    <layout>
          <navContainerName>left</navContainerName>
          <type>tabs</type>
     </layout>

 </settings>

 <dataSource name="roaming_form_data_source">
     <argument name="dataProvider" xsi:type="configurableObject">
         <argument name="class" xsi:type="string">Vendor\Module\Ui\DataProvider\Form\RoamingDataProvider</argument>
         <argument name="name" xsi:type="string">roaming_form_data_source</argument>
         <argument name="primaryFieldName" xsi:type="string">entity_id</argument>
         <argument name="requestFieldName" xsi:type="string">id</argument>
     </argument>
     <argument name="data" xsi:type="array">
         <item name="js_config" xsi:type="array">
             <item name="component" xsi:type="string">Magento_Ui/js/form/provider</item>
         </item>
     </argument>
     <settings>
         <submitUrl path="mobifone/roaming/save"/>
     </settings>
 </dataSource>

 <fieldset name="roaming_details">
      <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="label" xsi:type="string" translate="true">Roaming Details</item>
            <item name="sortOrder" xsi:type="number">10</item>
        </item>
    </argument>
     <field name="entity_id">
         <argument name="data" xsi:type="array">
             <item name="config" xsi:type="array">
                 <item name="visible" xsi:type="boolean">false</item>
                 <item name="dataType" xsi:type="string">text</item>
                 <item name="formElement" xsi:type="string">input</item>
                 <item name="source" xsi:type="string">current_roaming</item>
             </item>
         </argument>
     </field>

     <field name="roaming_name">
         <argument name="data" xsi:type="array">
             <item name="config" xsi:type="array">
                 <item name="label" xsi:type="string">Roaming Name</item>
                 <item name="visible" xsi:type="boolean">true</item>
                 <item name="dataType" xsi:type="string">text</item>
                 <item name="formElement" xsi:type="string">input</item>
                 <item name="source" xsi:type="string">current_roaming</item>
             </item>
         </argument>
     </field>
     <field name="roaming_image">
         <argument name="data" xsi:type="array">
             <item name="config" xsi:type="array">
                 <item name="dataType" xsi:type="string">string</item>
                 <item name="source" xsi:type="string">current_roaming</item>
                 <item name="label" xsi:type="string" translate="true">Roaming Image</item>
                 <item name="visible" xsi:type="boolean">true</item>
                 <item name="formElement" xsi:type="string">fileUploader</item>
                 <item name="elementTmpl" xsi:type="string">ui/form/element/uploader/uploader</item>
                 <item name="previewTmpl" xsi:type="string">Magento_Catalog/image-preview</item>
                 <item name="dataScope" xsi:type="string">image</item>
                 <item name="required" xsi:type="boolean">false</item>
                 <item name="sortOrder" xsi:type="number">13</item>
                 <item name="uploaderConfig" xsi:type="array">
                     <item name="url" xsi:type="url" path="mobifone/roaming_image/upload"/>
                 </item>
             </item>
         </argument>
     </field>
     <field name="description">
         <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="validation" xsi:type="array">
                    <item name="required-entry" xsi:type="boolean">true</item>
                </item>
                <item name="label" xsi:type="string">Description</item>
                <item name="visible" xsi:type="boolean">true</item>
                <item name="dataType" xsi:type="string">text</item>
                <item name="formElement" xsi:type="string">wysiwyg</item>
                <item name="template" xsi:type="string">ui/form/field</item>
                <item name="wysiwyg" xsi:type="boolean">true</item>
                <item name="source" xsi:type="string">current_roaming</item>
            </item>
        </argument>
    </field>
</fieldset>
<fieldset name="list_product">
    <argument name="data" xsi:type="array">
       <item name="config" xsi:type="array">
           <item name="label" xsi:type="string" translate="true">List Products</item>
           <item name="sortOrder" xsi:type="number">10</item>
       </item>
   </argument>
    <insertListing name="products">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="source" xsi:type="string">products</item>
            </item>
        </argument>
        <settings>
            <externalData>id</externalData>
            <externalProvider>${ $.ns }.product_listing_roaming</externalProvider>
            <selectionsProvider>${ $.ns }.${ $.ns }.columns.ids</selectionsProvider>
            <autoRender>true</autoRender>
            <dataScope>product_listing_roaming</dataScope>
            <ns>product_listing_roaming</ns>
        </settings>
    </insertListing>
</fieldset>