Chapter 7
A Filter Form

In the introduction to “A Search Form” you’ll recall the type of conversation I used to have with Mum. Sometimes I would ask, “Where’s my black top?” But this was so vague that Mum would respond with questions like, “Is it a football or tennis top?” This question is a filter on a large set of results. Without knowing the answer to this question, Mum couldn’t respond with an accurate answer.

Filters (also referred to as facet navigation or guided navigation) let users refine a large set of search results. This helps users home in on what they’re looking for.

First of all, though, it must be said that if you don’t need a filter, don’t include one. They’re only useful if searching returns a vast amount of results. On Google, where searching can yield thousands, if not millions of results, most people aren’t willing to click beyond the first or second page.

Letting users filter out irrelevant results is important. The ability to filter not only offers an additional dimension of control, but it does so in a way that matches each user’s own mental model. In “ Designing for Faceted Search ,” 1 Stephanie Lemieux says:

“Think of a cookbook: authors have to organize the recipes in one way only — by course or by main ingredient — and users have to work with whatever choice of organizing principle that has been made, regardless of how that fits their particular style of searching. An online recipe site using faceted search can allow users to decide how they’d like to navigate to a specific recipe [by course type, cuisine or cooking method, for example].”

At first glance, filters might look similar across different sites, but their behavior varies quite widely. When and how filters should be applied, how to denote selected filters, what elements should be used, how to give users feedback, how they’ll work on mobile and desktop: all of these things need to be taken into account.

Interactive Filters versus Batch Filters

There are two ways to let users filter: one at a time (interactive filtering), or selecting multiple filters at once (batch filtering). In “ User Intent Affects Filter Design ,” 2 Katie Sherwin describes an excellent way to think about this:

“[…] think about how you might order appetizers at a restaurant. Say you want to order three appetizers for the table, but as soon as you name the first one, the waiter snatches the menu out of your hands and walks back to the kitchen to get the chefs started on cooking that dish. Instead, a good waiter understands that you’re still in the process of ordering and knows to give you more time before taking away the menu. A good waiter allows you time to make a batch decision , even if that might slightly delay the delivery of the first item ordered. (However, sometimes the waiter may take the appetizer order, and then give you more time to decide on the main course. A good waiter is flexible and adapts to the needs of the customers.)”

Interactive Filters

Interactive filters update as soon as the user clicks a filter. The advantage is that users will see the results update as they go.

Left: an interactive filter with no filters selected. Right: the same, but with the red filter selected.
Left: an interactive filter with no filters selected. Right: the same, but with the red filter selected.

One disadvantage is that each click causes a page refresh, which can cause frustration due to lag and getting scrolled back to the top of the page — something that will happen every time the user selects a filter. This is especially problematic for keyboard users as they’ll have to tab back to where they were.

Batch Filters

Batch filters work by letting users set a number of options before submitting and reloading the page (see above). One advantage of this approach is that it’s faster, as users just make one request for several filters.

Left: a batch filter with filters about to be submitted. Right: the page with the filters applied.
Left: a batch filter with filters about to be submitted. Right: the page with the filters applied.

One disadvantage of this approach is that a combination of filters could lead to zero results.

Materially Dishonest Interfaces

We’ve already discussed the concept of material honesty several times in the book. This is because, rather unfortunately, dishonest interfaces are prevalent on the web. As a reminder, one material shouldn’t be used as a substitute for another because the end result is deceptive. In short, we should use the right material for the job. But what does this mean for filters?

As shown above, interactive filters tend to use links because they provide the expected behavior — that clicking a filter would immediately request the new page of results. But some sites style links to look like checkboxes by using CSS background images 3 , for example. But checkboxes are for input, not for requesting new pages.

Links styled as checkboxes.
Links styled as checkboxes.

