Nested Accordion

A simple, nestable accordion with some examples of nesting levels and a media query.

Nested Accordion

A simple accordion that allows for a nested structure. The style comes with some examples of how to style three levels and how to add a media query for decreasing the size on smaller screens. Clicking a trigger element will open the content.

The HTML

<ul id="cbp-ntaccordion" class="cbp-ntaccordion">
	<li>
		<h3 class="cbp-nttrigger">Oat cake tootsie roll</h3>
		<div class="cbp-ntcontent">
			<p><!-- ... --></p>
			<ul class="cbp-ntsubaccordion">
				<li>
					<h4 class="cbp-nttrigger">Donut pastry</h4>
					<div class="cbp-ntcontent"><!-- ... --></div>
				</li>
				<li>
					<h4 class="cbp-nttrigger">Carrot cake</h4>
					<div class="cbp-ntcontent">
						<!-- ... -->
						<ul class="cbp-ntsubaccordion">
							<li>
								<h5 class="cbp-nttrigger">Donut pastry</h5>
								<div class="cbp-ntcontent"><!-- ... --></div>
							</li>
							<li><!-- ... --></li>
							<li><!-- ... --></li>
						</ul>
					</div>
				</li>
				<li>
					<h4 class="cbp-nttrigger">Tootsie roll marshmallow</h4>
					<div class="cbp-ntcontent">
						<!-- ... -->
					</div>
				</li>
			</ul>
		</div>
	</li>
	<li><!-- ... --></li>
	<li><!-- ... --></li>
	<li><!-- ... --></li>
</ul>

The CSS

/* Icon font for arrow icons */
@font-face {
	font-family: 'icomoon';
	src:url('../fonts/icomoon_arrows/icomoon.eot');
	src:url('../fonts/icomoon_arrows/icomoon.eot?#iefix') format('embedded-opentype'),
		url('../fonts/icomoon_arrows/icomoon.woff') format('woff'),
		url('../fonts/icomoon_arrows/icomoon.ttf') format('truetype'),
		url('../fonts/icomoon_arrows/icomoon.svg#icomoon') format('svg');
	font-weight: normal;
	font-style: normal;
} /* Iconfont by Icomoon http://icomoon.io/ */

/* Accordion style */
.cbp-ntaccordion {
	list-style: none;
	margin: 0;
	padding: 0;
}

.cbp-ntsubaccordion {
	list-style: none;
}

.cbp-ntaccordion .cbp-nttrigger {
	cursor: pointer;
} 

.cbp-ntaccordion h3 {
	margin: 0 0 0.3em;
	padding: 1em 0 0.5em;
	border-bottom: 1px solid #ddd;
	font-size: 2.75em;
	font-weight: 300;
}

.cbp-ntaccordion h4 {
	font-size: 1.2em;
	text-transform: uppercase;
	letter-spacing: 0.4em;
	padding: 0.5em 0 0.5em;
	margin: 0 0 0.5em;
}

.cbp-ntaccordion h5 {
	font-size: 1.2em;
	color: #aaa;
	padding: 0.5em 0 0.5em;
	margin: 0 0 0.5em;
}

.cbp-ntaccordion .cbp-ntcontent p {
	color: #888;
	font-size: 1.25em;
	font-weight: 300;
	line-height: 1.5;
	padding: 0.2em 0 1.5em;
	margin: 0;
}

/* Arrow icons */
.cbp-ntaccordion > li > .cbp-nttrigger:before,
.cbp-ntsubaccordion > li > .cbp-nttrigger:before {
	font-family: 'icomoon';
	speak: none;
	font-weight: normal;
	font-variant: normal;
	text-transform: none;
	line-height: 1;
	color: #ddd;
	margin-right: 0.5em;
	-webkit-font-smoothing: antialiased;
}

.cbp-ntaccordion > li > .cbp-nttrigger:before {
	font-size: 75%;
}

.cbp-ntaccordion > li > .cbp-nttrigger:before {
	content: "36";
}
.cbp-ntaccordion > li > .cbp-nttrigger:hover:before {
	content: "35";
	color: inherit;
}
.cbp-ntaccordion > li.cbp-ntopen > .cbp-nttrigger:before,
.no-js .cbp-ntaccordion > li > .cbp-nttrigger:before {
	content: "34";
	color: inherit;
}

.cbp-ntsubaccordion > li > .cbp-nttrigger:before {
	content: "32";
}
.cbp-ntsubaccordion > li > .cbp-nttrigger:hover:before {
	content: "33";
	color: inherit;
}
.cbp-ntsubaccordion > li.cbp-ntopen > .cbp-nttrigger:before,
.no-js .cbp-ntsubaccordion > li > .cbp-nttrigger:before {
	content: "31";
	color: inherit;
}

/* Initial height is zero */
.cbp-ntaccordion .cbp-ntcontent {
	height: 0;
	overflow: hidden;
}

/* When open, set height to auto */
.cbp-ntaccordion .cbp-ntopen > .cbp-ntcontent,
.cbp-ntsubaccordion .cbp-ntopen > .cbp-ntcontent,
.no-js .cbp-ntaccordion .cbp-ntcontent {
	height: auto;
}

/* Example for media query */
@media screen and (max-width: 32em) { 

	.cbp-ntaccordion {
		font-size: 70%;
	}

}

Tiny break: 📬 Want to stay up to date with frontend and trends in web design? Subscribe and get our Collective newsletter twice a tweek.

The JavaScript

/**
 * jquery.cbpNTAccordion.js v1.0.0
 * http://www.codrops.com
 *
 * Licensed under the MIT license.
 * http://www.opensource.org/licenses/mit-license.php
 * 
 * Copyright 2013, Codrops
 * http://www.codrops.com
 */
;( function( $, window, undefined ) {

	'use strict';

	// global
	var $body = $( 'html, body' );

	$.CBPNTAccordion = function( options, element ) {
		this.$el = $( element );
		this._init( options );
	};

	// the options
	$.CBPNTAccordion.defaults = {};

	$.CBPNTAccordion.prototype = {
		_init : function( options ) {
			// options
			this.options = $.extend( true, {}, $.CBPNTAccordion.defaults, options );
			// cache some elements and initialize some variables
			this._config();
			// initialize/bind the events
			this._initEvents();
		},
		_config : function() {

			// the clickable items
			this.$items = this.$el.find( '.cbp-nttrigger' );

		},
		_initEvents : function() {
			
			this.$items.on( 'click.cbpNTAccordion', function() {
				var $listItem = $( this ).parent();
				if( $listItem.hasClass( 'cbp-ntopen' ) ) {
					$listItem.removeClass( 'cbp-ntopen' );
				}
				else {
					$listItem.addClass( 'cbp-ntopen' );
					$body.scrollTop( $listItem.offset().top );
				}
			} );

		},
		destroy : function() {
			this.$items.off( '.cbpNTAccordion' ).parent().removeClass( 'cbp-ntopen' );
		}
	};

	var logError = function( message ) {
		if ( window.console ) {
			window.console.error( message );
		}
	};

	$.fn.cbpNTAccordion = function( options ) {
		if ( typeof options === 'string' ) {
			var args = Array.prototype.slice.call( arguments, 1 );
			this.each(function() {
				var instance = $.data( this, 'cbpNTAccordion' );
				if ( !instance ) {
					logError( "cannot call methods on cbpNTAccordion prior to initialization; " +
					"attempted to call method '" + options + "'" );
					return;
				}
				if ( !$.isFunction( instance[options] ) || options.charAt(0) === "_" ) {
					logError( "no such method '" + options + "' for cbpNTAccordion instance" );
					return;
				}
				instance[ options ].apply( instance, args );
			});
		} 
		else {
			this.each(function() {	
				var instance = $.data( this, 'cbpNTAccordion' );
				if ( instance ) {
					instance._init();
				}
				else {
					instance = $.data( this, 'cbpNTAccordion', new $.CBPNTAccordion( options, this ) );
				}
			});
		}
		return this;
	};

} )( jQuery, window );

Manoela Ilic

Manoela is the main tinkerer at Codrops. With a background in coding and passion for all things design, she creates web experiments and keeps frontend professionals informed about the latest trends.

Stay in the loop: Get your dose of frontend twice a week

Fresh news, inspo, code demos, and UI animations—zero fluff, all quality. Make your Mondays and Thursdays creative!

