Nested Accordion

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

Nested Accordion

View demo Download source

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%;
	}

}

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 );

View demo Download source

Previous:
Next:

Tagged with:

ML is a freelance web designer and developer with a passion for interaction design. She studied Cognitive Science and Computational Logic and has a weakness for the smell of freshly ground peppercorns.

View all contributions by

Website: http://www.codrops.com

Related Articles

CSS Reference

Learn about all important CSS properties from the basics with our extensive and easy-to-read CSS Reference.

It doesn't matter if you are a beginner or intermediate, start learning CSS now.

Feedback 37

  1. 3

    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. 7

    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 );
    }
    } );

    },

    • 9

      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?

    • 10

      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. 12

    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 );

    • 13

      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. 15

    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. 16

    Hi,

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

    Thanks
    Ashok. M

  6. 20

    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”);
    });

    • 21

      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?

  7. 23

    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?

    • 24

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

  8. 25

    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.

  9. 26

    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.

  10. 27

    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

    • 28

      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.

Follow this discussion

Leave a Comment