The problem is that a link should look and behave like a link, not a checkbox. Batch filters, made from real checkboxes, let users select several filters. Making links look like checkboxes means users wouldn’t expect clicking a filter would immediately request the new results. That’s materially dishonest and therefore deceptive.

Why would designers do this? One possibility is that without making the links look like checkboxes, users wouldn’t know that they could choose multiple filters within the same category. Or, in the case of radio buttons, they wouldn’t know they could choose only one filter.

Breaking widely understood conventions without a very good reason can seriously harm the user experience. That’s all well and good as a theory, but how do we take this information and design a filter that works?

We’ll find out by taking things step by step.

Which Type of Filter Is Best?

Only conducting your own research for your problem can tell you the answer to that, but we’ll go with batch filters. This seems prudent because:

Users can choose multiple filters at once creating a faster experience.

Radio buttons and checkboxes have natural signifiers that indicate that one or multiple filters can be selected within the same category.

Users can still, if they wish, select just one filter at a time with a batch filter, which speaks to inclusive design principle 4, “Give control.”

Layout

Before tackling the complexity of the filter form itself, it’s important to look at it in the context in which users are likely to use it. In modular design, we can fall prey to focusing so deeply on the individual components that we forget to check how everything works when they combine to form the page (or journey).

The wallets category page as seen on desktop, with filters on the left and results on the right.
The wallets category page as seen on desktop, with filters on the left and results on the right.

In this case, the page has been carefully arranged so the relationship between the filter and the products is clear. This has been achieved by using the correct heading levels:

1. The primary heading (level 1) is at the top: “Wallets.”

2. Then there is a subheading (level 2) for each component: “Filter” and “Products.”

We’ve married the correct heading hierarchy semantically (as we’ll see in the code to follow) with how they’re sized and positioned visually . That is, the top-level heading comes first and is the biggest. The second-level headings come after and are smaller. This is important because not only does the filter let users find products more quickly, but it also provides context for the products in view.

The Markup

As the form is made from standard form components, the code for our filter form should be familiar.




<
main
>

  

<
h1
>

Wallets

</
h1
>

  

<
aside
 class

=
"filter"
 aria-labelledby

=
"filter-heading"
>

    

<
h2
 id

=
"filter-heading"
>

Filters

</
h2
>

    

<
form
 role

=
"form"
 method

=
"get"
 aria-labelledby

=
"filter-heading?"
>

    

<
fieldset
 class

=
"field"
>

        

<
legend
>

          

<
span
 class

=
"field-legend"
>

Color

</
span
>

        

</
legend
>

        

<
div
 class

=
"field-options"
>

          

<
div
 class

=
"field-checkbox"
>

          

<
label
 for

=
"color"
>

              

<
input
 type

=
"checkbox"
 name

=
"color"
 value

=
"green"
 id

=
"color"
>

              Green
          

</
label
>

          

</
div
>

          

<
div
 class

=
"field-checkbox"
>

          

<
label
 for

=
"color1"
>

              

<
input
 type

=
"checkbox"
 name

=
"color"
 value

=
"red"
 id

=
"color1"
>

              Red
          

</
label
>

          

</
div
>

          <!-- more checkboxes -->
      

</
div
>

    

</
fieldset
>

    

<
fieldset
 class

=
"field"
>

        

<
legend
>

          

<
span
 class

=
"field-legend"
>

Rating

</
span
>

        

</
legend
>

        

<
div
 class

=
"field-options"
>

          

<
div
 class

=
"field-radioButton"
>

            

<
label
 for

=
"rating"
>

              

<
input
 type

=
"radio"
 name

=
"rating"
 value

=
"4"
 id

=
"rating"
>

              4 stars and up
          

</
label
>

          

</
div
>

          

<
div
 class

=
"field-radioButton"
>

            

<
label
 for

=
"rating1"
>

              

<
input
 type

=
"radio"
 name

=
"rating"
 value

=
"3"
 id

=
"rating1"
>

              3 stars and up
          

</
label
>

          

</
div
>

          <!-- more radio buttons -->
      

</
div
>

    

</
fieldset
>

    <!-- other filter categories -->
    

<
input
 type

=
"submit"
 value

=
"Apply filters"
>

    

</
form
>

  

</
aside
>

  

<
div
 class

=
"results"
>

    

<
h2
>

Products

</
h2
>

    <!-- products -->
  

</
div
>



</
main
>


Notes

There are three headings on the page: the top level (<h1>Wallets</h1> ) and a level-two heading for the filter and products components. Many sites provide an incomplete and broken heading structure — for example, by replacing <h2>Products</h2> with <h1>Wallets</h1> . However, this orphans the <h2>Filter</h2> , which deceives both sighted and non-sighted users because users expect that a second-level heading comes after the first.

The specification has recently changed to allow headings inside legend elements. We could, then, consider marking up the text inside the legends as <h3> s. This would give screen reader users an alternative way to navigate the filter, which is sometimes called multimodality and speaks to inclusive design principle 5, “Offer choice.”

Notice the form has a role="form" attribute. This may seem counterintuitive, but it turns the form into a landmark, which makes it navigable in screen readers using shortcuts. Since the basic functionality works without JavaScript and triggers a page refresh, this helps users navigate back to the form from the top of the document. It also means users can browse the products and still get back to the filter component quickly.

Similarly, the filter is marked up as an <aside> , which is another type of landmark, typically used to denote a sidebar. An aside should be tangentially related to the main content, which suits the filter component well.

We’re using the GET method on the form to rebuild the page from the server without relying on client-side JavaScript at this stage. For example, submitting the form with the “Red” and “3 stars and above” options selected will build a page with ?color=red&rating=3 as the query parameter. We’ll look at enhancing the page with Ajax later.

The form contains a number of fields which have been included for the purpose of example. The type of form control you use should be based on the type of behavior your users need. As noted in previous chapters, checkboxes should be used for multiple selections; radio buttons if only one can be selected.

Keyboard users can press the arrow keys (left and right, up and down) to select a radio button. Pressing the down arrow key, for instance, will focus and select the next radio button with the name="rating" attribute: “3 stars and up.” In screen readers this will announce “rating, three stars and up, selected, radio button, two of four” (or similar).

Automatic Submission

As noted above, the filter form (like any other form, I might add) lets users select as many filters as they like before submitting them. This standard and conventional behavior should be familiar to users — except this is not always the case.

I interviewed Dave House, a former designer for Gumtree, a site which uses filters extensively. Dave and his team conducted many rounds of usability tests and here’s what he said:

“On desktop, Gumtree users would select filters without submitting them. They didn’t expect to have to submit their choices. We heard a lot of feedback saying ‘Your filters are broken.’”

Owing to the materially dishonest design of filters as explained earlier, it seems some people have come to expect that clicking a checkbox (or radio button) will reload the results without having to submit. This phenomenon is known as Jakob’s law 4 :

“Users spend most of their time on other sites. This means that users prefer your site to work the same way as all the other sites they already know.”

Automatically submitting the form when a filter is selected would effectively convert our batch filter into an interactive one. That’s a shame, as not only would we be exacerbating the problem of dishonest design, but we’d be forgoing the inherent advantages of batch filters.

And this may work for radio buttons and checkboxes, but what if there were text boxes that could be used to enter a price range? When would users expect the form to submit? Submitting while typing is out of the question. This leaves submitting the form on blur (tabbing or clicking out of the field), which is odd and unintuitive. We’d need a submit button just for that box.

If your users have the same expectations as Gumtree’s users, then you may have no choice. But before going to such lengths, let’s explore some other techniques to help users realize that submission is necessary.

First, the button should look like a button and be styled prominently to stand out on the page. And second, the button should be within easy reach (within reason). Some options include:

Making each filter category collapsible (we’ll look at how to do this later).