Feedback 37

Comments are closed.
  1. Is there a way to avoid it moving the accordian that was just opened to the top of the page. It’s a bit confusing having the page move around like that. Most other accordians I have seen normally just drop the accordian downwards without scrolling the screen.

  2. Quick solution for all of those looking for an auto close function, just change the _initEvents function from
    _initEvents : function() { this.$items.on( 'click.cbpNTAccordion', function() { var $listItem = $( this ).parent(); if( $listItem.hasClass( 'cbp-ntopen' ) ) { $listItem.removeClass( 'cbp-ntopen' ); } else { $listItem.addClass( 'cbp-ntopen' ); $body.scrollTop( $listItem.offset().top ); } } ); },
    to
    _initEvents : function() { this.$items.on( 'click.cbpNTAccordion', function() { var $listItem = $( this ).parent(); $('.cbp-ntopen').removeClass('cbp-ntopen'); if( $listItem.hasClass( 'cbp-ntopen' ) ) { $listItem.removeClass( 'cbp-ntopen' ); } else { $listItem.addClass( 'cbp-ntopen' ); $body.scrollTop( $listItem.offset().top ); } } ); },

    • as if you didn’t change any other code, the add the following line within the click function

      $('.cbp-ntopen').removeClass('cbp-ntopen');

      I should remove the class that tells an accordion element to be open. If you send me a URL with it implemented I would be more than happy to see if I could debug it for you?

    • Hi Robert, I have also try this too, but it doesn’t work. It removes cbp-ntopen and makes the list unable to open. Thanks!

  3. For those who want auto close – replace the “_initEvents : function()”(line 41) with the following code .
    Do this in your “jquery.cbpNTAccordion.js” file and make sure you use THIS file in your “index.html” (instead of the minimized one “jquery.cbpNTAccordion.min.js”) !!!
    I think that is the reason why Roberts solution not worked for most of you. (BTW: Roberts solution do not work for sublistings and subsublistings)

    _initEvents : function() {

    this.$items.on( ‘click.cbpNTAccordion’, function() {
    var $listItem = $( this ).parent();

    if( $listItem.parent().hasClass( ‘cbp-ntsubaccordion’ ) && $listItem.hasClass( ‘cbp-ntopen’ )) {
    $listItem.removeClass( ‘cbp-ntopen’ );
    } else if( $listItem.parent().hasClass( ‘cbp-ntsubaccordion’ ) ) {
    $listItem.parent().children().removeClass(‘cbp-ntopen’);
    $listItem.addClass( ‘cbp-ntopen’ );
    } else if($listItem.hasClass( ‘cbp-ntopen’ )) {
    $listItem.removeClass( ‘cbp-ntopen’ );
    } else {
    $(‘.cbp-ntopen’).removeClass(‘cbp-ntopen’);
    $listItem.addClass( ‘cbp-ntopen’ );
    //$body.scrollTop( $listItem.offset().top );
    }
    } );

    },

    NOTE:
    – Minimize the js file afterwards for performance reasons.
    – I not a frontend dev so there might be more elegant ways to do this.
    – If you want auto scrolling – replace the last line:
    //$body.scrollTop( $listItem.offset().top );
    by:
    $body.scrollTop( $listItem.offset().top );

    • Hello Author,
      really liked ur script. but the code isn’t still working for auto close. I did try all of the above scripts but it isn’t working. !
      Can u help me out.. what might be the possible reason..

      Thank you. !!

  4. Quick research resultet in “smooth transition need a fixed height”:
    http://stackoverflow.com/questions/6089548/css3-height-transition-not-working

    If you got a fixed height you can replace the css from line 111 in component.css with:

    /* When open, set height to fixed hieght to get fancy transitions */
    .cbp-ntaccordion .cbp-ntopen > .cbp-ntcontent,
    .cbp-ntsubaccordion .cbp-ntopen > .cbp-ntcontent,
    .no-js .cbp-ntaccordion .cbp-ntcontent {
    height: 500px;
    overflow: hidden;
    -moz-transition: height 1s ease;
    -webkit-transition: height 1s ease;
    -o-transition: height 1s ease;
    transition: height 1s ease;
    }

    Otherwise you need js to get the scrollheight. May be a bit tricky because of nested elements.
    See here: http://jsfiddle.net/minitech/hTzt4/

  5. Hi,

    Can you please tell me how to add expand all and collapse all by clicking toggle button.

    Thanks
    Ashok. M

  6. Hi Author,

    i need first Accordion to be open by default.

    how to do that ?

    Thanks & Regards
    RoHiTh

  7. Hi Author,

    i need first Accordion to be open by default.

    how to do that ?

    Thanks & Regards
    Ram

  8. Add this code to your document and first accordion to be open by default works fine.

    $(document).ready(function () {
    $(‘#cbp-ntaccordion’).children(“li”).first().addClass(“cbp-ntopen”);
    });

    • Can you let me know where do you add those above 2 lines pls? I am not expert at this but manage by tweaking sometimes. Do you mean to insert these lines in function in index.html or anywhere in css?

  9. How to have the nested accordion effect on two different divs? For me its working only on one div and list is not at all opening on another div. Does this have anything to do with containers?

    • Just add another div in the header e.g.
      for the first div $( ‘#cbp-ntaccordion’1 ).cbpNTAccordion();
      for the second div $( ‘#cbp-ntaccordion2’ ).cbpNTAccordion();

  10. I was wondering if this accordion would accommodate large quantities of text like a complete manual. The idea would then be to use the different Heading Styles (heading 1, Heading 2, Heading 3) to display the initial content of the manual and to browse it from there. Would this work or is there a another way – don’t want to reinvent the wheel.

  11. It would be better to change
    this.$items.on( 'click.cbpNTAccordion', function(){})
    to
    this.$el.on( 'click.cbpNTAccordion','.cbp-nttrigger', function(){})

    if event binding of dynamic html content is needed.

  12. Hi
    I really like this accordion and would like to use it, but from what I can tell the arrow icons show up as numbers on the iphone and windows mobile, does anyone now how to alter it so the arrows show properly, or if there is a way to omit the arrows altogether on a mobile.
    thanks

    • Make sure you’re calling the font files properly, and that they’re uploaded into the appropriate folder. I uploaded mine into a folder called “webfonts”.

      @font-face {
      font-family: ‘icomoon’;
      src:url(‘../webfonts/icomoon_arrows/icomoon.eot’);
      src:url(‘../webfonts/icomoon_arrows/icomoon.eot?#iefix’) format(’embedded-opentype’);
      src:url(‘../webfonts/icomoon_arrows/icomoon.woff’) format(‘woff’);
      src:url(‘../webfonts/icomoon_arrows/icomoon.ttf’) format(‘truetype’);
      src:url(‘../webfonts/icomoon_arrows/icomoon.svg#icomoon’) format(‘svg’);
      font-weight: normal;
      font-style: normal;
      } /* Iconfont by Icomoon http://icomoon.io/ */

      Also, swap out all the commas in the original code with semi-colons, adding an “src:” in front of all the lines that have a url.

      Worked for me.

  13. I would love to figure out how to stop this from jumping to the top also. I have a fixed header and this is creating problems for me.

  14. Hi there,

    Love this accordion, but I was wondering if there’s a way to call a certain trigger. EG, have a list of 1 2 3 4 as the main content of the page, but then I would also like a side bar with 1 2 3 4 that will call it’s respective drop down in the main content.

    I have played about, but I’m not so hot with JS, so just wondering if anybody knew of a method?

    Thanks in advance, and thanks for sharing Mary!

    • Also trying to figure this out.

      Has anyone found a solution to this?

      Thanks!

  15. Hi!

    THis is a really good accordion. I was wondering if there is a way to close the all the rest of the accordeon tabs when theres one opened.

    Thanks!

  16. I wan to slow down the opening using “ease”. The bit of java script that goes at the bottom of the page does not allow this. I do not want to limit the size of the accordion which if set, does allow a css ease. I have generated content that does not fit into a one size fits all height.

    Would it be possible to add in the lower js a way to have options and the accordions to slowly ease open?

    Thank you Dane

  17. For those having issues with user provided code, be sure that when you copy and paste the code from a comment that you change the smart quotes ( ‘ ’ ) into straight single quotes. The comments automatically convert straight single quotes into smart quotes which is why your code isn’t working.