Bootstrap is undoubtedly the market leader when it comes to CSS frameworks, with around 15% of the Internet’s top sites using it. The framework provides developers and designers with a fantastic starting point, and its mobile-first approach makes it ideal for any project. However, I recently ran into a small issue that I hadn’t encountered before, and I was rather surprised that such a bug had made it into the latest 4.0.0 build. Then, I had a cursory look over the GitHub issue tracker, and discovered that the problem I had located had been a perennial issue for Bootstrap for several years. As such, I set about finding a solution to the problem, and this post documents what I came up with after an hour or so of debugging.

The Problem:
When implementing a Dropdown component inside a responsive table element (i.e. .responsive-table), the resulting dropdown menu was cropped by the parent <div> as shown in the screencast below:

This is frustrating, but it’s easy to understand why it’s happening. When we declare a responsive table element in Bootstrap, we essentially wrap a standard <table> element inside a <div> with special overflow rules defined. As a result, the .dropdown-menu element is being hidden by these special overflow rules. There are some fixes in the Github repo, but most of these use a complex system of setting padding on the .responsive-table element, which causes the entire element to jump around. Furthermore, since Bootstrap now uses Popper.js, these hacks cause some conflicts with the positioning of elements.

The Fix:
The fix that I have developed is relatively straightforward, and works well in most circumstances. Of course, it is not a fix-all solution, and we will likely need to wait for Bootstrap to officially fix this bug — despite there being numerous closed tickets on Github (#11037 and #13214), there doesn’t seem to be an official fix.

So here’s the small piece of jQuery code I came up with to solve the issue:

$('.table-responsive').on('show.bs.dropdown', '.dropdown', function(e) {
    var $dropdown = $(this).children('.dropdown-menu'),
        $toggle   = $(this).children('.dropdown-toggle'),
        yPos      = $(this).offset().top + $toggle.outerHeight(true,true);

    $(this).data('dropdown-menu', $dropdown);
                    
    $dropdown.appendTo('body');
                    
    window.setTimeout(function() {
        var t   = $dropdown.css('transform').split(','),
            tY  = parseInt( t[ (t.length - 1) ] ),
            top = Math.max(0, (yPos - tY));
                      
        $dropdown.css('top', top);
    }, 1);               
}).on('hidden.bs.dropdown', '.dropdown', function() {
    $(this).data('dropdown-menu').appendTo( $(this) );
});

The logic is simple, when we detect the show.bs.dropdown event has been triggered on a .dropdown element within a .table-responsive element, take the .dropdown-menu and move it to the <body>, so that it will be popped out of the .table-responsive element. We then need to adjust its position (because of Popper.js), so we position it relative to the .dropdown-toggle element. Finally, we store the menu in the parent’s data so that we can restore it. When hidden.bs.dropdown is triggered on the same element, we move it back into the parent (otherwise it would become orphaned and not work subsequently).

With this snippet enabled, here are the results:

It’s a much cleaner solution, but there is one small issue (which I don’t think can be avoided); since we need to use setTimeout to handle the positioning of the dropdown menu after it’s been shown (since Popper intercepts the event and positions the dropdown), there is sometimes a very small jump of the menu when it appears. Unfortunately, I don’t think this can be mitigated, since we have to use a timeout otherwise Popper will override our top position when it is initiated.

I hope this will be useful for others, as I spent quite a while trying to resolve the problem and this is about the best solution I have been able to come up with. Here’s hoping Bootstrap will have an official stance on this in the future, but given their record with similar issues in the past, it is unfortunately unlikely.

[mks_button size=”large” title=”View on Github” style=”squared” url=”https://github.com/benmajor/Bootstrap-Dropdown-Responsive-Table-Fix” target=”_blank” bg_color=”#c62641″ txt_color=”#FFFFFF” icon=”fa-github-alt” icon_type=”fa” nofollow=”0″]

2 comments

  1. I thought I found a way around this issue by adding data-boundary=”viewport” to the dropdown toggle element, but then we tested with iPad and noticed that this workaround doesn’t work on iOS devices. So your solution is still needed and valid for dropdowns inside responsive tables.

    Only thing I would change is to listen to “shown.bs.dropdown” event. The “show” event is triggered before dropdown menu is shown, but “shown” event is triggered as soon as the dropdown menu is visible. When listening to “shown” event you don’t need the 1 ms setTimeout and I couldn’t reproduce the random menu jump issue.

    1. Hi Perttu,

      Thanks for your comment. Yes, I also thought that data-boundary could be a go-to, but I ran into problems on certain Android installs, and also as you, with iOS devices (some versions of Chrome on iOS did seem to be okay, though).

      Great idea to hook into shown.bs.dropdown, as you say it removes the need for the timeout and random jump issue; I’ll update the post now with your advice.

      Thanks for checking in,
      Ben.

Leave a Reply

Your email address will not be published. Required fields are marked *