While you've accepted a JavaScript solution, there are (at least) two ways that this can be achieved with CSS alone, the first using CSS :target
pseudo-classes, and the second using input
, and label
, elements.
The first, assuming HTML similar to the following:
<div id="faq_container">
<ol>
<li id="faq1">
<h2><a href="#faq1">Question 1</a></h2>
<div>
<p>Text, relating to question one.</p> <a class="close" href="#hide">close</a>
<!-- the above link doesn't link to anything, just changes the hash whcih stops the ':target' pseudo-class matching the the current 'div' element -->
</div>
</li>
<!-- subsequent elements follow the above structure, stripped for brevity -->
</ol>
</div>
With the following CSS (albeit there's more CSS in the demo, since I've stripped out some of the purely aesthetic stuff here, for brevity, as above):
li {
/* some stripped out aesthetics */
position: relative; /* used to position the '.close' links */
}
li div {
height: 0; /* to allow for animation of the height 'none' to 'block' can't animate */
overflow: hidden;
/* all vendor prefixes removed for brevity, here and later */
transition: all 0.5s linear; /* animates to the default properties, from other 'states' */
}
/* li:target matches when the 'id' of the 'li' is equal to the hash/fragment-identifier in the URL */
li:target div {
height: 4em; /* to allow for animation (this is the awkward part of using pure CSS) */
transition: all 0.5s linear; /* transitions to the 'selected' state (when the user clicks a link in the 'h2' element) */
}
li a:link, li a:visited {
/* aesthetics removed */
}
/* styling the 'interactive' states (:hover, :active, :focus), and the 'selected' state using 'li:target h2 a' */
li a:hover, li a:active, li a:focus, li:target h2 a {
font-weight: bold;
text-decoration: underline;
}
a.close {
/* styling the '.close' link, so it's invisible in the 'non-selected' state */
position: absolute;
opacity: 0;
width: 0;
overflow: hidden;
transition: all 0.65s linear;
}
/* styling the '.close' link, so it's only visible when the question is 'selected' */
li:target a.close {
opacity: 1;
width: 4em;
transition: all 0.65s linear;
}
JS Fiddle demo.
The second approach uses label
and input
elements (type="radio"
if only one question can be visible at a time, type="checkbox"
if multiple elements can be visible), based on the following HTML:
<input id="close" name="question" type="radio" />
<div id="faq_container">
<ol>
<li>
<input id="faq1" type="radio" name="question" />
<h2><label for="faq1">Question 1</label></h2>
<div>
<div>
<p>Text, relating to question one.</p>
<label for="close">Close</label>
<!-- the above 'label' closes the question, by referring to an
'input' of the same name (different 'id'), taking advantage
of the fact that only one radio-'input' of a given name can
be checked (this 'input' is just before the ancestor 'ol') -->
</div>
</div>
</li>
<!-- subsequent elements follow the above structure, stripped for brevity -->
</ol>
</div>
And the following CSS (as before, aesthetics removed for brevity):
/* you could, instead, use a class-name to identify the relevant radio-inputs */
input[type=radio] {
/* using 'display: none' (apparently) in some browsers prevents
interactivity, so we fake it, by hiding: */
position: absolute;
opacity: 0;
left: -1000px;
}
/* styling the 'div' that's the adjacent-sibling of an 'h2' which is an
adjacent-sibling of an 'input' all of which are descendants of a 'div' */
div input + h2 + div {
height: 0; /* to allow for animating with transitions */
overflow: hidden;
/* vendor prefixes, again, stripped out */
transition: all 0.5s linear;
}
/* using 'input:checked + h2 + div' causes problems in Chrome, check the references;
so we're styling (respectively) a 'div' which is an adjacent sibling to an 'h2'
which is an adjacent-sibling of a checked 'input', and/or
a 'div' which is a general-sibling of a checked 'input' (in both cases these are
all descendants of another 'div' element) */
div input:checked + h2 + div,
div input:checked ~ div {
height: 4em; /* to allow for animating with transitions */
overflow-y: auto; /* a personal preference, but allows for
scrolling if the height is insufficient
though it can be a little ugly, with a flicker */
transition: all 0.5s linear;
}
JS Fiddle demo.
The same approach can be used with checkboxes, which allows the label
to toggle the display of the relevant question, and makes the close
links/labels pointless, HTML:
<div id="faq_container">
<ol>
<li>
<input id="faq1" type="checkbox" name="question" />
<h2><label for="faq1">Question 1</label></h2>
<div>
<div>
<p>Text, relating to question one.</p>
</div>
</div>
</li>
<!-- subsequent elements follow the above structure, stripped for brevity -->
</ol>
</div>
And CSS (precisely as the preceding example, but changed input[type=radio]
to input[type=checkbox]
):
/* duplicated, and aesthetic, CSS removed for brevity */
input[type=checkbox] {
position: absolute;
opacity: 0;
left: -1000px;
}
JS Fiddle demo.
References: