Accordions

jQuery Accordian UI

This illustrates an accordion with variable height, based on content.

jQuery's accordion object needs 'autoHeight' set to 'false', and 'heightStyle' set to 'content'.

jQuery accordion class initialized with autoHeight: false and heightStyle: 'content'

Section 1

Mauris mauris ante, blandit et, ultrices a, suscipit eget, quam. Integer ut neque. Vivamus nisi metus, molestie vel, gravida in, condimentum sit amet, nunc. Nam a nibh. Donec suscipit eros. Nam mi. Proin viverra leo ut odio. Curabitur malesuada. Vestibulum a velit eu ante scelerisque vulputate.

Section 2

This is the longest section of them all.

Nam enim risus, molestie et, porta ac, aliquam ac, risus. Quisque lobortis. Phasellus pellentesque purus in massa. Aenean in pede. Phasellus ac libero ac tellus pellentesque semper. Sed ac felis. Sed commodo, magna quis lacinia ornare, quam ante aliquam nisi, eu iaculis leo purus venenatis dui.

  • List item one
  • List item two
  • List item three

Section 3

Sed non urna. Donec et ante. Phasellus eu ligula. Vestibulum sit amet purus. Vivamus hendrerit, dolor at aliquet laoreet, mauris turpis porttitor velit, faucibus interdum tellus libero ac justo. Vivamus non quam. In suscipit faucibus urna.

Section 4

Cras dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean lacinia mauris vel est.

Suspendisse eu nisl. Nullam ut libero. Integer dignissim consequat lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.

CSS accordion

Transition effects can get you part of the way there, but 3 things CSS can't do:

init

The browser needs the original height for the height transition (used when DIVs are not absolutely positioned).
Javascript saves the original heights of all content DIVs this way:

  • create an array of all item elements (H3 by default) in #css-accordion using .getElementsByTagName(itemTag);
  • loop through the array to find each content element (using .nextElementSibling)
  • assign the element an ID
  • save the element's height in an object with its ID as the key
  • set the element's height to '0'

Event listeners are added to all the A elements, so changeState() can handle click events.
Browsers pass an event object to the handler as its first parameter. Useful event properties include:

  • .target the element that triggered the event
  • .preventDefault() cancel normal behavior, similar to onclick="return false"
  • .type name of the event, e.g.: 'click'
  • .cancelBubble set or return whether event will propogate to parents
  • .stopPropagation() prevent parent events from firing
items

When the #css-accordion container has an "absolute" class, it is position:relative, allowing item elements inside it to slide against its right edge (using the 'left' property calculated as 100% - the element's width).

The changeState() method adds a 'css-active' class to the item element when its child A is clicked.

absolute

CSS positions even children of #css-accordion.absolute at top:0, left:0.
This works because #css-accordion.absolute is position:relative.
Although the layout moves them around, in the DOM these are adjacent to the odd children.
So the content element to expand can be found via A's .parentNode.nextElementSibling property.

Initial properties for content elements properties include:

  • overflow:hidden
  • opacity:0
  • transition:opacity .5s

When an A is clicked, changeState() sets the appropriate parent to 'css-active'.
CSS uses *:nth-child(odd).css-active + * to define the element following 'css-active' as opaque.
This rule causes the browser to render the transition from opacity:0 to 1.

static

When the 'absolute' class is removed, content elements revert to the default 'position:static'. Because they are 'overflow:hidden', subsequent A tags in any following item elements remain clickable.

CSS defines a transition rule for 'height'. NB: The browser still honors what CSS defines in the transition, even though javascript, not a CSS rule, alters the height.

From the above, it's clear that merely CSS defines how the browser should treat various states of the DOM. How those states are achieved depends on code reacting to user behavior and browser events.

toggle

The 'toggle absolute' button just toggles a class named 'absolute' on the #css-accordion container.
CSS rules define how the browser should respond.

notes

The init() method defaults to H3 as the element marking item elements, but it can receive a different block-level tag as a parameter. In this version, DL is the #css-accordion wrapper, so init() is passed 'DT' as the tag marking items.

CSS selects elements based on position, using #css-accordion > *:nth-child(odd) and (even) to distinguish item/content elements. Any alternating elements, then, can be used in the markup, so long as init() is passed the tag marking item elements.

However, lists like UL and OL are still a problem, since HTML only allows LI elments as siblings. Item/content pairs would need to be wrapped in a single LI, thus becoming grandchildren to the containing UL or OL. That, or odd/even LI would become item/content elements, which would be semantically ugly.

One way around this would be to flag item elements with a class, rather than have CSS select a particular element. In that way, a single LI could contain 2 child siblings, the first marked with, say, class="ac-item".

The drawback is the extra class attributes you'd need to sprinkle in the markup. It might be a nuisance to override browser defaults for lists, too, though lists seem more defensible semantically.

This paragraph tests resizing the #css-accordion container. When its DIVs are absolutely positioned, they shouldn't overlap this text when they're long, and this shouldn't encroach on the H3s when they're short.