Creating a semantic FAQ page with definition lists and advanced CSS, Part 2

And now for a more interesting layout of the FAQ page with CSS

Usually the FAQ page is a long text with lots of questions and answers. This page is looked for when you need more information on how the site works. In this tutorial we will make a stylish FAQ page using CSS3 only, without using JavaScript.

Страница FAQ

Idea

Help Center Facebook uses a great effect to preview the answer to a question. It displays an excerpt in small, darkened font. And when the visitor clicks on the question, the answer text is enlarged and displayed in full.

In our tutorial, we will create a similar effect using only CSS3.

HTML

Let’s start by marking up the structure of the document:

<section class="faq-section">

    <input type="checkbox" id="q1">
    <label for="q1">Question?</label>
    <p>... An introductory paragraph that will be trimmed ...</p>
    <p>... Additional Information ...</p>
</section>
Структура ответа         на вопрос

In the figure above you can see that the label will be the header of the section. But you can put the label in h1 if you want to follow the rules of semantics.

Use label::before to create the triangle on the right.

The first paragraph of each section will be converted into a descriptive answer passage to the question.

Principle of operation

No rocket technology is used here. There is a checkbox trick involved here to bind the elements  <input type="checkbox" id="abc"> и <label for="abc">. And the checkbox itself is hidden from the eyes of the page reader.

CSS

/*Adding fields*/
.faq-section{
        margin: 40px 0;
}
/*Hiding checkboxes and paragraphs*/
.faq-section input,
.faq-section p{
        display: none;
}
/*Showing only the trimmed introduction */
.faq-section label+p{
        display: block;
        color: #999;
        font-size: .85em;
        transition: all .15s ease-out;
        /* Clipping text */
        text-overflow: ellipsis;
        white-space: nowrap;
        overflow: hidden;
}

