Leverage Bootstrap to manage multiple tab panels with one tab navigation system

Is there a way to control two different tab contents with a single tab navigation in Bootstrap 4?

In Bootstrap 3, I used comma separated data targets for achieving this (like in this example: ).

However, in Bootstrap 4, this method no longer works for tabs.

I tried using the collapse component with multiple targets, but it didn't work for tabs either: https://getbootstrap.com/docs/4.0/components/collapse/#multiple-targets

Are there any other alternatives to achieve this functionality?

Below is the code snippet:

<div class="B">
    <div class="container">

        <div class="tab-content" id="ueberTabA">
            <div class="tab-pane fade" id="panel_a_first" role="tabpanel" aria-labelledby="first-tab">
                A First 
            </div>
            <div class="tab-pane fade show active" id="panel_a_second" role="tabpanel" aria-labelledby="second-tab">
                A Second
            </div>
            <div class="tab-pane fade" id="panel_a_third" role="tabpanel" aria-labelledby="third-tab">
                A Third
            </div>
        </div>
    </div>
</div>

<div class="container">
    <ul class="nav nav-tabs" id="ueberTab" role="tablist">
        <li class="nav-item"><a class="nav-link" id="first-tab" data-target="#panel_b_first, #panel_a_first" data-toggle="tab" href="#first" role="tab" aria-controls="first" aria-selected="false">first</a></li>
        <li class="nav-item"><a class="nav-link active" id="second-tab" data-target="#panel_b_second, #panel_a_second" data-toggle="tab" href="#second" role="tab" aria-controls="second" aria-selected="true">second</a></li>
        <li class="nav-item"><a class="nav-link" id="third-tab" data-target="#panel_b_thrid, #panel_a_third" data-toggle="tab" href="#third" role="tab" aria-controls="third" aria-selected="false">Unser third</a></li>
    </ul>
    <div class="tab-content" id="ueberTabB">
        <div class="tab-pane fade" id="panel_b_first" role="tabpanel" aria-labelledby="first-tab">
            B First 
        </div>
        <div class="tab-pane fade show active" id="panel_b_second" role="tabpanel" aria-labelledby="second-tab">
            B Second
        </div>
        <div class="tab-pane fade" id="panel_b_thrid" role="tabpanel" aria-labelledby="third-tab">
            B Third
        </div>
    </div>
</div>

Answer №1

Latest Update on Bootstrap 5 (2021)

If you're looking to display multiple tab panes with Bootstrap Tabs, it's not a built-in feature. However, you can achieve this using JavaScript to switch between secondary tab panes...


document.querySelectorAll('button[data-bs-toggle="tab"]').forEach((tab,index)=>{
    tab.addEventListener('show.bs.tab', function (event) {
        let targetClass = tab.dataset.bsTarget
        var currentPane = document.querySelector('#secondTabContent '+targetClass)
        var activeSibling = document.querySelector('#secondTabContent .tab-pane.active')
        
        // Hide the currently active sibling pane
        activeSibling.classList.remove('show')
        activeSibling.classList.remove('active')

        // Show the selected secondary pane
        currentPane.classList.add('show')
        currentPane.classList.add('active')
    })  
})

Explore how to implement multiple Tab panes in Bootstrap 5


Original Answer for Bootstrap 4

In Bootstrap 4.0.0, displaying multiple Tab panes is not supported. This limitation has been acknowledged as an open issue and may be considered as a feature in future versions like Bootstrap 4.1.

For more information, visit the official GitHub page

Answer №2

I have managed to come up with a solution that may not be the prettiest but gets the job done.

Here are the steps you need to follow:

