Flexible Slide-to-top Accordion

In today’s tutorial we’ll be creating a simple responsive accordion that, when opened, will slide to the top of the viewport and reveal the content by fading it in. The idea is to avoid that the user has to scroll the content area into place. We’ll also add some nice CSS3 transitions for the arrow to appear and to rotate when we click on an item. The accordion will be flexible, meaning that it will have a liquid width adjusting to the screen size.

In today’s tutorial we’ll be creating a simple responsive accordion that, when opened, will slide to the top of the viewport and reveal the content by fading it in. The idea is to avoid that the user has to scroll the content area into place. We’ll also add some nice CSS3 transitions for the arrow to appear and to rotate when we click on an item. The accordion will be flexible, meaning that it will have a liquid width adjusting to the screen size.

The beautiful fashion photography is by Rayand; check out his Flickr Photostream.

The Markup

The HTML structure will consist of a wrapper with the class and ID st-accordion and an unordered list. The list items will have a link element which will serve as the item title and a content area that is initially hidden. The span with the class st-arrow will be the little indicator on the right side that we will make appear when we hover over its parent, the link element.

<div id="st-accordion" class="st-accordion">
	<ul>
		<li>
			<a href="#">
				Item Title
				<span class="st-arrow">Open or Close</span>
			</a>
			<div class="st-content">
				<p>Some content</p>
			</div>
		</li>
		<li> ... </li>
	</ul>
</div>

Let’s take a look at the style.

The CSS

First, we will style the main wrapper. We will give it a width of 100 because we want it to adjust to the width of its surrounding wrapper. If you don’t have one, just use a suitable percentage here (if you want it to be liquid). The surrounding wrapper in the demo has a maximal width of 800 pixels and a width of 90%.
The st-accordion will have a minimal width of 270 pixels:

.st-accordion{
    width:100%;
    min-width:270px;
    margin: 0 auto;
}

Assuming that we have some kind of reset css that will remove paddings and margins from unordered lists etc., we define the style for each list element. We’ll set an initial height of 100 pixels which is basically the height of the link element and the overflow will be hidden, so we won’t see the content. When we open the item, we’ll animate its height in order to reveal the content. The borders that we are giving to the element will create a nice engraved separation between the items.

.st-accordion ul li{
    height: 100px;
    border-bottom: 1px solid #c7deef;
    border-top:1px solid #fff;
    overflow: hidden;
}

The first item should not have a top border:

.st-accordion ul li:first-child{
    border-top:none;
}

We’ll add a color transition to the link element which will create a nice effect on hover. The line-height should be the same like the initial height of the list element:

.st-accordion ul li > a{
    font-family: 'Josefin Slab',Georgia, serif;
    text-shadow: 1px 1px 1px #fff;
    font-size: 46px;
    display: block;
	position: relative;
    line-height: 100px;
	outline:none;
    -webkit-transition:  color 0.2s ease-in-out;
	-moz-transition:  color 0.2s ease-in-out;
	-o-transition:  color 0.2s ease-in-out;
	-ms-transition:  color 0.2s ease-in-out;
	transition:  color 0.2s ease-in-out;
}
.st-accordion ul li > a:hover{
    color: #1693eb;
}

The span for the arrow will be positioned absolutely and we’ll hide it by setting it outside of the link element and giving it an opacity of 0. The transition will be the item moving from the right and fading in:

.st-accordion ul li > a span{
	background: transparent url(../images/down.png) no-repeat center center;
	text-indent:-9000px;
	width: 26px;
	height: 14px;
	position: absolute;
	top: 50%;
	right: -26px;
	margin-top: -7px;
	opacity:0;
	-webkit-transition:  all 0.2s ease-in-out;
	-moz-transition:  all 0.2s ease-in-out;
	-o-transition:  all 0.2s ease-in-out;
	-ms-transition:  all 0.2s ease-in-out;
	transition:  all 0.2s ease-in-out;
}
.st-accordion ul li > a:hover span{
	opacity:1;
	right: 10px;
}

When we open an item, we will give it the class st-open and the link element will stay with the hover color. The span will be rotated to that the arrow stays pointing up and in sight:

