Bootstrap: Fixing Dropdowns in Responsive Tables

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.

View on Github

Add comment

Recent Posts

Recent Comments

Archives

Categories

Meta