$(document).on('click', '#ueberTab a', function(e) {
          otherTabs = $(this).attr('data-secondary').split(',');
          for(i= 0; i<otherTabs.length;i++) {
            nav = $('<ul class="nav d-none" id="tmpNav"></ul>');
            nav.append('<li class="nav-item"><a href="#" data-toggle="tab" data-target="' + otherTabs[i] + '">nav</a></li>"');
            nav.find('a').tab('show');
          }
        });
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script><div class="B">
    <div class="container">

        <div class="tab-content" id="ueberTabA">
            <div class="tab-pane fade" id="panel_a_first" role="tabpanel" aria-labelledby="first-tab">
                A First 
            </div>
            <div class="tab-pane fade show active" id="panel_a_second" role="tabpanel" aria-labelledby="second-tab">
                A Second
            </div>
            <div class="tab-pane fade" id="panel_a_third" role="tabpanel" aria-labelledby="third-tab">
                A Third
            </div>
        </div>
    </div>
</div>

<div class="container">
    <ul class="nav nav-tabs" id="ueberTab" role="tablist">
        <li class="nav-item"><a class="nav-link" id="first-tab" data-target="#panel_b_first" data-secondary="#panel_a_first" data-toggle="tab" href="#first" role="tab" aria-controls="first" aria-selected="false">first</a></li>
        <li class="nav-item"><a class="nav-link active" id="second-tab" data-target="#panel_b_second" data-secondary="#panel_a_second" data-toggle="tab" href="#second" role="tab" aria-controls="second" aria-selected="true">second</a></li>
        <li class="nav-item"><a class="nav-link" id="third-tab" data-target="#panel_b_thrid" data-secondary="#panel_a_third" data-toggle="tab" href="#third" role="tab" aria-controls="third" aria-selected="false">Unser third</a></li>
    </ul>
    <div class="tab-content" id="ueberTabB">
        <div class="tab-pane fade" id="panel_b_first" role="tabpanel" aria-labelledby="first-tab">
            B First 
        </div>
        <div class="tab-pane fade show active" id="panel_b_second" role="tabpanel" aria-labelledby="second-tab">
            B Second
        </div>
        <div class="tab-pane fade" id="panel_b_thrid" role="tabpanel" aria-labelledby="third-tab">
            B Third
        </div>
    </div>
</div>

Answer №3

If we want to expand Carol Skelly's answer to include more than 2 target tab panels while maintaining the built-in hide/show animation, a proof of concept is necessary based on the fade effect in BS 5 which only exposes fade animation.

In addition, BS 5 offers a showcase with nav links that can be removed for more complex layouts.

Below is a minimal markup example of the proof of concept:

/**
 * Allow a bootstrap single nav tab to target multiple tab panels
 */
document.querySelectorAll( '[data-bs-toggle="tab"][data-bs-target]' ).forEach( (navTab) => {
  const targetPanes = document.querySelectorAll( navTab.dataset.bsTarget )

  if( targetPanes.length > 1 ) {
    navTab.addEventListener( 'show.bs.tab', ( e ) => {
      const activeNav     = navTab.closest( '[role="tablist"]' ).querySelector( '.active[role="tab"]' )
      const activePanes   = document.querySelectorAll( activeNav.dataset.bsTarget )

      // Hide other panes
      activePanes.forEach( ( activePane ) => {
        // Ensure selector targets only tab panels
        if( activePane.matches( '[role="tabpanel"]' ) ) {
          const transProperty     = activePane.classList.contains( 'fade' ) ? 'opacity' : null
          const outTransDuration  = transProperty ? utils.toMilliseconds( utils.getComputedTransitions( activePane, '', transProperty ).duration ) : 0

          // Remove class making pane visible
          activePane.classList.remove( 'show' )
          // Wait for transition...
          setTimeout( () => {
            // Then hide pane
            activePane.classList.remove( 'active' )

            targetPanes.forEach( ( targetPane ) => {
              // Ensure selector targets only tab panels
              if( targetPane.matches( '[role="tabpanel"]' ) ) {
                const transProperty   = targetPane.classList.contains( 'fade' ) ? 'opacity' : null
                const inTransDuration = transProperty ? utils.toMilliseconds( utils.getComputedTransitions( targetPane, '', transProperty ).duration ) : 0
                
                // Show pane
                targetPane.classList.add( 'active' )
                // Can't figure why, but this ensures animation of target panes beyond first lasts as long as animation of first
                setTimeout( () => {
                  targetPane.classList.add( 'show' )
                }, 0)
              }
            } )
          }, outTransDuration )
        }
      } )

    } )
  }
} )

/**
 * Utils to get transition duration of a CSS property
 */
const utils = {
  getComputedTransitions: ( elem, pseudo, property ) => {

    const computedStyles  = window.getComputedStyle( elem, pseudo )
    const delays          = computedStyles.getPropertyValue( 'transition-delay' ).split( ', ' )
    const durations       = computedStyles.getPropertyValue( 'transition-duration' ).split( ', ' )
    const properties      = computedStyles.getPropertyValue( 'transition-property' ).split( ', ' )
    const timingFunctions = computedStyles.getPropertyValue( 'transition-timing-function' ).split( ', ' )
  
    let transitions = {}
  
    for( let i in properties ) {
      transitions[properties[i] || 'all'] = {
        duration: durations[i],
        delay: delays[i],
        timingFunction: timingFunctions[i],
      }
    }
  
    if( property ) {
      return transitions[property] ? transitions[property] : transitions['all']
    }
  
    return transitions
  
  },

  toMilliseconds: ( string ) => {

    if( string.match( /^[0-9.]+s$/g ) ) {
      return parseFloat( string ) * 1000
    }
    if( string.match( /^[0-9.]+ms$/g ) ) {
      return parseFloat( string )
    }
  
    return string
  
    
}
<script src="https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="85e7eaeaf1f6f1f7e4f5c5b0abb6abb6">[email protected]</a>/dist/js/bootstrap.bundle.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="f0929f9f848384829180b0c5dec3dec3">[email protected]</a>/dist/css/bootstrap.min.css" rel="stylesheet"/>
<div role="tablist">
  <button type="button" class="active" data-bs-toggle="tab" data-bs-target=".pane-first" role="tab" aria-controls="pane-first" aria-selected="true">Tab first</button>
  <button type="button" data-bs-toggle="tab" data-bs-target=".pane-second" role="tab" aria-controls="pane-first" aria-selected="false">Tab second</button>
</div>
...
<div class="tab-content">
  <div class="tab-pane fade pane-first active show" role="tabpanel">Block 1, pane first</div>
  <div class="tab-pane fade pane-second" role="tabpanel">Block 1, pane second</div>
</div>
...
<div class="tab-content">
  <div class="tab-pane fade pane-first active show" role="tabpanel">Block 2, pane first</div>
  <div class="tab-pane fade pane-second" role="tabpanel">Block 2, pane second</div>
</div>

The fade transitions may not be perfectly synchronized, especially when toggling block 1 tab panels.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

When the mouse reaches the far left portion of the screen, an action will be triggered

Is there a way to trigger an action when the mouse reaches the far left or right of the screen using HTML, CSS, jQuery, or any other method? I'm specifically trying to show my navbar when the mouse reaches the left end of the screen. Any assistance wo ...

Difficulties arise when trying to align text vertically between two floating images

After running the code, I noticed that the text "Text Here" is positioned at the bottom of the div instead of the top. I have attempted to adjust it using properties like vertical-align, padding, and margin-top within the .subText styling, but so far, I ha ...

The TextField component in MUIv5 is showing some frustrating white space in the corners

I've integrated the MaterialUI_v5 library and included a TextField component within a Paper component. The background of the Paper component has been customized to appear in a shade of green. For the Textfield component, I have applied a styling tha ...

Prevent the occurrence of a fixed height problem associated with the Bootstrap Navbar (Mega Menu)

In my project, I have a Custom Mega Menu that dynamically creates code to display all Nav bar entries. Since changing the logic of Mega menu creation is difficult, I am looking for a CSS solution to avoid fixed height in bootstrap columns. My code structu ...

Transmitting JavaScript Arrays within Arrays via Ajax

I am receiving my fields as an array using JavaScript. Each field in the array contains properties. For example: arr[0] { 'id':'1', 'title':'testtitle', 'value': 'test value' } arr[1] { ...

AJAX: How to handle a successful POST request?

I have been recently exploring AJAX and I can see why I hesitated to delve into this particular area of JavaScript; it appears quite intricate. Most discussions seem centered around how to SEND data via POST, with little focus on what happens once the POS ...

What is the best way to make an element unclickable on an HTML page while still making it draggable using jQuery's draggable

One way to create an element on a page with a mouse click is by using the following code: $("#sheet").append('<input class="elements" id="Button12" type="button" value="button" />'); If you want the element to be temporarily unclickable, ...

Transfer various field values to jQuery

I am looking to send multiple field values to jQuery for validation, but currently only receiving the value of the changed field. How can I pass both field values to jQuery? Enter some text: <input type="text" name="txt1" value="Hello" onchange="myF ...

Is there a way to identify when a radio button's value has changed without having to submit the form again?

Within this form, I have 2 radio buttons: <form action='' method='post' onsubmit='return checkForm(this, event)'> <input type = 'radio' name='allow' value='allow' checked>Allow ...

The JSON GET method displays HTML content when accessed through code or console, but presents a JSON object when accessed through a web address

I am currently trying to execute the following code: $(document).ready(function () { $.ajax({ url: 'http://foodfetch.us/OrderApi/locations', type: 'GET', success: function(data){ alert(data); ...

Begin an unnumbered hierarchical list commencing at 1.2.1

I am trying to create a nested unordered list with specific numbering. My goal is for the list to start at "1.2.1, 1.2.2, etc." Please refer to the attached fiddle for reference. The desired outcome is shown in the following image: https://i.stack.imgur ...

Ways to locate the div that specifically contains a word with an exact match

I recently came across this link: jQuery :contains(), but to match an exact string After finding the div I need, I want to modify its attributes using the filter option. It should look something like this: $('#viewer .textLayer div:contains(' ...

Error encountered when utilizing a specialized jQuery extension: "not a function"

Every time I attempt to execute this function (function($) { $.fn.generateURLHash = function() { return document.URL.substr(document.URL.indexOf('#')+1); }; })(jQuery); when I call $.generateURLHash(), an error pops up in th ...

Adjust the contents of a div to automatically fit within a hidden overflow div

Trying to fit the contents of a div (including an image, search bar, and three buttons stacked on top of each other) into another div with CSS styling that hides overflow has been a challenge. The CSS code for the div in question is: .jumbotron { overfl ...

Tips for making your inner content as wide as possible

I'm currently working on developing collapsible tables, where an outer and inner table are displayed for every row that is clicked. This is the code I have implemented: HTML: <table class="table outerTbl mb-0"> <thead> <t ...

Struggles with maintaining proper vertical alignment

Despite following numerous tutorials and tips, every time I make a change to center align my content, it ends up breaking something else and pushing me further away from my goal. While I have successfully centered the content horizontally, I am now strugg ...

"An issue with the external jQuery file loop causes it to skip either the first or

I have a webpage where I am dynamically loading content from an external JavaScript file. This file identifies div elements with a specific class name and then fills the content of those divs based on a custom attribute, which contains a link to an API. Th ...

scrollable list using bootstrap

<div class="panel panel-primary" id="result_panel"> <div class="panel-heading"><h3 class="panel-title">List of Results</h3> </div> <div class="panel-body"> <ul class="list-group"> &l ...

Does the RequireJS order plugin begin fetching files in the correct order, but fail to wait for them to finish downloading?

I've been trying to incorporate RequireJS into my project, but I'm encountering some issues with its functionality. It seems like the order plugin should download scripts in the correct order and wait for each one to finish before moving on to th ...

Update the message displayed in MUI DataTables when there are no matching records found

Whenever the data array in my MUIDataTable is empty, a default message pops up stating: "Sorry, no matching records found". I'm looking to personalize this message and input my own text. Any assistance would be greatly appreciated. ...