.st-accordion ul li.st-open > a{
    color: #1693eb;
}
.st-accordion ul li.st-open > a span{
	-webkit-transform:rotate(180deg);
	-moz-transform:rotate(180deg);
    transform:rotate(180deg);
	right:10px;
	opacity:1;
}

Let’s style the content and its elements:

.st-content{
    padding: 5px 0px 30px 0px;
}
.st-content p{
    font-size:  16px;
    font-family:  Georgia, serif;
    font-style: italic;
    line-height:  28px;
    padding: 0px 4px 15px 4px; 
}
.st-content img{
    width:125px;
    border-right:1px solid #fff;
    border-bottom:1px solid #fff;
}

With a media query we will make sure that the font size of the item title will be smaller:

@media screen and (max-width: 320px){
	.st-accordion ul li > a{
		font-size:36px;
	}
}

And that’s all the style! Let’s move on to the JavaScript.

The JavaScript

Let’s look at the most important parts of this plugin. We’ll start by the default options:

$.Accordion.defaults 		= {
	// index of opened item. -1 means all are closed by default.
	open			: -1,
	// if set to true, only one item can be opened. 
	// Once one item is opened, any other that is 
	// opened will be closed first
	oneOpenedItem	: false,
	// speed of the open / close item animation
	speed			: 600,
	// easing of the open / close item animation
	easing			: 'easeInOutExpo',
	// speed of the scroll to action animation
	scrollSpeed		: 900,
	// easing of the scroll to action animation
	scrollEasing	: 'easeInOutExpo'
};

We initialize our plugin by calling the init function:

_init 				: function( options ) {
		
		this.options 		= $.extend( true, {}, $.Accordion.defaults, options );
		
		// validate options
		this._validate();
		
		// current is the index of the opened item
		this.current		= this.options.open;
		
		// hide the contents so we can fade it in afterwards
		this.$items.find('div.st-content').hide();
		
		// save original height and top of each item	
		this._saveDimValues();
		
		// if we want a default opened item...
		if( this.current != -1 )
			this._toggleItem( this.$items.eq( this.current ) );
		
		// initialize the events
		this._initEvents();
		
	},

The _saveDimValues function saves the original height and top of an item so that we know where we have to scroll when we open an item.

If we’ve set an item to be opened by default, we will call _toggleItem.
Then we initialize the events.

The _toggleItem function takes care of the two cases when clicking an item. Either we have the item already open, i.e. it has the class st-open, or it is closed. If it’s open, we’ll set the current to -1 and fade out the content while setting the item’s height to its original one. If the item we are clicking is closed, we’ll set the index of that item to be the current one, animate the height to fit the content and fade in the content. Then we scroll the window to the point that the clicked item stays at the top by calling the _scroll function:

_toggleItem			: function( $item ) {
	
	var $content = $item.find('div.st-content');
	
	( $item.hasClass( 'st-open' ) ) 
			
		? ( this.current = -1, $content.stop(true, true).fadeOut( this.options.speed ), $item.removeClass( 'st-open' ).stop().animate({
			height	: $item.data( 'originalHeight' )
		}, this.options.speed, this.options.easing ) )
		
		: ( this.current = $item.index(), $content.stop(true, true).fadeIn( this.options.speed ), $item.addClass( 'st-open' ).stop().animate({
			height	: $item.data( 'originalHeight' ) + $content.outerHeight( true )
		}, this.options.speed, this.options.easing ), this._scroll( this ) )

},

In the _initEvents function we initialize two events, clicking on an item and the window resize. When we click on an item we either open or close it calling the _toggleItem function and if we’ve set the option oneOpenedItem to true, we first close any opened item before opening the current one.

When the window gets resized we need to reset the original item values and the content’s height. We’ll also want to scroll the item to the top again.

_initEvents			: function() {
	
	var instance	= this;
	
	// open / close item
	this.$items.find('a:first').bind('click.accordion', function( event ) {
		
		var $item			= $(this).parent();
		
		// close any opened item if oneOpenedItem is true
		if( instance.options.oneOpenedItem && instance._isOpened() && instance.current!== $item.index() ) {
			
			instance._toggleItem( instance.$items.eq( instance.current ) );
		
		}
		
		// open / close item
		instance._toggleItem( $item );
		
		return false;
	
	});
	
	$(window).bind('smartresize.accordion', function( event ) {
		
		// reset original item values
		instance._saveDimValues();
	
		// reset the content's height of any item that is currently opened
		instance.$el.find('li.st-open').each( function() {
			
			var $this	= $(this);
			$this.css( 'height', $this.data( 'originalHeight' ) + $this.find('div.st-content').outerHeight( true ) );
		
		});
		
		// scroll to current
		if( instance._isOpened() )
		instance._scroll();
		
	});
	
},