Duplicate the submit button at the top of the form.

Consider making the buttons stay on screen by using position: sticky 5 .

Should We Just Change to Links?

Perhaps we should just throw away the form and use links. The advantage would be we wouldn’t need JavaScript — users would get standard (link) behavior for free.

But there are several disadvantages:

We can’t use links for the dynamic price range inputs, for example, which is unnecessarily constraining.

We may need to signify the “select one” or “select multiple” behavior across different link filters, which is challenging without making links look like checkboxes (here we go again).

If users select many filters then they may exceed the max limit of the query string. Posting a form averts this problem.

Really, we’re exchanging one set of problems for another, arguably larger set. Let’s see how we can submit the form automatically using JavaScript and the issues that may arise from doing so.

Submitting the Form Automatically

If, despite our efforts to thwart breaking convention, users still expect the form to submit automatically, we can use JavaScript to submit the form in response to the form’s change event.

As our filter only contains checkboxes and radio buttons, we can first remove the now redundant submit button. However, we can’t completely remove the button from the document (with display: none , for example) because some platforms (iOS for one) will not submit forms when a submit button isn’t present. And, as mentioned in chapter 6, omitting the submit button stops users being able to submit the form implicitly, with the Enter key. In which case, we can use our special visually-hidden class () , plus tabindex="-1" to make sure the button isn’t user-focusable.


function
 FilterRequester(
)
 {
  this.
form =
 $(
'.filter form'
)
;
  this.
form.
find(
'[type=submit]'
)
    .
addClass(
'visually-hidden'
)
    .
attr(
'tabindex'
,
 '-1'
)
;
}

Now the submit button has been hidden accessibly, we can submit the form when a filter is changed by listening to the change event:


function
 FilterRequester(
)
 {
  //...

  this.
form.
find(
'[type=radio], [type=checkbox]'
)
.
on(
'change'
,
 $.
proxy(
this,
 'onInputChange'
)
)
;
}

FilterRequester.
prototype.
onInputChange =
 function
(
)
 {
  this.
form.
submit(
)
;
}
;

You should note that this fails Web Content Accessibility Guidelines Success Criterion 3.2.2 6 :

“Changing the setting of any user interface component does not automatically cause a change of context”

Additionally, keyboard users operating the filters must use their arrow keys to move through the radio buttons. Each arrow keypress not only focuses adjacent radio buttons, but also selects them. As a result, keyboard users won’t be able to move from one radio button to another without the form being submitted. What if they want the third or fourth radio button?

Even if you ignore the difficulties associated with certain interaction modalities, having the page refresh in the middle of choosing filters is a poor user experience. Let’s see if Ajax can fix these issues.

Ajax

Ajax is a technology that lets users dynamically update parts of an interface without a page refresh. The advantage for our filter form is that users can select as many filters as they like without being interrupted by a page refresh and the focus moving to the top of the document.

To do this, we can listen to the change event and fire off an Ajax request:

FilterRequester.
prototype.
onInputChange =
 function
(
)
 {
    var
 data =
 this.
form.
serialize(
)
;
    this.
requestResults(
data)
;
  }
;
 
  FilterRequester.
prototype.
requestResults =
 function
(
data)
 {
    $.
ajax(
{
 data:
 data,
 .
.
.
}
)
;
  }

While the main issue has been solved, the introduction of Ajax has created additional problems. Let’s discuss each of these now and see how we might deal with them.

Communicating Loading States

When a web page is loading, the web browser shows a loading indicator. This loading indicator is accurate, accessible, and as it’s part of the browser shell it appears in the same place no matter the website, making it trustworthy and familiar.

When Ajax is used, we have to provide our own mechanism to inform users that the request is loading. This is normally the purview of a loading spinner.

A loading spinner.
A loading spinner.

But you should note that unlike the browser, it doesn’t tell users how long is left, or if the connection is slow. In the next chapter, we’ll look at ways to provide an accurate progress bar with Ajax.