/*If the checkbox is checked, we show all paragraphs*/
.faq-section input[type=checkbox]:checked~p{
        display: block;
        color: #444;
        font-size: 1em;
        /* Restoring the default clipping */
        text-overflow: clip;
        white-space: normal;
        overflow: visible;
}
/*Styles for the label*/
.faq-section label{
        font-size: 1.2em;
        cursor: pointer;
        background: #eee;
        display: block;
        position: relative;
        padding: 7px 10px;
        font-weight: bold;
        border: 1px solid #ddd;
        border-left: 3px solid #888;
        text-shadow: 0 1px 0 rgba(255,255,255,.5);
        transition: all .15s ease-out;
}
/*Removing text selection when switching*/
.faq-section label::selection{
        background: none;
}
.faq-section label:hover{
        background: #f5f5f5;
}
/*If the checkbox is checked, set the styles accordingly*/
.faq-section input[type=checkbox]:checked~label{
        border-color: #ff7f50;
        background: #f5deb4;
        background-image: linear-gradient(to bottom, #fff, #f5deb4);
        box-shadow: 0 0 1px rgba(0,0,0,.4);
}
/*Label arrow - default state*/
.faq-section label::before{
        content: '';
        position: absolute;
        right: 4px;
        top: 50%;
        margin-top: -6px;
        border: 6px solid transparent;
        border-left-color: inherit;
}
/*Update the arrow on the right*/
.faq-section input[type=checkbox]:checked~label::before{
        border: 6px solid transparent;
        border-top-color: inherit;
        margin-top: -3px;
        right: 10px;
}

Browser support

How about displaying our page in older browsers? That’s a great question. And the answer is an acceptable degradation in appearance:

Вид в старых         браузерах

With the following code, we take care of browsers IE8 and older to preserve our page content for visitors using older tools.

<!--[if lt IE 9]>
02
    <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
03
    <style>
04
                .faq-section label,
05
                .faq-section label:hover{
06
                        cursor: default;
07
                        background: #eee;
08
                }
09
                body .faq-section p{ /* Increase specificity */
10
                        display: block;
11
                        color: #444;
12
                        font-size: 1em;
13
                        text-overflow: clip;
14
                        white-space: normal;
15
                        overflow: visible;
16
                }
17
    </style>
18
<![endif]-->

Toggling content on and off using JavaScript

Another way to do this would be to keep the answers (dd’s) hidden until the user clicks on the question (dt). Clicking on the dt would dynamically show the answer to the question. Using JavaScript is always a little bit risky, but this would provide a much cleaner interface. Let’s try it.

First we need a script that will hide and show stuff. We could wrap all of our questions in links that would call a JavaScript function to show the answer. But we don’t want to have to go through the whole document and add those in. It could get messy, and there are many opportunities to make mistakes. Instead, what we want to do is call a function when the dt tags are clicked on that would display the dd’s.

The first thing we are going to do is create some classes in our CSS to make things turn on and off. We could do this directly in the JavaScript but this way we won’t have to repeat it and we can re-use the class whenever we want. Put this into your CSS:

.hide {display: none;}
.show {display: block;}

Now we can start the JavaScript. In a new .js file, we will start by finding our questions and answers and puting them into two variables. To do this we will use the getElementsByTagName property. This will put all of the elements in an array (remember that, we’ll need to know that later).

var questions = document.getElementsByTagName('dt');
var answers = document.getElementsByTagName('dd');

Before we forget, we need to put in a link to our new .js file from our HTML page:

<script type="text/JavaScript" src="faq/toggle.js"></script>

Next we will write a function to go through the questions and turn the corresponding answer on when the question is clicked. The function will loop through all the questions (from the questions array variable we already defined), set an onclick handler for each question, and apply a function that will apply the .hide and .show classes to toggle the answer on and off. I have modified this code from BonRouge’s Unobtrusive javascript toggle scripts. It looks like this:

function displayToggle(){
     for (i=0; i<questions.length; i++) { // loops through the questions

        questions[i].onclick=function() { // displays the question 
            on clickvar next = this.nextSibling;
            // if it gets to a non-element node, go to the next 
            while(next.nodeType != 1) next=next.nextSibling; 
            onenext.className=((next.className=="hide") ? "show" : "hide");
         }
      }
}

We want to call this function when the page loads, so we need to add in the following code:

window.onload=function() {displayToggle();}

Great, this does almost everything we need it to. The problem we have now is that all the answers are displayed at the start, and we want them to be hidden. We also want to add some links to display all the answers and hide them all again. I’m mentioning this now because we are going to use the same functions to do both.

The functions we need will take all the answers and turn them on and off all at once. To do this, we will use the answers variable we defined earlier, loop through the array, and set some functions to put in our .show and .hide classes.

Here is the code:

// function for the link that turns them all off
function toggleAllOff(){
     for (var i = 0; i < answers.length; i++) {
        answers[i].className = 'hide';
     }
}
        
// function for the link that turns them all on
function toggleAllOn(){
     for (var i = 0; i < answers.length; i++) {
        answers[i].className = 'show';
     }
}

Now we can put two links in our faq page to turn all the definitions on and off:

<a href="#" onclick="JavaScript:toggleAllOn();">Show all answers</a>
<a href="#" onclick="JavaScript:toggleAllOff();">Hide all answers</a>

Check it out! Hide all the answers, show all the answers. To start the page with all answers turned off, we just need to call the toggleAllOff function in the displayToggle funcction that we’re calling when the page loads, so we need to add one more line to our displayToggle function:

function displayToggle(){
     // calls the toggle all off function 
     // to turn all the answers off when the page is loaded     
     toggleAllOff(); 

     for (i=0; i<questions.length; i++) {  // loops through the questions
         questions[i].onclick=function() {  // shows the answers onclick
             var next = this.nextSibling;
             // if it gets to a non-element node, go to the next one
             while(next.nodeType != 1) next=next.nextSibling; 
             next.className=((next.className=="hide") ? "show" : "hide");
          }
      }
}

Ta-da! A collapsible FAQ page that degrades gracefully and uses proper web standards!

There are just a few more things I need to do to the CSS to make this page look a little nicer. I’ll make my letter smaller, and add some padding around the section headings so they don’t run into the definition lists.

Here is the final result and here is the page in actual use. In the latter example I modified the JavaScript to make the list sections collapsible rather than just the individual questions.

Share