I think the issue you're experiencing is that $('.accordion-toggle:visible').length is being retrieved before the panel has opened/closed.
You should use shown.bs.collapse and hidden.bs.collapse to detect when a panel has finished opening or closing.
Here's my crack. It's not quite there, but it's a fair bit cleaner (maybe).
var active = false, $collapseBtn = $('#collapse_init'), $accordion = $('#accordion'); $collapseBtn.on('click',function(){ active = ! active; $('.panel-collapse').collapse( active ? 'show' : 'hide'); $('.panel-title').attr('data-toggle', active ? '' : 'collapse' ); }) $accordion.on({ 'shown.bs.collapse': function () { active = true; $collapseBtn.text( 'Hide all' ); }, 'hidden.bs.collapse': function ( e) { var $this = $( this ); active = ! ! $('.panel-collapse:visible').not( $this ).length; $collapseBtn.text( ( active ? 'Hide' : 'Show' ) + ' all' ); } }); https://codepen.io/anon/pen/dRePNQhttps://codepen.io/jamespoel/pen/dRePNQ