Also, the loading spinner, in its current form, is only determinable by sighted users. To provide a comparable experience (inclusive design principle 1) for screen reader users, we’ll employ a live region (as first set out in “A Checkout Form”).




<
div
 aria-live

=
"assertive"
 role

=
"alert"
 class

=
"visually-hidden"
>

Loading products.

</
div
>


When the products are loaded:




<
div
 aria-live

=
"assertive"
 role

=
"alert"
 class

=
"visually-hidden"
>

Loading complete. 13 products listed.

</
div
>


As the loading spinner is enough communication for sighted users, the live region is given the special visually-hidden class as set out in “A Checkout Form.”

Breaking the Back Button

When a user loads a web page, the browser refreshes and puts the previous page into its history. This allows users to press the back button to return to it quickly. But when Ajax is used to make updates, it’s not put into the browser’s history: technically, the user hasn’t navigated to another page.

Despite this, if the page looks like it has significantly changed, then users will expect the back button to work as normal. Baymard’s usability study 7 found that breaking the back button’s behavior caused confusion, disappointment, anger, and even abandonment.

This is the sort of thing that can happen when a little enhancement breaks convention. This is why the best experiences are usually the simplest.

Fortunately, we can use HTML5’s History API 8 , which was designed to help solve this problem. First, we have to create a history entry when the Ajax request succeeds, like this:

FilterRequester.
prototype.
onInputChange =
 function
(
)
 {
  var
 data =
 this.
form.
serialize(
)
;
 // color=red&rating=3

  this.
requestResults(
data)
;
  history.
pushState(
data,
 null
,
 '/path/to/?'
+
data)
;
}
;

FilterRequester.
prototype.
requestResults =
 function
(
query)
 {
  $.
ajax(
{
 .
.
.
,
 success:
 $.
proxy(
this,
 'onRequestSuccess'
,
 query)
}
)
;
}
;

FilterRequester.
prototype.
onRequestSuccess =
 function
(
query,
 response)
 {
  history.
pushState(
response,
 null
,
 '/path/to/?'
+
query)
;
  //...

}
;

Notes

The first parameter is the state which we want to be stored with the history entry. In this case, it’s the JSON response that’s used to render the updated page.

The second parameter is the title. As it’s not well supported and it’s not necessary in our case, we’re ignoring it by passing null.

The third parameter is the history’s URL, which is the URL including the query string.

With this in place, we need to listen to when the history changes:


function
 FilterRequester(
)
 {
  //...

  $(
window)
.
on(
'popstate'
,
 $.
proxy(
this,
 'onPopState'
)
)
;
}

FilterRequester.
prototype.
onPopState =
 function
(
e)
 {
  this.
requestResults(
e.
originalEvent.
state)
;
}
;

Notes

The state property contains the JSON response we associated with the history entry on creation. It’s then passed to the already written requestResults method so it can be used to render the page again without an AJAX call.

As we’re using jQuery to listen to the onpopstate event, the state (normally e.state ) property is found in e.originalEvent.state .

Users Might Not Notice the Results Update

Another problem with making updates using Ajax is ensuring that users notice the results update. Take a situation where the filter component is very long and scrolls beyond the fold. Imagine that while selecting a filter toward the bottom, it returns too few results such that the user sees a blank screen.

Page showing a long list of filters with the few results now offscreen.
Page showing a long list of filters with the few results now offscreen.

We can’t move focus as that defeats the entire point of introducing Ajax in the first place. There are a two ways we might solve this problem.

First, we can set a maximum height on the filter and give it an additional inner scroll bar. However, inline scroll areas are really hard to use; so much so, that Baymard Institute says they should be avoided 9 .

Second, we can use progressive disclosure to collapse the filter categories. We’ll be looking at this in detail shortly.

Battery Drain and Data Allowance