These were the most important functions for this accordion.
I hope you like this simple accordion and find it useful!

Tagged with:

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 up to date with the latest web design and development news and relevant updates from Codrops.

Feedback 99

Comments are closed.
  1. Hi! I love this according but am running into a unique problem when I view this accordian on an iphone. For some reason, the content scrolls above the top of the screen and then bounces back down. Any ideas on how to fix this?

  2. Amazing stuff ! I really like it 🙂 Very easy to use and at the same time – very powerful thing! Excellent!

  3. Hi,

    Can someone tell me if i can use this script to create a navigation menu with multiple subitems?

    For example

    – Home
    – page 1
    – page 2
    – page 2a

    – Info

    Cause its a beautiful script

  4. this is beautifully designed. i’m trying to implement with a drupal site i am developing. does anyone or maybe do you @Mary Lou know how i can do this? do i need to create a new content type or something of the sort?

    any help would be much appreciated!

  5. Hi All,

    While trying to have the first item opened on load here is what I found out:

    on jquery.accordion.js, line 56
    openis set to -1 if changed to 0 it works fine.

  6. Hi Mary

    Love this! I have one question if its ok. The first item that I want to have open on the page load has a flexible height so it cant be read and opens at 0px. Is there a way to solve this that you know?

    Cheers!

  7. Hi there,

    Thanks for this amazing tutorial, it works really well. I do however have one question. Anyone had an issue where the headings on the accordion would be invisible on Google Chrome? I’m not exactly sure how to go about fixing it.

    Kind Regards.

  8. Hi Mary,

    This is probably one of the smoothest Ajax code out there.

    I like the attention to details (especially where the arrow goes up the moment you press on down).

  9. I found a bug in the autocollapse Demo. After I drag the browser smaller or bigger and click one of link elements, the header cannot locate in a right position. It is always out of browser. Any one has same experience? How can I fix it?

    Thanks a lot

  10. Hi, i am trying to find where can i disable the slide effect that push to the top every header i am open.Anyone knows?I just want to open the whole thing with the arrow effect and of course the slide down…
    Thnx

    • Hey,

      just change this:
      scrollTop : ( instance.options.oneOpenedItem ) ? instance.$items.eq( current ).data( 'offsetTop ) : instance.$items.eq( current ).offset().top
      to this:
      scrollTop : ( instance.options.oneOpenedItem ) ? instance.$items.eq( current ).data( '0' ) : instance.$items.eq( current ).offset().top

  11. Woow.. nice work.

    One question: I want use Ajax for fill each option. So how I can defined a call for a “function”? I’ve tried something like this:

    <a href=”#” rel=”nofollow”> blabblablabla

    and with jQuery…

    jQuery(‘.MYCLASS’).click(‘live’, function(){
    alert(‘test before write some stuff ajax’);
    });

    but doesn’t work. Please help…

  12. Thanks so much for this tutorial!
    The only snag I’ve run into is when I open, and then close a list item, it shrinks so that you can no longer see the word. Any idea why?

  13. You can ignore my last comment. After many hours of tinkering, I found that the culprit was a lousy margin!

  14. Hi Mary,
    a great example!
    I would also like to know, like some others.
    What is the method for defining a custom point from the top of an item to scroll to? Say 50px from the top of the opened item?
    I hope you find time to answer this question.

    greetings

    rico

  15. One more thing Mary, can you recommend any beginner javascript books?
    There’s so many I don’t know where to start.

    Greetings

    Rico

  16. Is there a way to put a CLOSE text link inside the “st-content” area? So that when the accordion expands open, a small CLOSE link appears at the bottom of that opened section (in addition to the top header link)? Can’t figure it out or if it’s even possible. Thx.

  17. Hey, first off, thanks for the great tutorial!!

    But I’m having a little problem with this. The title is moving down like 15px when being clicked. I can’t figure out what it could be.
    It’s on a test page I made. The links are in the work-section. Here’s the link: http://b2ebeatz.com/page/#
    I would appreciate if someone would help me!

    Thank you

    • I think i fixed it. Sorry for spamming. And again, thanks for the tutorial! I really appreiciate the work you do!!

  18. Good elegant code, but anyone knows how to reveal the content of the entire accordion for printing? I can’t find the method. Thank you.

  19. If someone has their screen flickr on Webkit browsers, use this property on the rotate stuff:

    -webkit-transform-style: preserve-3d;

  20. Hi and thanks for this great stuff!

    A question to all – how can the current opened item be linked and open – if the third section is called #item-3 is there a way to have it open if the address http://www.domain.com/index.html#item-3 is called from the address bar or linked from outside?

  21. Hi, thanks for this tutorial!

    I have the same problem of Nikita. I need to open the different items with links, anyone know how to do this ?

    Thanks

  22. @Kwong:

    I put in a close all button (when you click a link with a class “close”). Probably not the “best” solution, but it works:

    $(‘.close’).click(function() {

    $(‘.st-open’).animate({height: “90”, top: “200”}, “600”, function() {
    $(‘#st-accordion > li’).removeClass(‘st-open’);
    });
    });

    Still struggling with keyboard shortcuts.

  23. Hey there!

    Thanks for this wonderful tutorial, I have two quick questions I’d like to ask.

    1st. Is it possible to have another dropdown within the one that’s open?
    2nd. How can you disable the pull to top and stay there thing, so when I open the tab, it scrolls to the top, but lets me scroll down even when it’s still loading, at the moment, when I view the example on my iPhone, it forces me to wait for the tab to fully open before I can scroll, and won’t let me scroll anywhere until it does, I know it’s only a 1-2 second animation, but these things matter.

    Thanks for you’re time!

    • Hey Steven I’m with you. I’m trying to figure out how to nest a second accordion inside an already opened .st-content

      Let me know if you figured out a solution.

  24. hai mary . i want to use this beautifull accordion for my wordpress 3.3.1 website… can u guide me… ??
    Thanks in Advance..

  25. Hello Mary! First let me thank you for this awesome script. My problem is easing during opening on !first! click. It doesnt animate process of opening just opens content. On second click it works – it closes content with right behavior. After this it works as expected even opening…. I dont know how to solve this… Any ideas? Thank you…

    http://devsrv.at/honza/

    • Hey, I had the same issue.

      It’s related to the CSS. you need to have a height and line-height on the LI and line-height on the A.

      It worked for me! Good luck

  26. Hi! Thank you for this awesome tutorial!
    Is it possible to have the same effect but as horizontal accordion?

    Hope someone can help me!

  27. Wondering the same thing as Nikita and Sergio. Is there a way to link to a open section? I know you can set which section to have “open” in the js when you get to the page. Is there a way to put that into a link? Like, http://site.com/#section2 to take the user to the 2nd section or http://site.com/#section3 to open the 3rd section when landing from that link. Thanks so much for any help!!

    • I am looking to do the same thing, I really love the effect of this script but i cannot seem to figure out how to make this work. I can do it with the normal accordion with no problem. Can someone please assist on this as I have tried many many things with no joy.

  28. I love this script!
    Can anyone help me set an item to automatically open on page load?
    Thanks.

  29. I am trying to hijack the “height” function. I have a little function I wrote for a toggle inside .st-content

    Basically I want to recalculate the height of “.st-content” when I click “.albumTitle” and reveal the div “.hideInfo”

    Any thoughts?

    $(“.albumTitle”).click(function(){
    var info = $(this).siblings(“.hideInfo”);
    if (info.is(“:hidden”)) {
    info.slideDown();
    } else {
    info.slideUp();
    }
    });

    html —

    Music

    Album Title Here

    itunes
    amazon

    01. Track Name
    02. Track Name
    03. Track Name
    04. Track Name
    06. Track Name
    07. Track Name
    08. Track Name
    09. Track Name
    10. Track Name
    11. Track Name
    12. Track Name

    Release Date: Oct. 29, 2005
    Runtime: 43:17