As we’re now making heavy use of Ajax to rerender the page every time the user selects a filter, this will cause users’ data and battery to be eaten up at a more rapid rate.

Ajax Is Not Necessarily Faster

Despite popular belief, Ajax is not necessarily faster than a page refresh.

First, it must be said, that Ajax is subjected to much of the same latency as a standard page request. If the site (or connection) is slow, then so too will be the Ajax request. In fact, an Ajax request can be even slower.

This is mostly because it engineers away progressive rendering (also called chunking) which the browser provides for free. In “Fun hacks for faster content,” Jake Archibald explains the concept of progressive rendering 10 :

“When you load a page, the browser takes a network stream and pipes it to the HTML parser, and the HTML parser is piped to the document. This means the page can render progressively as it’s downloading. The page may be 100k, but it can render useful content after only 20k is received.”

As Ajax has to wait for the entire 100Kb before showing anything, users have to wait a lot longer to see something.

This is not to say Ajax is bad. It’s just that we should use it judiciously and when we know that users will benefit from it. We introduced Ajax on the assumption that users needed it, but where possible we should, generally speaking, reserve the use of Ajax for making smaller page updates, for which it is better suited.

In the end, we can only be sure of what’s best by conducting user research with a diverse group of people, using a broad range of browsers and devices, on varying connection speeds in the context of our own problem.

Screen size shouldn’t be used to infer fast or slow connection speeds. Like many other people, most of my time on mobile involves me being connected to Wi-Fi. An inclusive experience is one that is made fast for all, by not adding superfluous features and bloat to a page in the first place.

Collapsible Filters

If your filter has many categories and many options within those categories, we need to be sure users aren’t overloaded with too much choice (as explained in chapter 4, “A Login Form”). The best way to do this is to have fewer filters, so don’t include ones that users don’t need.

Additionally, we can collapse the categories. This is advantageous for a number of reasons:

1. The submit button is more likely to draw users’ attention as it will be in view.

2. Ajax-injected results will likely be in view.

3. Users shouldn’t have to scroll nearly as much, while still being able to scan the filter categories.

4. Keyboard users won’t have to tab through all the filters to get to the one they want. This is because hidden content isn’t focusable.

Enhancing the Markup

The basic markup consists of standard form fields. As an example, this is what the color field markup looks like:




<
fieldset
 class

=
"field"
>

  

<
legend
>

    

<
span
 class

=
"field-legend"
>

Color

</
span
>

  

</
legend
>

  

<
div
 class

=
"field-options"
>

    <!-- checkboxes here -->
  

</
div
>



</
fieldset
>


The JavaScript-enhanced markup will look like this:




<
fieldset
 class

=
"field"
>

  

<
legend
>

    

<
button
 type

=
"button"
 aria-expanded

=
"false"
>

Color

</
button
>

  

</
legend
>

  

<
div
 class

=
"field-options hidden"
>

    <!-- checkboxes here -->
  

</
div
>



</
fieldset
>


The legend now contains a button element with a type="button" attribute, which stops it from submitting the form. We don’t want it do that: we just want it to expand and collapse the filters.

Had we instead converted the legend into a button using ARIA’s role="button" , we would be overriding the legend’s semantics. This means screen readers wouldn’t announce the legend as the group’s accessible label. We would have also had to recreate all the free browser-provided behavior associated with the <button> element, such as being focusable and activated by pressing Space and Enter keys.

State

The checkboxes are hidden by the hidden class, as first explained in chapter 1, “A Registration Form.” Removing it will reveal the checkboxes.

The button has an aria-expanded attribute, initially set to false, which denotes that the section is collapsed. When the button is clicked this will be switched to true, which means it’s expanded. For screen readers, the button will be announced as “Color, collapsed, button” (or similar, depending on the screen reader).

We also need to communicate the state of the component visually. Replacing the entire legend with a button element is not ideal because we still want the legend to look like what it is: a legend. By the same token, the interface needs to make it clear that clicking the legend will toggle the filter.

Left: collapsed filters. Right: expanded filters.
Left: collapsed filters. Right: expanded filters.

We can signify this functionality with the conventional plus (can be expanded) and minus (can be collapsed) symbols, though up and down triangles may work just as well. Let’s make use of a lightweight SVG icon placed inside the button:




<
button
 type

=
"Button"
 aria-expanded

=
"false"
>

  
  Color
  

<
svg
 viewBox

=
"0 0 10 10"
 aria-hidden

=
"true"
 focusable

=
"false"
>

    

<
rect
 class

=
"vert"
 height

=
"8"
 width

=
"2"
 y

=
"1"
 x

=
"4"
 />

    

<
rect
 height

=
"2"
 width

=
"8"
 y

=
"4"
 x

=
"1"
 />

  

</
svg
>



</
button
>


The aria-hidden="true" attribute hides the icon from screen readers — the button’s text is enough. The focusable="false" attribute fixes the issue that in Internet Explorer SVG elements are focusable. And the class="vert" attribute on the vertical line allows us to show and hide it based on the state using CSS, like this:


[aria-expanded="true"] .vert 
{
  display
:
 none;
}

Script

All the script does is create and inject a button and toggle visibility when clicked. Here’s the entire script:


function
 FilterCollapser(
fieldset)
 {
  this.
fieldset =
 fieldset;
  this.
options =
 this.
fieldset.
find(
'.field-options'
)
;
  this.
legend =
 this.
fieldset.
find(
'legend'
)
;
  this.
createButton(
)
;
  this.
hide(
)
;
}

FilterCollapser.
prototype.
createButton =
 function
(
)
 {
  this.
button =
 $(
'<button type="button" aria-expanded="true">'
+
this.
legend.
text(
)
+
'<svg viewBox="0 0 10 10" aria-hidden="true" focusable="false"><rect class="vert" height="8" width="2" y="1" x="4" /> <rect height="2" width="8" y="4" x="1" /></svg></button>'
)
;
  this.
button.
on(
'click'
,
 $.
proxy(
this,
 'onButtonClick'
)
)
;
  this.
legend.
html(
this.
button)
;
}
;

FilterCollapser.
prototype.
onButtonClick =
 function
(
e)
 {
  this[
this.
button.
attr(
'aria-expanded'
)
 ==
 'true'
 ?
 'hide'
 :
 'show'
]
(
)
;
}
;

FilterCollapser.
prototype.
hide =
 function
(
)
 {
  this.
button.
attr(
'aria-expanded'
,
 'false'
)
;
  this.
options.
addClass(
'hidden'
)
;
}
;

FilterCollapser.
prototype.
show =
 function
(
)
 {
  this.
button.
attr(
'aria-expanded'
,
 'true'
)
;
  this.
options.
removeClass(
'hidden'
)
;
}
;

Small-Screen Experience

Up to now, we’ve only considered the interface in the context of desktop-sized screens, where there’s enough space to fit the filter next to the results. But what about the small-screen experience?

With a mobile-first mindset, if you cut out the superfluous content and lay out what remains, the experience usually works well. And this approach scales up easily for large viewports: increasing the font size and white space is usually enough.

However, the two components (the filter and the results) are closely weighted in terms of importance. Really, the filter needs to be as prominent as the results, something we’ve been able to achieve in desktop-sized screens.

We can’t just put the filters first, as this will push the results down the page. And we can’t just put them after the results, as users would have to move beyond them — most users wouldn’t know they exist.

We’re left with having to collapse the filters behind a toggle button. We’ve covered this behavior extensively in chapters 5 and 6. Now we’re going to focus on another related problem.

Earlier, I mentioned part of the interview I had with Dave House, a former Gumtree designer, who conducted a lot of research on how to deal with filters. In particular, that desktop users expected the filter to update the results as the user clicked filters with Ajax. However, Gumtree’s research also showed that on mobile this wasn’t desirable because users couldn’t see the results refresh. Here’s what Dave said:

“On mobile, Ajax wasn’t desirable because users couldn’t see any visible refresh of the results.The amount of filters available in some categories meant this would have happened off-screen. To the user this would have looked like nothing had changed. We didn’t want to move focus to the results on every interaction because users often wanted to pick more than one filter. We reluctantly had to use an adaptive approach. On mobile, clicking the “Refine” menu button would reveal a fullscreen filter panel where users could build up their refinements and submit when they were done.”

Gumtree reluctantly went for an adaptive approach, giving mobile users and desktop users different experiences. But there might be a way to give users a more responsive experience — one that conforms to inclusive design principle 3, “Be consistent.”

By applying this principle we ensure that users get the same, familiar conventions applied consistently. In this case, users learn how to use the filters once, to then use them on any device or screen size.

We need to make sure that users can see the results update as they filter. We can achieve this by having the filters appear on top of the results without completely covering them. It works because users can see the results on the left, while filters are selected on the right.

Both Amazon (left) and eBay (right) have the filter appear on top of the results, allowing users to see the results update as filters are selected.
Both Amazon (left) and eBay (right) have the filter appear on top of the results, allowing users to see the results update as filters are selected.

I found this technique described in “ Mobile Faceted Search with a Tray: New and Improved Design Pattern 11 by Kathryn Whitenton, which is worth reading in full.

Denoting Selected Filters

When the user selects a filter, it becomes checked as standard. This is an important signifier. Keeping selected filters in their original position alongside other selected filters is useful, because some users will remember where the filter was when they originally selected it.

We can also help users by grouping the selected filter in a separate list at the top. This confirms to users that what they selected is being viewed. It also gives users easy access should they want to remove any of the active filters, without having to scroll through them scanning for the marked filters.

We can achieve this by adding a little component to the top of the filter, like this:




<
div
 class

=
"current-filters"
 aria-labelledby

=
"current-filters"
>

  

<
h3
 id

=
"current-filters"
>

Current filters

</
h3
>

  

<
ul
>

    

<
li
>



<
a
 href

=
"..."
>

Red (Remove)

</
a
>



</
li
>

  

</
ul
>



</
div
>


Notes

The section is appropriately labeled with a third-level (<h3> ) heading as it sits under the second-level “Filter” heading.

The surrounding div is labeled by heading. This means the heading will be announced for screen reader users who have tabbed to (focusable) links.

Summary

In this chapter we’ve nimbly covered several design details that often crop up with filters. While we’ve persistently tried to keep to convention, non-conventional approaches have been explored that may be needed to satisfy users’ new expectations — expectations that have, unfortunately, been born out of the many materially dishonest interfaces present on the web today.

With that said, we’ve carefully made the effort to include a number of provisions that give users a good and inclusive user experience, should they need the Ajax-driven, automatically submitted and responsive filter component. And we’ve done that, by applying five of the seven inclusive design principles set out in the introduction; namely: provide a comparable experience, consider situation, be consistent, give control, and offer choice.

Things to Avoid

Making links look like radio buttons and checkboxes.

Automatically submitting forms without exhausting other simpler techniques.

Assuming Ajax always delivers a faster and better user experience.

Prioritizing best practice above user needs.

Demos

Filter Form 12

1. http://smashed.by/facetedsearch

2. http://smashed.by/applyingfilters

3. http://smashed.by/cssbackground

4. http://smashed.by/endofwebdesign

5. http://smashed.by/positionsticky

6. http://smashed.by/constbehavior

7. http://smashed.by/macysfilter

8. http://smashed.by/historyapi

9. http://smashed.by/inlinescroll

10. http://smashed.by/fastercontent

11. http://smashed.by/mobilefacetedsearch

12. http://smashed.by/filterformdemo