Form Design Patterns
Form Design Patterns
Form Design Patterns
1 A Registration Form 18
2 A Checkout Form 68
5 An Inbox 220
A
dam Silver is an interaction designer with over 15
years experience working on the web for a range
of companies including Tesco, BBC, Just Eat,
Financial Times, the Department for Work and Pensions
and many others.
E
very so often, someone will point out that I use
blackish text on whitish backgrounds for almost all
my page layouts. And the only comeback I can think
of is that the same approach has worked for hundreds of
billions of publications over the course of hundreds of years.
You know, that old chestnut.
— Heydon Pickering
viii Introduction
Introduction
I remember my first foray into forms. At the turn of the cen-
tury, web design was one of the modules on the information
communication and technology course I took at sixth form
college. My learning mostly consisted of cutting and past-
ing snippets of HTML, CSS, and Javascript. Yes, I came from
the view-source school of web design and development.
Why Forms?
Every meaningful interaction that happens on the web is
achieved by a form of some sort. Without forms, the web
merely becomes a passive experience — just a way to con-
sume content.
on the page. But their low barrier to entry turns them into
what Heydon Pickering refers to as a “10,000-volt electro-
magnet for attracting usability problems.”1
Why Patterns?
Design patterns serve as guidance and solutions to people
solving similar problems over and over. The reason for
design patterns is twofold.
1 http://smashed.by/idp
x Introduction
1. A REGISTRATION FORM
2. A CHECKOUT FORM
4. A LOGIN FORM
5. AN INBOX
6. A SEARCH FORM
7. A FILTER FORM
8. AN UPLOAD FORM
9. AN EXPENSE FORM
Some forms are very long and take hours to complete. We’ll
look at some of the patterns we can use to make long forms
easier to manage.
xiv Introduction
It turns out that not only is this the easiest and cheapest
way to design something, but also that users have a better
time operating these simpler interfaces in the end. It’s the
content and functionality users want anyway.
2 http://smashed.by/websgrain
3 http://smashed.by/idprinciples
xvi Introduction
A Registration Form
L
et’s start with a registration form. Most companies
want long-term relationships with their users. To
do that they need users to sign up. And to do that,
they need to give users value in return. Nobody wants to
sign up to your service — they just want to access what-
ever it is you offer, or the promise of a faster experience
next time they visit.
<form>
<label for="firstName">First name</label>
<input type="text" id="firstName" name="firstName">
<label for="lastName">Last name</label>
<input type="text" id="lastName" name="lastName">
<label for="email">Email address</label>
<input type="email" id="email" name="email">
<label for="password">Create password</label>
<input type="password" id="password" name="password"
placeholder="Must be at least 8 characters">
<input type="submit" value="Register">
</form>
20 Chapter 1
Labels
In Accessibility For Everyone, Laura Kalbag sets out four broad
parameters that improve the user experience for everyone:1
The label
increases the hit
area of the field.
1 http://smashed.by/a11y4all
A Registration Form 21
Placeholders
The placeholder attribute is intended to store a hint. It
gives users extra guidance when filling out a field — par-
ticularly useful for fields that have complex rules such as a
password field.
2 http://smashed.by/nohints
3 http://smashed.by/unreadableweb
24 Chapter 1
<div class="field">
<label for="password">
<span class="field-label">Password</span>
<span class="field-hint">Must contain 8+ characters
with at least 1 number and 1 uppercase letter.</span>
</label>
<input type="password" id="password" name="password">
</div>
<div class="field">
<label for="password">Password</label>
<p class="field-hint" id="passwordhint">Must contain 8+
characters with at least 1 number and 1 uppercase letter.</p>
<input type="password" id="password" name="password"
aria-describedby="passwordhint">
</div>
If you can use a native HTML element or attribute with the seman-
“ tics and behaviour you require already built in, instead of re-pur-
posing an element and adding an ARIA role, state or property to
make it accessible, then do so.
Float Labels
The float label pattern by Matt Smith is a technique that
uses the label as a placeholder.6 The label starts inside the
control, but floats above the control as the user types, hence
the name. This technique is often lauded for its quirky, min-
imalist, and space-saving qualities.
4 http://smashed.by/arialabelinput
5 http://smashed.by/firstrule
6 http://smashed.by/floatlabel
26 Chapter 1
The float label pattern. On the left, an unfocused text field shows the label
inside; on the right, when the text field receives focus, the label moves above
the field.
And float labels don’t actually save space. The label needs
space to move into in the first place. Even if they did save
space, that’s hardly a good reason to diminish the usability
of forms.
Seems like a lot of effort when you could simply put labels above
7 http://smashed.by/luketweet
A Registration Form 27
8 http://smashed.by/questionprotocol
28 Chapter 1
In all likelihood, you don’t need to ask for the user’s first and
last name for them to register. If you need that information
later, for whatever reason, ask for it then. By removing these
fields, we can halve the size of the form. All without resort-
ing to novel and problematic patterns.
NO PASSWORD SIGN-IN
One way to avoid asking users for a password is to use the
no password sign-in pattern. It works by making use of the
security of email (which already needs a password). Users
enter only their email address, and the service sends a
special link to their inbox. Following it logs the user into the
service immediately.
Medium’s passwordless sign-in screen.
A Registration Form 29
Not only does this reduce the size of the form to just one
field, but it also saves users having to remember another
password. While this simplifies the form in isolation, in
other ways it adds some extra complexity for the user.
It’s not that one technique is always better than the other.
It’s that a question protocol urges us to think about this as
part of the design process. Otherwise, you’d mindlessly add
a password field on the form and be done with it.
PASSPHRASES
Passwords are generally short, hard to remember, and easy
to crack. Users often have to create a password of more than
eight characters, made up of at least one uppercase and one
lowercase letter, and a number. This micro-interaction is
hardly ideal.
Field Styling
The way you style your form components will, at least in part,
be determined by your product or company’s brand. Still, label
position and focus styles are important considerations.
9 http://smashed.by/userfriendlypw
A Registration Form 31
LABEL POSITION
Matteo Penzo’s eye-tracking tests showed that position-
ing the label above (as opposed to beside) the form control
works best.10
Placing a label right over its input field permitted users to capture
But there are other reasons to put the label above the field.
On small viewports there’s no room beside the control. And
on large viewports, zooming in increases the chance of the
text disappearing off screen.11
10 http://smashed.by/labelplacement
11 http://smashed.by/nofloatreasons
32 Chapter 1
It means that a text box should look like a text box. Empty
boxes signify “fill me in” by virtue of being empty, like a
coloring-in book. This happens to be part of the reason
placeholders are unhelpful. They remove the perceived
affordance an empty text box would otherwise provide.
Finally, the label and the text box itself should be large
enough to read and tap. This probably means a font size
of at least 16 pixels, and ideally an overall tap target of at
least 44px.13
12 http://smashed.by/lawofproximity
13 http://smashed.by/touchtargetsizes
A Registration Form 33
FOCUS STYLES
Focus styles are a simpler prospect. By default, browsers put
an outline around the element in focus so users, especially
those who use a keyboard, know where they are. The prob-
lem with the default styling is that it is often faint and hard
to see, and somewhat ugly.
input:focus {
outline: 4px solid #ffbf47;
}
<div class="field">
<label for="email">
<span class="field-label">Email address</span>
</label>
<input type="email" id="email" name="email">
</div>
14 http://smashed.by/lettercase
A Registration Form 35
PROGRESSIVE ENHANCEMENT
Progressive enhancement is about users. It just happens
to make our lives as designers and developers easier too.
Instead of keeping up with a set of browsers and devices
(which is impossible!) we can just focus on features.
15 http://smashed.by/featurequeries
A Registration Form 37
16 http://smashed.by/designperf
38 Chapter 1
<div class="field">
<label for="password">
<span class="field-label">Choose password</span>
<span class="field-hint">Must contain 8+ characters
with at least 1 number and 1 uppercase letter.</span>
</label>
<input type="password" id="password" name="password">
</div>
A Registration Form 39
A PASSWORD REVEAL
Obscuring the value as the user types makes it hard to fix
typos. So when one is made, it’s often easier to delete the
whole entry and start again. This is frustrating as most
users aren’t using a computer with a person looking over
their shoulder.
input[type=password]::-ms-reveal {
display: none;
}
function PasswordReveal(input) {
// store input as a property of the instance
// so that it can be referenced in methods
// on the prototype
this.input = input;
this.createButton();
};
PasswordReveal.prototype.createButton = function() {
// create a button
this.button = $('<button type="button">Show password</
button>');
// inject button
$(this.input).parent().append(this.button);
// listen to the button’s click event
this.button.on('click', $.proxy(this, 'onButtonClick'));
};
PasswordReveal.prototype. {
// Toggle input type and button text
if(this.input.type === 'password') {
this.input.type = 'text';
this.button.text('Hide password');
} else {
this.input.type = 'password';
this.button.text('Show password');
}
};
42 Chapter 1
[aria-pressed="true"] {
box-shadow: inset 0 0 0 0.15rem #000, inset 0.25em 0.25em
0 #fff;
}
A Registration Form 45
MICROCOPY
The label is set to “Choose password” rather than “Pass-
word.” The latter is somewhat confusing and could prompt
the user to type a password they already possess, which
could be a security issue. More subtly, it might suggest the
user is already registered, causing users with cognitive
impairments to think they are logging in instead.
Button Styles
What’s a button? We refer to many different types of
components on a web page as a button. In fact, I’ve already
covered two different types of button without calling them
out. Let’s do that now.
But there’s rarely a need for that. Most submit buttons con-
tain just text.
17 http://smashed.by/submitbuttons
18 http://smashed.by/perceivedaffordance
A Registration Form 47
PLACEMENT
Submit buttons are typically placed at the bottom of the
form: with most forms, users fill out the fields from top to
bottom, and then submit. But should the button be aligned
left, right or center? To answer this question, we need to
think about where users will naturally look for it.
19 https://resilientwebdesign.com/
48 Chapter 1
Field labels and form controls are aligned left (in left-to-
right reading languages) and run from top to bottom.
Users are going to look for the next field below the last one.
Naturally, then, the submit button should also be positioned
in that location: to the left and directly below the last field.
This also helps users who zoom in, as a right-aligned button
could more easily disappear off-screen.
TEXT
The button’s text is just as important as its styling. The
text should explicitly describe the action being taken. And
because it’s an action, it should be a verb. We should aim to
use as few words as possible because it’s quicker to read. But
we shouldn’t remove words at the cost of clarity.
The exact words can match your brand’s tone of voice, but
don’t exchange clarity for quirkiness.
Validation
Despite our efforts to create an inclusive, simple, and friction-
free registration experience, we can’t eliminate human
error. People make mistakes and when they do, we should
make fixing them as easy as possible.
HTML5 VALIDATION
HTML5 validation has been around for a while now. By
adding just a few HTML attributes, supporting browsers
will mark erroneous fields when the form is submitted.
Non-supporting browsers fall back to server-side validation.
<form novalidate>
HANDLING SUBMISSION
When the user submits the form, we need to check if there
are errors. If there are, we need to prevent the form from
submitting the details to the server.
20 http://smashed.by/formvalidation
A Registration Form 51
function FormValidator(form) {
form.on('submit', $.proxy(this, 'onSubmit'));
}
FormValidator.prototype. {
if(!this.validate()) {
e.preventDefault();
// show errors
}
};
Note that we are listening to the form’s submit event, not the
button’s click event. The latter will stop users being able to
submit the form by pressing Enter when focus is within one
of the fields. This is also known as implicit form submission.21
DISPLAYING FEEDBACK
It’s all very well detecting the presence of errors, but at this
point users are none the wiser. There are three disparate
parts of the interface that need to be updated. We’ll talk
about each of those now.
Document Title
21 http://smashed.by/implicitsubmission
52 Chapter 1
22 http://smashed.by/everyonehasjs
A Registration Form 53
The browser tab title prefixed with “(2 errors)” acting as a quasi notification.
Error Summary
FormValidator.prototype.showSummary = function () {
// ...
this.summary.focus();
};
FormValidator.prototype. {
e.preventDefault();
var href = e.target.href;
var id = href.substring(href.indexOf("#"), href.length);
$(id).focus();
};
56 Chapter 1
.hidden {
display: none;
}
Inline Errors
23 http://smashed.by/errormessages
A Registration Form 57
Inline error pattern with red error text and warning icon just above the field.
<div class="field">
<label for="blah">
<span class="field-error">
<svg width="1.5em" height="1.5em">
<use xmlns:xlink="http://www.w3.org/1999/xlink"
xlink:href="#warning-icon"></use></svg>
Enter your email address.
</span>
<span class="field-error">Enter an email address</span>
</label>
</div>
<input aria-invalid="false">
FormValidator.prototype. {
this.resetPageTitle();
this.resetSummaryPanel();
this.removeInlineErrors();
if(!this.validate()) {
e.preventDefault();
this.updatePageTitle();
this.showSummaryPanel();
this.showInlineErrors();
}
};
A Registration Form 59
INITIALIZATION
Having finished defining the FormValidator component,
we’re now ready to initialize it. To create an instance of
FormValidator, you need to pass the form element as the
first parameter.
It takes one line of code to take a phone number and strip out all the
“ dashes and parentheses and spaces, and it takes ten lines of code to
write an error message that you left them in.
24 http://smashed.by/onelineofcode
A Registration Form 61
We could wait until the user leaves the field (onblur), but
this is too late as the user has mentally prepared for (and
often started to type in) the next field. Moreover, some users
switch windows or use a password manager when using a
form. Doing so will trigger the blur event, causing an error
to show before the user is finished. All very frustrating.
25 http://smashed.by/inlinevalidation
62 Chapter 1
MailChimp’s password field with instructions that get marked as the user
meets the requirements.
A Registration Form 63
You should put the rules above the field. Otherwise the
onscreen keyboard could obscure the feedback. As a result,
users may stop typing and hide the keyboard to then check
the feedback.
Like labels, hints, and any other content, a good error mes-
sage provides clarity in as few words as possible. Normally,
we should drive the design of an interface based on the
content — not the other way around. But in this case, under-
standing how and why you show error messages influences
the design of the words. This is why Jared Spool says “con-
tent and design are inseparable work partners.”27
26 http://smashed.by/errormessagesroi
27 http://smashed.by/contentdesign
A Registration Form 65
Here’s a checklist:
Summary
In this chapter we solved several fundamental form design
challenges that are applicable well beyond a simple registra-
tion form. In many respects, this chapter has been as much
about what not to do, as it has about what we should. By
avoiding novel and artificial space-saving patterns to focus
on reducing the number of fields we include, we avoid sev-
eral usability failures while simultaneously making forms
more pleasant.
THINGS TO AVOID
DEMOS
A Checkout Form
1 http://smashed.by/parenting
A Checkout Form 69
Two years later, in 2016, Robin Whittleton from the UK’s Gov-
ernment Digital Service (GDS), told me that putting each thing
on a page of its own was a design pattern called “one thing
per page.”2 Behind the improved numbers, there are many
reasons why it drastically improves the user experience.
2 http://smashed.by/1thingperpage
A Checkout Form 71
3 http://smashed.by/nomoreaccordions
72 Chapter 2
4 http://smashed.by/danielblake
A Checkout Form 73
Asking for information at the wrong time can alienate a user. The
Now think about the point where you’ve told the salesper-
son which car you want to buy. Now it’s appropriate to start
negotiating about payment. It would be quite odd if the
salesperson did not do so.
5 http://smashed.by/formsthatwork
74 Chapter 2
Just like the car salesperson, we’ll ask for the right information
at the right time. For example, payment happens toward the
end. Users will be given a chance to check their order before
submitting it. Finally, the confirmation page acts as a sales
receipt for administrative purposes. Here’s the complete flow:
1. Email address
2. Mobile phone (optional)
3. Delivery address
4. Delivery options
5. Delivery notes
6. Payment
7. Check your answers
8. Confirmation
Guest Checkout
Inclusive design principle 2, “Consider situation,” says:
6 http://smashed.by/3mbutton
A Checkout Form 75
1. Email Address
In chapter 1, “A Registration Form,” we had to ask users for
an email address (see page 33). We can reuse that pattern
here, saving us the effort of solving the same problem again
from scratch.
The email address field with hint text explaining why users
are being asked for this.
2. Mobile Phone
Like the email field, we should be asking ourselves why
we’re requesting a phone number. We know the courier
offers real-time text messages on the day of delivery —
A Checkout Form 77
Mobile phone field with “(optional)” text at the end of the label.
<div class="field">
<label for="mobile">
<span class="field-label">Mobile number (optional)</span>
<span class="field-hint">So we can notify you about
delivery</span>
</label>
<input type="tel" id="mobile" name="mobile">
</div>
[…] including the phrase “optional” after a label is much clearer than
“ any visual symbol you could use to mean the same thing. Someone
may always wonder ‘what does this asterisk mean?’ and have to go
hunting for a legend that explains things.
7 http://smashed.by/requiredfields
A Checkout Form 79
3. Delivery Address
The delivery address contains five fields that together make
up an address. Visually there is a slight difference between
the fields: field width.
8 http://smashed.by/optfields
80 Chapter 2
<div class="field">
<label for="address1">
<span class="field-label">Address line 1</span>
</label>
<input type="text" id="address1" name="address1">
</div>
<div class="field">
<label for="address2">
<span class="field-label">Address line 2 (optional)</span>
</label>
<input type="text" id="address2" name="address2">
</div>
A Checkout Form 81
<div class="field">
<label for="city">
<span class="field-label">City</span>
</label>
<input type="text" id="city" name="city">
</div>
<div class="field">
<label for="postcode">
<span class="field-label">Postcode</span>
</label>
<input type="text" id="postcode" name="postcode">
</div>
FIELD WIDTH
In “Write Less Damn Code”, Heydon Pickering jokingly
points out that the reason some people used to add XHTML
1.1 compliant banners to their website was to ensure the
height of the menu matches the height of the content.9
Similarly, you might be tempted to give every address field
the same width.
But giving the postcode field the same width as every other
field increases the cognitive effort needed to fill it out. This
is because the width gives users a clue as to the length of
the content it requires.
9 http://smashed.by/heydontalk
82 Chapter 2
If a field was too long or too short, [users] started to wonder if they had
“ misunderstood the label. […] This was especially true for fields with
uncommon data or a technical label like CVV [card verification value].
CAPTURE+ ENHANCEMENT
Capture+ is a third-party plugin that lets users search for
their address quickly and accurately.11 Instead of manually
typing each part of the address in five separate boxes, users
type into just one.
10 http://smashed.by/formfieldux
11 http://smashed.by/addresscapture
A Checkout Form 83
A text box using the Capture+ plugin showing options as users type
their postcode.
4. Delivery Options
This is the first field that consists of multiple controls; in
this case, radio buttons.
Delivery option radio buttons with two options: free delivery, and premium.
<fieldset class="field">
<legend>
<span class="field-legend">Delivery options</span>
</legend>
<div class="field-radioButton">
<input type="radio" name="option" id="option"
value="Standard" checked>
<label for="option">Standard (Free, 2-3 days)</label>
</div>
<div class="field-radioButton">
<input type="radio" name="option" id="option2"
value="Premium">
<label for="option2">Premium (£6, Next day)</label>
</div>
</fieldset>
A Checkout Form 85
GROUPING
To group multiple controls, we must wrap them in a
fieldset. The legend describes the group like a label
describes the individual control.
You may be tempted to group all fields this way. For exam-
ple, the address form from earlier could be wrapped inside a
fieldset with a legend set to “Address.” While this is tech-
nically valid, it’s unnecessary and verbose, as the field labels
make sense without a legend. Put another way, users don’t
need to hear “Address: Address Line 1” as it doesn’t add value.
86 Chapter 2
SMART DEFAULTS
As most users will want free delivery, that option comes
first. It’s also selected by default thanks to the checked
attribute. This stops users from ever seeing an error and
gives users less to do.
“ — Caroline Jarrett12
STYLING
By default, radio buttons (and checkboxes) are rendered
quite small. This makes them hard to click or tap, especially
for people with motor impairments.
We can increase the size using CSS, but this isn’t as simple
as it sounds. In “Making radio buttons and checkboxes eas-
ier to use,” Robin Whittleton explains that the way browsers
respond to CSS differs.13
12 http://smashed.by/designmantra
13 http://smashed.by/govukissues
A Checkout Form 87
14 http://smashed.by/govukcheckboxes
15 http://smashed.by/govukissues
88 Chapter 2
5. Delivery Notes
Imagine you’re at work. You receive a notification to say
your item is being delivered. When you arrive home, instead
of seeing the package, you find a card saying it couldn’t be
delivered because it was too big to fit through the letterbox.
Frustrating.
<div class="field">
<label for="notes">
<span class="field-label">Delivery notes (optional)</span>
<span class="field-hint">Tell us where to leave your
package in case you’re not in. For example,
“Leave it with my neighbor”.</span>
</label>
<textarea id="notes" name="notes"></textarea>
</div>
LIMITING TEXT
Limiting the amount of text a user can type can and should
be handled by validation, as set out in chapter 1, “A Registra-
tion Form.” But there are some additional considerations.
The maxlength attribute (which takes a number value) lim-
its the amount of a text a user can type. As soon as the limit
is reached, the browser will ignore the input.
90 Chapter 2
CHARACTER COUNTDOWN
Instead, we should let users type freely, and tell users how
many characters they have left. This way, users can see the
feedback when they finally look up at the screen and can
edit their entry in response. If they don’t notice the feed-
back, an error will
be shown when they
submit the form,
thanks to the valida-
tion routine (set out
in chapter 1, “A Regis-
tration Form”).
The character countdown
telling users how many
characters they have left.
16 http://smashed.by/maxlength
A Checkout Form 91
The event listener will then check the length of the typed
value against the configurable maxLength to calculate how
many characters are remaining. This is then injected into
the status box:
CharacterCountdown.prototype. {
var remaining = this.options.maxLength—this.field.val().
length;
this.status.html(this.options.message.replace(/%count%/,
remaining));
};
92 Chapter 2
Live Regions
Notes:
17 http://smashed.by/aria
18 http://smashed.by/ariastatus
A Checkout Form 93
6. Payment
It’s hardly surprising that most transactions are abandoned
at the payment page. Not only is this screen shown toward
the end of the journey (when users have had the most time
to reconsider their decision and used up a lot of energy), but
they may have to stop and find their credit card.
REMOVING FIELDS
There are a number of details on a credit or debit card: name
on card, card number, valid from date, expiry date, issue
number, security number; all of these are commonly found
on payment forms. But not all of these details are needed to
process a payment.
19 http://smashed.by/stripe
20 http://smashed.by/whatispci
A Checkout Form 95
Only the numerics contained in card details are used for verification.
“ That is, the house number is used, but not street name. We ask for it
for our records. Being able to eyeball this stuff is handy in any situa-
tion where you have to query what’s happened. Besides, some people
expect that they’ll have to provide an address.
96 Chapter 2
Øyvind is not a designer per se, but his input into the design
process was crucial. Many of us assume that back-end
developers don’t care about the user experience, but tapping
into their knowledge is immensely valuable.
AUTOFILL
Most modern browsers can automatically fill in form fields,
by way of the autocomplete attribute. When the user focuses
a particular field, the browser checks if it has that information
stored — if it does, the user can select it without having to type.
Since iOS 8, Safari lets users scan their card using the
iPhone’s camera — it uses the same mechanism to automati-
cally fill out those fields.
21 http://smashed.by/chromeautofill
22 You can refer to the full list of available values in the HTML
specification: http://smashed.by/autocompletespec
98 Chapter 2
<div class="field">
<label for="ccname">
<span class="field-label">Name on card</span>
</label>
<input type="text" id="ccname" name="ccname" autocomplete="cc-name">
</div>
<div class="field">
<label for="cardnumber">
<span class="field-label">Card number</span>
</label>
<input type="text" id="cardnumber" name="cardnumber"
autocomplete="cc-number">
</div>
<div class="field">
<label for="expdate">
<span class="field-label">Expiry date</span>
</label>
<input type="text" id="expdate" name="expdate"
autocomplete="cc-exp">
</div>
<div class="field">
<label for="cvc">
<span class="field-label">Security code</span>
</label>
<input type="number" id="cvc" name="cvc" autocomplete="cc-csc">
</div>
<fieldset class="field">
<legend>
<span class="field-legend">Is your billing address the
same as delivery?</span>
</legend>
<div class="field-checkbox">
<label for="things">
<input type="checkbox" name="things" value="" id="things"
checked>Yes, it’s the same
</label>
</div>
</fieldset>
A Checkout Form 99
NUMBER INPUT
The number input (<input type="number">) lets mobile
users more quickly type a number via a numeric keypad. On
desktop, the input will contain increment and decrement
buttons called spinners, which make it easy to make small
adjustments without having to select and type.
“ [...] numerals are often used for labels (as with telephone numbers),
for ordering (as with serial numbers), and for codes (as with ISBNs).
23 http://smashed.by/number
100 Chapter 2
24 http://smashed.by/typenumber
A Checkout Form 101
25 http://smashed.by/govuka11y
102 Chapter 2
Similarly, for the expiry date some users might type a slash,
others may leave it out. Whether it’s a slash or space, or a card
number, or an expiry date, we should be forgiving by strip-
ping whitespace and normalizing the format where possible.
The first problem is that sites don’t always refer to this field
as the CVC number. Sometimes it’s referred to as a security
code number or card verification value (CVV). Being speci-
fied as an acronym doesn’t help either. And to top it off, on
the card the number is never accompanied by a description,
making it hard to reconcile the requirements.
CheckboxCollapser.prototype. {
this.check();
};
CheckboxCollapser.prototype.check = function() {
if(this.checkbox.prop('checked')) {
this.toggleElement.addClass('hidden');
} else {
this.toggleElement.removeClass('hidden');
}
};
Notes
Take Jack (I made him up), a father of two infants. It’s the
middle of the night, and his newborn baby is crying incon-
solably. Naturally, Jack’s tired and stressed. To make things
worse, there are no more nappies.
He grabs the phone, adds nappies to the basket, fills out all
the details and submits the order. Great — except it isn’t. He
ordered the wrong size nappies and paid with the wrong
card. What Jack entered was valid, but still a mistake.
As this is the final step in the flow, the button’s text should
be set to “Place order” or similar. Leaving it as “Continue”
would mislead the user into thinking there is another step
to complete, which is likely to result in more cancelled
orders and strain on the customer service department. This
would increase operating costs and shows that good design
is also good for business.
MAKING CHANGES
Every piece of information gathered during checkout
should be represented on the review page. Users shouldn’t
have to go back to check information — that would defeat
the purpose of the page. Users should only need to go back
if they spot a mistake.
Left: the review page with edit links. Right: the user editing their mobile num-
ber, having spotted a mistake.
When the user makes a change, they are taken back to the
‘check your answers’ page again for another review, which
puts users firmly in control and reduces stress and anxiety.
8. Confirmation Page
Confirmation pages are so much more than just confirming
the order. Neglecting the user experience here is a great way
to lose out on future business.
call the free sales number and are quickly put through to
a helpful agent. Parting with money is usually made easy.
But when you need to make a claim, it’s more painful: the
number isn’t free and calls take a long time be answered. All
very stressful.
You should tell users what happens next, such as when deliv-
ery will take place, and what to do if something goes wrong.
It’s also the best time to ask users to sign up (if they checked
out anonymously). As we have most of the information
to hand, we only need to ask for a password, making this
step both optional and easy. Users should have had a good
experience up to now, which should naturally encourage
sign-up.
• a reference number
• contact details
• what happens next and when
• what to do if something goes wrong
• ask users to sign up in return for something
112 Chapter 2
Layout
Up to now, we’ve focused on the design of the form within
each page, but we haven’t considered the interface holis-
tically. In fact, this is one of the dangers of composing
interfaces out of predefined smaller components: the overall
design can end up neglected.
INDICATING PROGRESS
Progress bars or indicators are often used within check-
out because — at least in theory — they give users an idea
of where they are and how long’s left. Despite the sound
reasoning, there isn’t much evidence to show that progress
bars are all that useful or even noticed. For example, the UK
government’s Carer’s Allowance team removed a 12-step
progress bar with no effect on completion rates or times.26
26 http://smashed.by/sharedspaces
114 Chapter 2
The problem is that a progress bar should tell the user what
steps exist in advance. But the steps are based on users’
answers. Either you show every possible step, which is
misleading, or you update the progress bar (by removing
or adding steps) as you go, which somewhat defeats the
purpose of having one.
A Checkout Form 115
Misleading progress bar because the payment step isn’t applicable when
collecting in-store.
First, keep the text inside the <h1> so screen readers get a
comparable experience (inclusive design principle 1). Second,
you’ll need to hide it from sighted users like this:
.visually-hidden {
border: 0!important;
clip: rect(0 0 0 0)!important;
height: 1px!important;
margin: -1px!important;
A Checkout Form 117
overflow: hidden!important;
padding: 0!important;
position: absolute!important;
width: 1px!important;
}
Order Summary
When you’re shopping in a physical shop, you pick up items
and place them in your shopping basket. Eventually, you
checkout at the till. All the while, you can see what you’re
buying. Sometimes, at the last minute, you change your
mind and take an item off the conveyor belt. Or you realize
you forgot something and dash off to get it.
“ The system should always keep users informed about what is going
on, through appropriate feedback.
— Jakob Nielsen, “10 Usability Heuristics for User Interface Design”27
27 https://smashed.by/usabilityheuristics
118 Chapter 2
The order summary panel gets populated as the user completes each screen.
The email screen containing just the basket.
The mobile number screen, now containing the email address previously
populated.
A Checkout Form 119
BACK LINKS
As the user is following a linear flow, we need to consider
the need to step back. The browser’s back button provides
this functionality for free, but some people mistrust it
because of bad past experiences when their data was lost.
28 http://smashed.by/backbutton
120 Chapter 2
Summary
In this chapter, we started out by looking at the one thing
per page pattern, which helps to break down large forms
into small chunks, making it easy for users to fill out and
make amendments.
Demos
• Optional telephone field:
http://smashed.by/telinputdemo
• Delivery radio buttons:
http://smashed.by/deliveryoptionsdemo
• Delivery notes with countdown:
http://smashed.by/charcountdowndemo
• Payment form: http://smashed.by/paymentformdemo
122 Chapter 2
CHECKLIST
I
n this chapter, we’ll design a flight booking service. At
first glance this may seem a bit niche, especially when
compared to “A Registration Form” and “A Checkout
Form.” However, we’re going to explore several complex
problems that, in the end, will result in reusable patterns
— patterns that are very much transferable to other prob-
lem domains, such as booking a cinema ticket, or even a
hotel room.
1. Where to Fly
First, users have to choose an origin and destination; that
is, places to fly from and to. Without this information, the
service can’t offer any flights. What’s the best way of asking
users for this information?
124 Chapter 3
We should try to use the features that are native to the browser.
They are familiar (by convention) and fully accessible out of
the box. They also require far less work to implement.
SELECT BOX
Also known as dropdown menus, select boxes hide options
behind a menu. Clicking the select box reveals the options.
Once an option is selected, the menu collapses. Select
boxes are often used for their space-saving qualities.
What’s most interesting, though, is why we need to save
space in the first place.
Destination field as a select box. Left: collapsed. Right: expanded with options
showing.
A Flight Booking Form 125
In her talk “Burn Your Select Tags,” Alice Bartlett shares the
user research she undertook at the UK’s Government Digital
Service (GDS).1
1 http://smashed.by/burnselecttags
2 http://smashed.by/dropdownlastresort
126 Chapter 3
RADIO BUTTONS
Radio buttons, unlike select boxes, are generally well under-
stood and easy to use, not least because they don’t hide
options. They are exposed, making them easy to compare,
scan, and select. They’re also malleable; that is, they let us
use whatever content, in whatever format we want, inside
the related label (more on that shortly).
3 http://smashed.by/ppldontscrollmyth
A Flight Booking Form 127
SEARCH INPUT
A search box (<input type="search">) is similar to a regu-
lar text box (<input type="text">). A search box, however,
lets users clear the field by tapping a delete icon, or pressing
Escape when focused. With a text box, you have to select
the text and press Delete, which takes a little longer.
DATALIST
Users need a control that lets them filter a long list of
destinations. A control that marries the flexibility of a text
box with the assurance of a select box. This type of con-
trol goes by many different names, including: type ahead,
predictive search, and combo box; but we’ll refer to it as an
autocomplete control.
128 Chapter 3
4 http://smashed.by/datalistcaniuse
A Flight Booking Form 129
AN AUTOCOMPLETE CONTROL
By creating a custom autocomplete component from
scratch, there’s an opportunity to create a powerful expe-
rience that also allows for common typos and endonyms
(discussed later). A word of warning though: we’re going to
break new ground. Designing a robust and fully inclusive
autocomplete control is hard work — but that’s what our job
is all about.
5 http://smashed.by/webcomponents
130 Chapter 3
<div class="field">
<label for="destination">
<span class="field-label">Destination</span>
</label>
<select name="destination" id="destination">
<option value="">Select</option>
<option value="1">France</option>
<option value="2">Germany</option>
<option value="3">Spain</option>
</select>
</div>
<div class="field">
<label for="destination">
<span class="field-label">Destination</span>
</label>
<select name="destination" aria-hidden="true"
tabindex="-1" class="visually-hidden">
<!-- options here -->
</select>
<div class="autocomplete">
<input aria-owns="autocomplete-options--destination"
autocapitalize="none" type="text" autocomplete="off"
aria-autocomplete="list" role="combobox" id="destination"
aria-expanded="false">
<svg focusable="false" version="1.1" xmlns="http://www.
w3.org/2000/svg">
<!-- rest of SVG here -->
</svg>
<ul id="autocomplete-options--destination"
role="listbox" class="hidden">
<li role="option" tabindex="-1" aria-selected="false"
data-option-value="1" id="autocomplete_1">
France
</li>
<li role="option" tabindex="-1" aria-selected="true"
data-option-value="2" id="autocomplete_2">
Germany
</li>
<!-- more options here -->
</ul>
<div aria-live="polite" role="status" class="visually-hidden">
13 results available.
</div>
</div>
</div>
132 Chapter 3
Hiding the select box while still having its value submitted
involves a number of techniques in combination. The vis-
ually-hidden class and aria-hidden="true" attribute (as
first set out in “A Checkout Form”) hide the select box from
sighted and screen reader users respectively. The tabin-
dex="-1" attribute stops keyboard users from being able to
focus it.
The SVG icon is layered on top of the text box using CSS.
Note the focusable="false" attribute, which fixes the
issue that in Internet Explorer SVG elements are focusable
by default.
Menu Notes
6 http://smashed.by/combobox
134 Chapter 3
Live Region
When the user types into the text box, we need to listen out
for certain keys by using JavaScript.
Autocomplete.prototype.createTextBox = function() {
// ...
this.textBox.on('keyup', $.proxy(this, 'onTextBoxKeyUp'));
// ...
};
Autocomplete.prototype. {
switch (e.keyCode) {
case this.keys.esc:
case this.keys.up:
case this.keys.left:
case this.keys.right:
case this.keys.space:
case this.keys.enter:
case this.keys.tab:
case this.keys.shift:
// ignore these keys otherwise the menu will show briefly
break;
case this.keys.down:
this.onTextBoxDownPressed(e);
break;
default:
this.onTextBoxType(e);
}
};
136 Chapter 3
Autocomplete.prototype. {
// only show options if user typed something
if(this.textBox.val().trim().length > 0) {
// get options based on value
var options = this.getOptions(this.textBox.val().
trim().toLowerCase());
// build the menu based on the options
this.buildMenu(options);
7 http://smashed.by/magicnumber
A Flight Booking Form 137
“ platforms is that the tab and shift+tab keys move focus from one UI
component to another while other keys, primarily the arrow keys,
move focus inside of components that include multiple focusable
elements. The path that the focus follows when pressing the tab key
is known as the tab sequence or tab ring.
8 http://smashed.by/generalnav
138 Chapter 3
9 http://smashed.by/activedescendant
A Flight Booking Form 139
this.textBox.on('blur', function(e) {
// hide menu
});
this.textBox.on('blur', $.proxy(function(e) {
// set a delay before hiding the menu
this.timeout = window.setTimeout(function() {
// hide menu
}, 100);
}, this));
this.menu.on('focus', $.proxy(function(e) {
// cancel the hiding of the menu
window.clearTimeout(this.timeout);
}, this));
this.textBox.on('keydown', $.proxy(function(e) {
switch (e.keyCode) {
case this.keys.tab:
// hide menu
break;
}
}, this));
A Flight Booking Form 141
Unlike the blur event, this approach doesn’t cover the case
where users blur the control by clicking outside of it. We
have to handle this case manually by listening to the doc-
ument’s click event, but being careful to work out what’s
clicked — we don’t want to hide the menu if the user clicks
within the control.
$(document).on('click', $.proxy(function(e) {
if(!$.contains(this.container[0], e.target)) {
// hide the menu
}
}, this));
When the text box is focused, pressing the Down key trig-
gers onTextBoxDownPressed() like this:
Autocomplete.prototype. {
var option;
var options;
var value = this.textBox.val().trim();
142 Chapter 3
/*
When the value is empty or if it exactly
matches an option show the entire menu
*/
if(value.length === 0 || this.isExactMatch(value)) {
// get options based on the value
options = this.getAllOptions();
// build the menu based on the options
this.buildMenu(options);
// show the menu
this.showMenu();
// retrieve the first option in the menu
option = this.getFirstOption();
// highlight the first option
this.highlightOption(option);
/*
When there’s a value that doesn’t have
an exact match show the matching options
*/
} else {
// get options based on the value
options = this.getOptions(value);
// if there are options
if(options.length > 0) {
// build the menu based on the options
this.buildMenu(options);
// show the menu
this.showMenu();
// retrieve the first option in the menu
option = this.getFirstOption();
// highlight the first option
this.highlightOption(option);
}
}
};
A Flight Booking Form 143
The else condition will populate the menu with options that
match (if any), and again will focus the first menu option. At
the end of both scenarios the highlightOption() method
is called, which we’ll look at later.
.autocomplete [role=listbox] {
max-height: 12em;
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
}
Clicking an Option
10 http://smashed.by/eventdelegation
A Flight Booking Form 145
Autocomplete.prototype.createMenu = function() {
//...
this.menu.on('click', '[role=option]', $.proxy(this,
'onOptionClick'));
//...
};
Autocomplete.prototype. {
var option = $(e.currentTarget);
this.selectOption(option);
};
Autocomplete.prototype.selectOption = function(option) {
var value = option.attr('data-option-value');
this.setValue(value);
this.hideMenu();
this.focusTextBox();
};
Autocomplete.prototype.createMenu = function() {
this.menu.on('keydown', $.proxy(this, 'onMenuKeyDown'));
};
Autocomplete.prototype. {
switch (e.keyCode) {
case this.keys.up:
// Do stuff
break;
case this.keys.down:
// Do stuff
break;
case this.keys.enter:
// Do stuff
break;
case this.keys.space:
// Do stuff
break;
case this.keys.esc:
// Do stuff
break;
case this.keys.tab:
// Do stuff
break;
default:
this.textBox.focus();
}
};
148 Chapter 3
Key Action
If the first option is focused, set focus to the text
Up
box; otherwise set focus to the previous option.
Focus the next menu option. If it’s the last menu
Down
option, do nothing.
Tab Hide the menu.
Enter or Select the currently selected option and focus the
Space text box.
Escape Hide the menu and set focus to the text box.
Any other
Focus the text box so users can continue typing.
character
Autocomplete.prototype.highlightOption = function(option) {
// if there’s a currently selected option
if(this.activeOptionId) {
// get the option
var activeOption = this.getOptionById(this.
activeOptionId);
// unselect the option
activeOption.attr('aria-selected', 'false');
}
// set new option to selected
option.attr('aria-selected', 'true');
A Flight Booking Form 149
.autocomplete [role=option][aria-selected="true"] {
background-color: #005EA5;
border-color: #005EA5;
color: #ffffff;
}
<select>
<option value="">Select</option>
<option value="1">France</option>
<option value="2">Germany</option>
<option value="3">Spain</option>
</select>
A Flight Booking Form 151
Autocomplete.prototype.getOptions = function(value) {
var matches = [];
// Loop through each of the option elements
this.select.find('option').each(function(i, el) {
// if the option has a value and the option’s text node
matches the user-typed value
if($(el).val().trim().length > 0 && $(el).text().
toLowerCase().indexOf(value.toLowerCase()) > -1) {
// push an object representation to the matches array
matches.push({
text: $(el).text(),
value: $(el).val()
});
}
});
return matches;
};
<select>
<!-- options -->
<option value="2" data-alt="Deutschland">Germany</option>
<!-- options -->
</select>
<select>
A Flight Booking Form 153
With the select box ready, we can change the filter function to
check the alternative name like this:
Autocomplete.prototype.getOptions = function(value) {
var matches = [];
// Loop through each of the option elements
this.select.find('option').each(function(i, el) {
// if the option has a value and the option’s text node
matches the user-typed value or the option’s data-alt
attribute matches the user-typed value
if( $(el).val().trim().length > 0
&& $(el).text().toLowerCase().indexOf(value.
toLowerCase()) > -1
|| $(el).attr('data-alt')
&& $(el).attr('data-alt').toLowerCase().indexOf(value.
toLowerCase()) > -1 ) {
// push an object representation to the matches array
matches.push({
text: $(el).text(),
value: $(el).val()
});
}
});
return matches;
};
2. When to Fly
Dates are notoriously hard: different time zones, formats,
delimiters, days in the month, length of a year, daylight
savings, and on and on.11 It’s hard work designing all this
complexity out of an interface.
Often, three select boxes are used: one for day, month, and
year. Admittedly, we’ve just discussed the cons of select
boxes, but it must be said that one of their redeeming quali-
ties is that they help users enter the right information. But
in the case of dates, even this quality doesn’t hold up because
you can select an invalid date, such as 31 February 2017.
11 http://smashed.by/falsehoodstime
A Flight Booking Form 155
A date of birth field made up of three select boxes: day, month and year.
A text box populated with “10/09/12” which could be one of several dates
depending on the format.
12 http://smashed.by/dateselector
156 Chapter 3
The way you should ask users for dates depends on the types of date
If you ask for a date exactly as it’s shown on a passport, credit card
“ or similar item, make the fields match the format of the original.
This will make it easier for users to copy it across accurately.
MEMORABLE DATES
A memorable date is one that you remember easily such as
your date of birth. Typing six digits unassisted into a text
box is much quicker than scrolling, swiping, and clicking
A Flight Booking Form 157
Date of birth field made up of three text boxes: day, month, and year.
<fieldset class="field">
<legend>
<span class="field-legend">Date of birth</span>
<span class="field-hint">DD MM YYYY</span>
</legend>
<div class="field-dayWrapper">
<label for="day">Day</label>
<input class="field-dayBox" type="text"
pattern="[0-9]*" name="day" id="day">
</div>
<div class="field-monthWrapper">
<label for="month">Month</label>
<input class="field-monthBox" type="text"
pattern="[0-9]*" name="month" id="month">
</div>
<div class="field-yearWrapper">
<label for="year">Year</label>
<input class="field-yearBox" type="text"
pattern="[0-9]*" name="year" id="year">
</div>
</fieldset>
158 Chapter 3
A DATE PICKER
When choosing a date to fly on, users are neither entering
a memorable date nor one found in a document. They are
searching for a date sometime in the future and usually
within the next few months.
13 http://smashed.by/nobodywantsyourproduct
14 http://smashed.by/progenhance
A Flight Booking Form 161
This markup has been used many times already in the book.
The only difference is that the input’s type attribute is set to
date.
<div class="field">
<label for="departure">
<span class="field-label">Departure date</span>
</label>
<input type="date" id="departure" name="departure">
</div>
The date input will change into a basic text input — this is
known as graceful degradation. While users won’t get the
convenience of a date picker, they’ll still be able to enter
a date. This is an example of HTML’s inherently resilient
nature. When things fails, they don’t break.
The enhanced date picker interface shown in its expanded state, with a but-
ton to the right of the text field, and the calendar inline below it.
15 http://smashed.by/sizetaptargets
164 Chapter 3
Feature Detection
function dateInputSupported() {
var el = document.createElement('input');
try {
el.type = "date";
} catch(e) {}
return el.type == "date";
}
if(!dateInputSupported()) {
var DatePicker = function() {
// code here
};
}
After the date picker has been initialized, the markup will
have been changed to include the date picker controls:
toggle button, next and previous month buttons, and the
calendar grid.
16 http://smashed.by/featuredetection
166 Chapter 3
<div class="field">
<label for="when">
<span class="field-label">Date</span>
</label>
<div class="datepicker">
<input type="text" id="when" name="when">
<button type="button" aria-expanded="true" aria-
haspopup="true">Choose</button>
<div class="datepicker-wrapper hidden">
<!-- Calendar widget goes here -->
</div>
</div>
</div>
Notes
• The type="button" attribute stops the button
from submitting the form. If the type was set to
submit (or omitted altogether) when pressed, it
would incorrectly submit the form.
• The aria-haspopup="true" attribute indicates
that the button reveals a calendar. It acts as a warn-
ing that, when pressed, the focus will be moved to
the calendar. Its value is always set to true.
• The aria-expanded attribute indicates whether
the calendar is currently in an open (expanded) or
closed (collapsed) state by toggling between true
and false values.
A Flight Booking Form 167
The date picker in its original state (left), and after revealing the calendar (right).
DatePicker.prototype. {
// showing
if(this.toggleButton.attr('aria-expanded') == 'true') {
this.hide();
// hiding
} else {
this.show();
this.calendar.find('button:first-child').focus();
}
};
168 Chapter 3
DatePicker.prototype.hide = function() {
this.calendar.addClass('hidden');
this.toggleButton.attr('aria-expanded', 'false');
};
DatePicker.prototype.show = function() {
this.calendar.removeClass('hidden');
this.toggleButton.attr('aria-expanded', 'true');
};
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 17
17" width="1em" height="1em">...</svg>
</button>
</div>
<!-- grid here -->
</div>
The month’s title and year are placed within a live region (as
first discussed in chapter 2). This means its content will be
announced by screen readers when the calendar is revealed.
This same information will be continually announced as
users move between different months.
With the calendar now revealed, the user can move between
the Previous Month and Next Month buttons by using the
Tab key. This is because we’ve used <button> elements,
which are naturally focusable and part of the tab sequence.
DatePicker.prototype.addEventListeners = function() {
this.calendar.on('click', 'button:first-child',
$.proxy(this, 'onPreviousClick'));
this.calendar.on('click', 'button:last-child',
$.proxy(this, 'onNextClick'));
};
DatePicker.prototype. {
this.showPreviousMonth();
};
DatePicker.prototype. {
this.showNextMonth();
};
The Grid
<table role="grid">
<thead>
<tr>
<th aria-label="Sunday">Su</th>
<th aria-label="Monday">Mo</th>
<th aria-label="Tuesday">Tu</th>
<th aria-label="Wednesday">We</th>
A Flight Booking Form 171
<th aria-label="Thursday">Th</th>
<th aria-label="Friday">Fr</th>
<th aria-label="Saturday">Sa</th>
</tr>
</thead>
<tbody>
<tr>
<td tabindex="-1" aria-selected="false"
aria-label="4 February, 2018" role="gridcell"
data-date="Sun Feb 04 2018 00:00:00 GMT+0000 (GMT)">
<span aria-hidden="true">4</span>
</td>
<td tabindex="-1" aria-selected="false"
aria-label="5 February, 2018" role="gridcell"
data-date="Mon Feb 05 2018 00:00:00 GMT+0000 (GMT)">
<span aria-hidden="true">5</span>
</td>
<td tabindex="-1" aria-selected="false"
aria-label="6 February, 2018" role="gridcell"
data-date="Tue Feb 06 2018 00:00:00 GMT+0000 (GMT)">
<span aria-hidden="true">6</span>
</td>
<td tabindex="-1" aria-selected="false"
aria-label="7 February, 2018" role="gridcell"
data-date="Wed Feb 07 2018 00:00:00 GMT+0000 (GMT)">
<span aria-hidden="true">7</span>
</td>
<td tabindex="-1" aria-selected="false"
aria-label="8 February, 2018" role="gridcell"
data-date="Thu Feb 08 2018 00:00:00 GMT+0000 (GMT)">
<span aria-hidden="true">8</span>
</td>
<td tabindex="-1" aria-selected="false"
aria-label="9 February, 2018" role="gridcell"
data-date="Fri Feb 09 2018 00:00:00 GMT+0000 (GMT)">
172 Chapter 3
<span aria-hidden="true">9</span>
</td>
<td tabindex="-1" aria-selected="false" aria-
label="10 February, 2018" role="gridcell"
data-date="Sat Feb 10 2018 00:00:00 GMT+0000 (GMT)">
<span aria-hidden="true">10</span>
</td>
</tr>
<tr>...</tr>
<tr>...</tr>
<tr>...</tr>
</tbody>
</table>
Clicking a Day
DatePicker.prototype. {
var d = new Date($(e.currentTarget).attr('data-date'));
this.updateTextBoxDate(d);
this.hide();
this.input.focus();
this.selectDate(d);
this.selectedDate = d;
};
174 Chapter 3
Keyboard Interaction
17 http://smashed.by/tabindex
A Flight Booking Form 175
DatePicker.prototype.addEventListeners = function() {
// ...
this.calendar.on('keydown', '[role=gridcell]',
$.proxy(this, 'onCellKeyDown'));
// ...
};
DatePicker.prototype. {
switch(e.keyCode) {
case this.keys.down:
this.onDayDownPressed(e);
break;
case this.keys.up:
this.onDayUpPressed(e);
break;
case this.keys.left:
this.onDayLeftPressed(e);
break;
case this.keys.right:
this.onDayRightPressed(e);
break;
case this.keys.space:
case this.keys.enter:
this.onDaySpacePressed(e);
break;
}
};
176 Chapter 3
Key Action
Down Focus the same day in the subsequent week. If it’s the
last week, switch to the next month first.
Up Focus the same day in the previous week. If it’s the
first week, switch to the previous month first.
Left Focus the previous day. If it’s the first day of the
month, switch to the previous month first.
Right Focus the next day. If it’s the last day of the month,
switch to the next month first.
Enter or Performs the same actions as clicking the day: popu-
Space late and focus the text box, and hide the menu.
Escape Hide the calendar and focus the toggle button.
Future Support
18 http://smashed.by/webcontinuum
A Flight Booking Form 179
3. Choosing Passengers
Next we need to know how many people are travelling. The
age of the passengers affects the price of the ticket, so we’ll
arrange the interface according to these age groups.
Passenger count form with three fields: one for adults, children, and infants.
180 Chapter 3
<div class="field">
<label for="adults">
<span class="field-label">How many people aged 16 years
and over are flying?</span>
</label>
<input type="number" id="adults" name="adults" min="0"
max="9">
</div>
<div class="field">
<label for="children">
<span class="field-label">How many children, aged
between 2 and 15 years old, are flying?</span>
</label>
<input type="number" id="children" name="children"
min="0" max="9">
</div>
<div class="field">
<label for="infants">
<span class="field-label">How many infants, under 2
years old, are flying?</span>
</label>
<input type="number" id="infants" name="infants" min="0"
max="9">
</div>
A STEPPER COMPONENT
To supply all browsers with bigger, more ergonomic but-
tons, we can create a custom stepper component using
JavaScript. On mobile, they’ll save users from triggering the
on-screen keyboard, which reduces the time and effort to
complete the task.
182 Chapter 3
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
appearance: none;
margin: 0;
}
A Flight Booking Form 183
<div class="field">
<label for="adults" id="adults-label">How many people
aged 16 and over are flying?</label>
<div class="stepper">
<button type="button" aria-label="Add" aria-
describedby="adults-label">−</button>
<input type="number" id="adults" name="adults"
value="1">
<button type="button" aria-label="Remove" aria-
describedby="adults-label">+</button>
<div class="visually-hidden" role="status" aria-
live="polite">1</div>
</div>
</div>
Notes
• The buttons and number input are wrapped in a
<div> so they can be styled as a group underneath
the label.
• The button’s aria-label attribute ensures that
screen readers announce “Remove” instead of “mi-
nus symbol.” Same goes for “Add” instead of “plus
symbol.”
184 Chapter 3
You’ll notice that we’re using icons for the buttons. Icons
are often the source of heated debates amongst designers,
mostly because they have their pros and cons.
A Flight Booking Form 185
The cons are that icons can be misunderstood, and they are
a poor replacement for just using text. In “The best icon is a
text label,” Thomas Byttebier goes as far to say:19
What good has a beautiful interface if it’s unclear? Hence it’s simple:
19 http://smashed.by/texticons
186 Chapter 3
4. Choosing a Flight
Now all the relevant information has been collected, we can
give users a list of flights from which to choose one.
The system shows flights that match the date the user spec-
ified earlier. Additionally, the interface lets users move back
and forth between days. The group’s label is set as normal via
the <legend> and reads “Available flights on 18 August 2018.”
<div class="field-radioButton">
<label for="flight1">
<input type="radio" name="flight" value="1"
id="flight">
<span>Departing at 18:20pm</span>
<span>Arriving at 20:30pm</span>
<span>£99</span>
</label>
</div>
<fieldset aria-invalid="true">
<legend>
<span class="field-legend">
Available flights on 18 August 2018
</span>
<span class="field-error">
<svg width="1.5em" height="1.5em">
<use xmlns:xlink="http://www.w3.org/1999/xlink"
xlink:href="#warning-icon"></use></svg>
Choose a flight.
</span>
</legend>
<div class="field-radioButton">
<label for="flight1">
<input type="radio" name="flight" value="1" id="flight">
...
</label>
</div>
<div class="field-radioButton">
<label for="flight2">
<input type="radio" name="flight" value="2" id="flight2">
...
</label>
</div>
<div class="field-radioButton">
<label for="flight3">
<input type="radio" name="flight" value="3" id="flight3">
...
</label>
</div>
</fieldset>
A Flight Booking Form 189
5. Choosing A Seat
Choosing a seat isn’t the most complicated part of the jour-
ney, yet the combination of perceived affordance, layout,
and interaction design can make or break this part of the
journey if we’re not careful.
LAYOUT
Up to now, any field that uses radio buttons has them
stacked beneath one another, which is good for most situ-
ations. For the seat chooser, however, this makes the page
especially long, and — more importantly — harder to scan,
as there’s a lack of structure.
Left: seat checkboxes stacked beneath each other making the page
long. Right: seat checkboxes laid out in rows making the page shorter
and seats easier to find.
<label for="S1A">
<input type="checkbox" name="seat" value="1A" id="S1A">
<span class="plane-seatNumber">1A <span
class="vh">Window</span></span>
</label>
NESTED FIELDSETS
The radio buttons are placed inside an extra fieldset (and
legend) to indicate which class the seat belongs to: first
class, or economy. Visually this is fine, but screen readers
don’t always behave as expected. Sometimes, they announce
both legends when the first radio button is focused. Some-
times they don’t announce the outer legend at all. You can
192 Chapter 3
Class chooser with two radio buttons: economy, and first class.
20 http://smashed.by/fieldsetelements
A Flight Booking Form 193
“ tells us what they do and how to use them. That’s why checkboxes
are square and radio buttons are round. Their appearance isn’t just
for show—it signals what to expect from them. Making a checkbox
round is like labeling the Push side of a door Pull.
A radio button tells you that just one can be selected; check-
boxes tell you that more than one can. So if one person is
travelling, use radio buttons; otherwise, use checkboxes.
21 http://smashed.by/checkboxes
22 http://smashed.by/millerslaw
23 http://smashed.by/sevenchoices
194 Chapter 3
In our case, a Boeing 747 has over 400 seats. Call me a rebel,
but I’m struggling to see a better way of presenting fewer
seats. Choosing a seat is quite a unique interaction and ben-
efits from presenting this many choices in plain site.
UNAVAILABLE SEATS
Unavailable seats are marked by disabling the checkbox (or
radio button). Browsers will gray them out so that sighted
users know they aren’t selectable. Similarly, screen read-
ers won’t announce them, and keyboard users can’t focus
them. This is one of the few use cases where disabling
elements is appropriate.
LAYOUT ENHANCEMENTS
Laying out seats in rows can cause seats to wrap in small
viewports, which destroys the idea of laying them out as
modeled in real life. We could style the seats so they don’t
wrap, but this would cause horizontal scrolling. Neither of
these problems are deal breakers, but if we could reduce the
chance of this happening, we should.
<script>
document.documentElement.className = 'enhanced';
</script>
LIMITING SELECTION
If the user specified two travellers, we need a way to allow
users to select only two seats. There’s no way natively to limit
the amount of checkboxes the user can check. If a user selects
more than their quota, they’ll get an error message. Without
user research, it’s hard to know whether this is a problem. But
if it is, we can enhance the experience with JavaScript.
function SeatLimiter(max) {
this.max = max;
this.checkboxes = $('.plane-seat input');
this.checkboxes.on('click', $.proxy(this,
'onCheckboxClick'));
}
SeatLimiter.prototype. {
var selected = this.checkboxes.filter(':checked');
if(e.target.checked && selected.length > this.max) {
selected.not(e.target)[0].checked = false;
}
};
Summary
In this chapter, we continued to use the one thing per page
pattern which allowed us to make use of the total screen
estate. We looked at ways of reducing friction, not only
through interface design, but also by looking at the journey
as a whole.
198 Chapter 3
THINGS TO AVOID
• Nested fieldsets.
A Flight Booking Form 199
Demos
• Autocomplete:
http://smashed.by/autocompletedemo
• Memorable date:
http://smashed.by/memorabledatedemo
• Date picker:
http://smashed.by/datepickerdemo
• Stepper:
http://smashed.by/stepperdemo
• Seat chooser (nested):
http://smashed.by/seatchoosernesteddemo
• Seat chooser:
http://smashed.by/seatchooserdemo
200 Chapter 4
A Login Form
“As a user, I want to log in to [your service] so that I can [do stuff]”
— Nobody, ever!
N
obody wants to log in to your site. They’re forced
to as a security measure. Without it, everyone has
access to everyone else’s stuff. Bad.
Given how long login forms have been around for and how
basic they are in appearance, you’d be surprised at how
often they contain the same usability mistakes that stop
users doing something they don’t even want to do in the
first place. Add social login into the mix and things get
even harder.
The booking reference field without hint (left) and with hint (right).
A Login Form 203
Auto-Capitalization, Autocorrect
and Spell-Checking
Some Android and iOS browsers try to help users by auto-cap-
italizing words in text boxes (<input type="text">). For
example, if I type “adam,” it will be changed to “Adam,” which
can be helpful depending on the circumstance.
<input autocapitalize="none">
<input autocorrect="off">
<input spellcheck="false">
Auto-Tabbing
Some login forms, such as those found on bank sites, ask
users for certain characters of their password. Or they may
ask for certain digits from a security pin. In either case, users
are normally given three separate text boxes or select boxes.
Santander bank password field with separate three text boxes for each
requested character.
The first problem with this approach is that sites will auto-
tab between the fields. That is, focus is moved to the next
text box automatically as the user enters a predetermined
number of characters. But as the BBC’s UX guidance says:1
1 http://smashed.by/managingfocus
A Login Form 207
In this case, there’s just no good reason for it. And splitting
up a text box into three is unnecessary. A single, clearly
labelled text box lets users type three characters freely.
Password field with three separate text boxes (left) and a single text box (right).
2 http://smashed.by/autotabbing
208 Chapter 4
“Sign in” is perhaps more human than “Log in.” When you
visit a spa or office building, signing in grants you entry.
And you sign out as you leave. It’s usually sensible to use the
same language for digital experiences too.
We know which one doesn’t match, we’re just not going to tell you,
“ because our security people think that if we told you that it was the
password, they would know they had a legal username and they
would try every possible password in history.
3 http://smashed.by/onelineofcode
210 Chapter 4
Do: password error message “The password doesn’t correspond to your username.”
LAYOUT
When trying to perform an action anonymously that
requires being logged in, users will be sent to the login page.
Left: Tesco product list page. Right: Tesco login page with a different layout.
Left: the login page with a link to the register page. Right: the register page
with a link to the login page.
the placement of the link within the login form that can
cause usability issues. If the link is just above the password
field, when users tab from the email field, it’s the link that
will receive focus, not the password field. Some users will
tab and start typing, not realizing what’s happened.
Worse still is when the link is placed before the submit but-
ton. When keyboard and screen reader users tab from the
password field and press Enter, they’ll expect the form to
submit. But instead, they’ll be taken to reset their password.
When they realize what’s happened, they’ll need to go back,
reenter their credentials, and be careful not to make the
same mistake again.
Left: forgot password link between last field and submit button. Right: forgot
password link before the form.
A Login Form 215
Social Login
Sites have recently started to offer users the ability to log in
with social networks, such as Facebook, Twitter, and Google.
This saves users having to type credentials they may not
remember.
4 https://medium.com
216 Chapter 4
PRIVACY
Users are worried about their privacy. They don’t know
what you’ll do automatically, and they want to feel as
though their information is safe and any actions they per-
form are intended.
Medium does this well: on the login page it says, “We won’t
post without asking,” which puts users’ minds at ease.
Medium’s social login buttons explaining “We won’t post without asking.”
SEAMLESS INTERCHANGE
Some users won’t remember how they originally cre-
ated an account; therefore, they won’t know which login
method to choose.
Medium’s settings page lets users connect and disconnect different social
media accounts easily.
Summary
In this chapter we started by quashing traditional advice
that omitting hint text and explicit error messages improve
security on login forms. We then looked at some of the sub-
tle usability issues that can be introduced with social login.
Finally, we looked at ways of improving the experience for
keyboard and mobile users, which meant avoiding auto-tab-
bing, autocorrecting and auto-capitalizing input.
THINGS TO AVOID
Demo
• Log in form: http://smashed.by/loginformdemo
220 Chapter 5
An Inbox
M
y sister loves to-do lists. In fact, she loves them
so much, that one of her favourite things is mak-
ing new lists out of old ones. The world is full
of lists. There’s even a list of great people.1 On the web there
are several types of lists, and there are some design patterns
that have emerged over the years that help to manage them.
List Types
First, we’re going to look at how best to mark up a list of
emails. Discussing lists may seem out of place in a book
1 http://smashed.by/thegreat
2 http://smashed.by/inboxzero
An Inbox 221
DESCRIPTION LISTS
A description list (<dl>) — formerly called a definition list
— is for grouping a list of terms and corresponding defini-
tions; for example, product details such as size, price, and
material. As a list of emails isn’t a collection of terms and
definitions, this type of list isn’t appropriate.
<dl>
<dt>Size:</dt>
<dd>250cm × 135cm × 90cm</dd>
<dt>Price:</dt>
<dd>£429.95</dd>
<dt>Material:</dt>
<dd>Reclaimed teak</dd>
</dl>
222 Chapter 5
TABLES
A table (<table>) is an arrangement of data, laid out in rows
and columns. Like a spreadsheet, tables are well-suited for
data that needs to be compared, sorted, and totalled.
Gmail uses tables and puts recipient, subject, and date sent
into columns. Interestingly, though, there are no table head-
ings, which is the first clue that tables have been used for
layout purposes rather than their semantic qualities, which
causes various access issues. Jeremy Keith talks about this
in his book Resilient Web Design:3
3 http://smashed.by/tablelayout
An Inbox 223
<table class="inbox">
<a href="/email/1">
<tr>
<td>John Oates</td>
<td>Your Amazon.co.uk order #123 is out for
delivery</td>
<td>10 August</td>
</tr>
</a>
</table>
<ul class="inbox">
<li>
<a href="/emails/1/">
<div class="inbox-recipient">John Oates</div>
<div class="inbox-subject">Your Amazon.co.uk order
#123 is out for delivery</div>
<div class="inbox-date">10 August</div>
</a>
</li>
</ul>
An Inbox 225
<ul class="inbox">
<li>
<input type="checkbox" name="email">
<a href="/emails/1/">
<div class="inbox-recipient">John Oates</div>
<div class="inbox-subject">Your Amazon.co.uk order
#123 is out for delivery</div>
<div class="inbox-date">10 Aug</div>
</a>
</li>
...
</ul>
USING MODES
Trying to meet two user needs (viewing and managing) in
a single interface is partially responsible for the problem in
the first place. One way to avoid the issue would be to split
these needs apart using the concept of modes.
An Inbox 227
Top: inbox in read mode where each row is a link to read the email. Bottom: in-
box in manage mode where each row is a label that toggles the checkbox state.
Modes are best suited when one mode is used more fre-
quently than the other. But when both are used frequently,
like an inbox, having to switch back and forth all the time
may be undesirable.
<li>
<input type="checkbox" name="email" aria-
labelledby="inbox_label1">
<a href="/emails/1/" id="inbox_label1">
<div class="inbox-recipient">John Oates</div>
<div class="inbox-subject">Your Amazon.co.uk order #123
is out for delivery</div>
<div class="inbox-date">10 Aug</div>
</a>
</li>
An Inbox 229
<li>
<input type="checkbox" name="email" id="email1">
<label for="email1" class="visually-hidden">From John
Oates, subject ‘Your Amazon.co.uk order #123 is out for
delivery’ (10 August 2017)</label>
<a href="/emails/1/">
<div class="inbox-recipient">John Oates</div>
<div class="inbox-subject">Your Amazon.co.uk order #123
is out for delivery</div>
<div class="inbox-date">10 Aug</div>
</a>
</li>
Note: The CSS for the visually hidden class is set out in “A
Checkout Form.”
can create a specific message just for them. For example, the
label has the word “subject” prefixed, which is useful in this
context. This follows inclusive design principle 1, “Provide a
comparable experience,” which is not about giving users the
same experience, but one of comparable value and utility.
Actioning Emails
Letting users select multiple emails is all well and good, but
we’re going to want to facilitate actioning them too. This
form has three actions and, therefore, three submit buttons:
Archive, Delete, and Mark as spam.
232 Chapter 5
The best solution to this problem is to avoid it; that is, to have
just one action per form. Depending on the design, this may
not be easy, which is unfortunately the case with the inbox.
Gmail’s inbox screen showing a selected email with an additional menu now
available.
STICKY MENUS
The menu is placed above the list of emails; as users scroll, it
might disappear off screen. A sticky menu, however, would
stay on-screen as soon as the menu gets to the top edge of
the viewport.
Sticky menu in three states. Left: menu positioned above the content as normal,
before the page is scrolled. Center: the menu (not sticky) rolls off-screen after the
page has been scrolled. Right: a sticky menu still on-screen even after the page
has been scrolled.
Note: Where sticky menus are useful, you can use position:
sticky as a progressive enhancement. In the past, we had
to resort to complicated techniques that created jarring and
broken experiences across a range of mobile and tablet devices.4
4 http://smashed.by/fixedposition
236 Chapter 5
A Responsive Menu
When there’s enough space, the buttons should just be laid
out at all times, making them readily available and interac-
tive. But if you have more than three buttons in the menu,
or you need to display additional components along the
same row, it’s going to be hard to fit them on screen, espe-
cially on mobile.
Left: on mobile with menu buttons stacked. Right: on desktop with menu
buttons laid out in a row.
Select boxes are for input. That’s why forms that contain
select boxes — like any other input — must be accompanied
by a submit button to submit the choice. Not only is this
convention, but it’s also in the Web Content Accessibility
Guidelines (WCAG):5
5 http://smashed.by/constbehavior
An Inbox 239
Expanded select box with the first option selected. Pressing down immediate-
ly submits the second option when the user might have wanted to select the
third option.
form) until the user presses Space or Enter. But not all
browsers are alike nor implement the specification consis-
tently. Ignoring people who use one of the less forgiving
browsers doesn’t make the problem any less real.
The other problem with using a select box is that it’s always
collapsed, even when there’s enough space to lay out the
options. One solution is to use JavaScript to create a com-
pletely different component for big screens. This is known
as adaptive design.6
When the web came along, we settled on 640 pixel widths (as
computer monitors commonly supported this resolution).
Then a few years later, when larger monitors came to mar-
ket, we increased it to 800 and then 960 pixels. We no longer
cared about people with smaller monitors. We expected users
to maximize their browser window; if they didn’t, they’d get a
horizontal scroll bar, and that would be their problem.
More years passed. The mobile web was born. Or, more
accurately, we could use websites on our phones, which
happen to have small screens. A million devices came out.
6 http://smashed.by/rwdadaptive
An Inbox 241
Adaptive Design
7 http://smashed.by/adaptivedesign
242 Chapter 5
8 http://smashed.by/ress
An Inbox 243
Responsive Design
Left: select box menu for small screens. Right: menu buttons laid out in a row
for large screens.
In this case, the big screen view entirely discards the select
box in favour of a different interface using CSS and Java-
Script. We either have to change the HTML dynamically
with JavaScript, or we have to have both layouts in HTML,
ready to be enabled and disabled through a CSS breakpoint.
An Inbox 245
Not only is all of this more work, but the page will take
longer to load and there are now two vastly different varia-
tions of the same feature to maintain indefinitely. Adaptive
design should always be a last resort.
Third, not all users use a mouse (or other types of pointing
device) and touchscreen devices are usually operated with-
out one.
A TRUE MENU
Having explored the pitfalls of adaptive design and hover
menus, we can now safely proceed to design a true respon-
sive menu that opens on click.
An Inbox 247
Left: a collapsible menu for small screens. Right: a menu bar for large screens.
<div class="menu">
<div role="menu">
<input type="submit" name="archive" value="Archive"
role="menuitem">
<input type="submit" name="delete" value="Delete"
role="menuitem">
<input type="submit" name="spam" value="Mark as spam"
role="menuitem">
</div>
</div>
Notes
• The menu itself has role="menu" indicating that
it contains menu items. When a menu item is
focused, screen readers will announce it as a three-
item menu.
248 Chapter 5
Menu.prototype.setupResponsiveChecks = function() {
this.mql = window.matchMedia(this.mq);
this.mql.addListener($.proxy(this, 'checkMode'));
this.checkMode(this.mql);
};
Menu.prototype.checkMode = function(mql) {
if(mql.matches) {
this.enableBigMode();
} else {
this.enableSmallMode();
}
};
250 Chapter 5
<div class="menu">
<button type="button" aria-haspopup="true" aria-
expanded="false">
Actions
<span aria-hidden="true">▾</span>
</button>
<div role="menu">
<input role="menuitem" type="submit" name="archive"
value="Archive">
<input role="menuitem" type="submit" name="delete"
value="Delete">
<input role="menuitem" type="submit" name="spam"
value="Mark as spam">
</div>
</div>
An Inbox 251
Notes
• The aria-haspopup attribute indicates that the
button shows a menu. It acts as warning that, when
pressed, the user will be moved to the pop-up menu.
• The <span> contains the Unicode character for a
down arrow. Conventionally, this indicates vis-
ually what aria-haspopup does non-visually —
that pressing the button reveals something. The
aria-hidden="true" attribute prevents screen
readers from announcing “down pointing triangle”
or similar. Thanks to aria-haspopup, it’s not need-
ed in the non-visual context.
• The aria-expanded attribute tells users whether
the menu is currently expanded (open) or collapsed
(closed) by toggling between true and false
values.
9 http://smashed.by/rwdjs
252 Chapter 5
Menu.prototype. {
if(this.menuButton.attr('aria-expanded') == 'false') {
this.showMenu();
this.menu.find('input').first().focus();
} else {
this.hideMenu();
this.menuButton.focus();
}
};
[aria-expanded="true"] + [role=menu] {
display: block;
}
[aria-expanded="false"] + [role=menu] {
display: none;
}
Select All
Users may want to archive every email in their inbox.
Rather than selecting each email one by one, we can provide
a more convenient method. One way to service this func-
tionality is through a special checkbox, placed at the top and
in vertical alignment with the other checkboxes, creating a
visual connection. Clicking it would check every checkbox
in one fell swoop.
Success Messages
When the user submits the form, the selected emails will
disappear from the inbox. When an action has been com-
pleted, telling users is the respectful thing to do. Not doing
so leaves users wondering what happened, if anything.
Both the error and success message panels are placed within
the natural flow of the document and toward the top of the
page to indicate their importance. The role="alert" attri-
bute ensures screen readers will announce it when the page
loads or if it is updated on the client.
TOAST MESSAGES
Some applications employ what is known as a “toast” mes-
sage or notification. When the application needs to notify
users, a little (non-modal) dialog will pop up on the page — a
bit like a piece of toast from a toaster. Then, after a certain
amount of time, the notification disappears automatically,
usually with a fading animation.
Summary
In this chapter we began by choosing the right way to
present a collection of emails and the impact of combining
two disparate modes — reading email and actioning it —
into one interface.
THINGS TO AVOID
DEMOS
• Inbox: http://smashed.by/inboxdemo
262 Chapter 6
A Search Form
I
’m an organized person. Even as a boy, I remember
always having a place for things. To be fair, I’ve always
been minimalist too. Organizing when you only own a
few things is easy. So it’s no surprise I rarely lost things. On
the odd occasion I did, I just shouted in the general direc-
tion of the resident search engine: “Where’s my…,” and I’d
have my answer.
Search Everything
Not only was I able to ask Mum where my stuff was, really
I was able to ask her anything. When designing a global
search form, users should be able to do the same thing. Too
often, users can only find stuff that lives in the database.
On Amazon, search will only return products. On YouTube,
search will only return videos.
1 http://smashed.by/contentanddesign
264 Chapter 6
Left: generic search label “Search.” Right: specific search label “Search products.”
A search form.
<div role="search">
<form>
<div class="field">
<label for="search">
<span class="field-label">Search</span>
</label>
<input type="search" id="search" name="search">
</div>
<input type="submit" value="Search">
</form>
</div>
Notes
• As noted in chapter 3, “A Flight Booking Form,” the
search input (<input type="search">) lets users
clear the field more conveniently than a standard
text box, by clicking the delete cross or by pressing
Escape.
266 Chapter 6
There’s No Room
Typically, search is placed within the header. Like naviga-
tion, this makes it easily discoverable and quick to access.
Putting such an integral feature elsewhere on the page
would be counterintuitive and unconventional.
2 http://smashed.by/searchrole
3 http://smashed.by/hamburgermenu
A Search Form 267
the issue of space isn’t much of, well, an issue — there’s usu-
ally plenty of room. On mobile, though, we’re going to have
to think a bit more. There’s only so much space available to
play with.
Left: submit button below the search field. Right: submit button next to the
text box to save vertical space.
268 Chapter 6
<div class="field">
<label for="search" class="visually-hidden">
<span class="field-label">Search</span>
</label>
<input type="search" id="search" name="search">
</div>
Note: The CSS for the visually hidden class is set out in
chapter 2, “A Checkout Form” (page 116).
A Search Form 269
Also, if you recall the hint and error patterns from “A Registra-
tion Form,” (on page 56) the text is injected into the <label>.
If you need to show this information, you’ll have to come up
with a new solution, which seems unnecessary. Besides, hid-
ing the label doesn’t usually save enough space to fit the form
inside the header anyway, especially in smaller viewports.
Medium’s search form lacks a submit button but has a magnifying glass icon
4 http://smashed.by/notallblind
A Search Form 271
<header>
...
</header>
<div role="search">...</div>
272 Chapter 6
<header>
...
<button type="button" aria-haspopup="true" aria-
expanded="false">Search form</button>
</header>
<div role="search" class="hidden">...</div>
Notes
• A toggle button is injected into the header.
• The search form is hidden using the hidden class as
introduced in chapter 1.
• The aria-haspopup attribute indicates that the
button reveals a part of the interface (the search
form). It acts as warning that, when pressed, the
focus will be moved to the search form.
• The aria-expanded attribute tells screen reader us-
ers whether the search form is currently expanded
or collapsed by toggling between true and false
values.
A Search Form 273
A SMALL SCRIPT
function SearchForm() {
this.header = $('header');
this.form = $('.searchForm');
this.form.addClass('hidden');
this.button = $('<button type="button" aria-
haspopup="true" aria-expanded="false">
<img src="/public/img/magnifying-glass.png" width="20"
height="20" alt="Search products"></button>');
this.button.on('click', $.proxy(this, 'onButtonClick'));
this.header.append(this.button);
}
SearchForm.prototype. {
if(this.button.attr('aria-expanded') == 'false') {
this.button.attr('aria-expanded', 'true');
this.form.removeClass('hidden');
this.form.find('input').first().focus();
} else {
this.form.addClass('hidden');
this.button.attr('aria-expanded', 'false');
}
};
Notes
• The constructor is responsible for enhancing the
HTML and listening to the button’s click event.
• When the button is clicked, the script checks the
aria-expanded attribute to see if the form is cur-
rently expanded or collapsed.
274 Chapter 6
Summary
In this chapter we started by looking at how important it
is to give users what they searched for — not just products,
or articles, but anything the site contains.
5 http://smashed.by/infinitescrolling
276 Chapter 6
CHECKLIST
Demos
• Search form: http://smashed.by/searchformdemo
278 Chapter 7
A Filter Form
I
n the introduction to “A Search Form” you’ll recall the
type of conversation I used to have with Mum. Some-
times 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.
“ 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
1 http://smashed.by/facetedsearch
2 http://smashed.by/applyingfilters
280 Chapter 7
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.
A Filter Form 281
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.
3 http://smashed.by/cssbackground
A Filter Form 283
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 check-
boxes means users wouldn’t expect clicking a filter would
immediately request the new results. That’s materially
dishonest and therefore deceptive.
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 focus-
ing 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.
A Filter Form 285
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?">
286 Chapter 7
<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>
A Filter Form 287
<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 pro-
vide 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.
288 Chapter 7
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.
290 Chapter 7
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.
4 http://smashed.by/endofwebdesign
A Filter Form 291
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.
5 http://smashed.by/positionsticky
292 Chapter 7
function FilterRequester() {
this.form = $('.filter form');
this.form.find('[type=submit]')
.addClass('visually-hidden')
.attr('tabindex', '-1');
}
function FilterRequester() {
//...
this.form.find('[type=radio], [type=checkbox]').
on('change', $.proxy(this, 'onInputChange'));
}
FilterRequester.prototype. {
this.form.submit();
};
6 http://smashed.by/constbehavior
A Filter Form 295
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.
FilterRequester.prototype. {
var data = this.form.serialize();
this.requestResults(data);
};
FilterRequester.prototype.requestResults = function(data)
{
$.ajax({ data: data, ...});
}
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 prog-
ress bar with Ajax.
7 http://smashed.by/macysfilter
8 http://smashed.by/historyapi
298 Chapter 7
FilterRequester.prototype. {
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. > 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.
A Filter Form 299
function FilterRequester() {
//...
$(window).on('popstate', $.proxy(this, 'onPopState'));
}
FilterRequester.prototype. {
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.
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.
9 http://smashed.by/inlinescroll
A Filter Form 301
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 con-
tent after only 20k is received.
10 http://smashed.by/fastercontent
302 Chapter 7
As Ajax has to wait for the entire 100Kb before showing any-
thing, 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.
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
A Filter Form 303
<fieldset class="field">
<legend>
<span class="field-legend">Color</span>
</legend>
<div class="field-options">
<!-- checkboxes here -->
</div>
</fieldset>
304 Chapter 7
<fieldset class="field">
<legend>
<button type="button" aria-expanded="false">Color</button>
</legend>
<div class="field-options hidden">
<!-- checkboxes here -->
</div>
</fieldset>
STATE
The checkboxes are hidden by the hidden class, as first
explained in chapter 1, “A Registration Form.” Removing it
will reveal the checkboxes.
A Filter Form 305
[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');
A Filter Form 307
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. {
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 con-
text of desktop-sized screens, where there’s enough space to
fit the filter next to the results. But what about the small-
screen experience?
308 Chapter 7
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 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.
310 Chapter 7
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.
11 http://smashed.by/mobilefacetedsearch
A Filter Form 311
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 the heading.
This means the heading will be announced for
screen reader users who have tabbed to (focusable)
links.
312 Chapter 7
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.
THINGS TO AVOID
Demos
• Filter Form: http://smashed.by/filterformdemo
314 Chapter 8
An Upload Form
The web is more than just text. Whether it’s sending a CV
to a recruiter by email, or adding photos to an eBay advert,
we need to let users upload files. Forms have this capability
baked in, of course.
A File Picker
A file picker (<input type="file">) is another type of
form control. When clicked, it will spawn a dialog that lets
users browse files on their computer or device. Once a file is
selected, the dialog closes and the picker updates to reflect
the file has been chosen.
An Upload Form 315
Left: a file picker without a file selected. Right: a file picker with a file selected.
<form enctype="multipart/form-data">
<div class="field">
<label for="documents">
<span class="label">Choose file</span>
</label>
<input class="field-file" type="file" id="file" name="file">
</div>
<input type="submit" value="Upload" name="upload">
</form>
Notes
<div class="field">
<label for="file">
<span class="label">Choose file</span>
</label>
<input class="visually-hidden" type="file" id="file"
name="file">
</div>
Now that it’s hidden, we can style the control’s label, which
is easy to style. As described in “A Registration Form,” this
works because a control’s label acts as a proxy to the control
itself: clicking the label is like clicking the input.
Left: a label styled as normal. Right: the modified label styled as a button.
Focus States
As the input is visually hidden, the user won’t get any feed-
back that it’s in focus when they tab to it. To do this, we can
use JavaScript to add a class of focused to the label when
the input is focused, which will allow us to style it:
.focused {
/* focus styles */
}
When the user selects a file from the dialog, it’s the input
that will change state (as shown earlier). To reflect the cho-
sen file, we’ll need to update the label text when the input’s
onchange event fires.
An Upload Form 319
$('[type=file]').on('change', function(e) {
// change label
});
Left: the button-styled label before file selection. Right: after selection.
Pitfalls
Multiple file form with extra screen to let users continue adding files.
Not only does this design let users upload multiple files in
unsupported browsers, but it also lets the user review their
submission, which is a useful addition regardless.
An Upload Form 323
Left: a persistent upload form before a file has been uploaded. Right: the same
form with a file uploaded and the upload form beneath.
324 Chapter 8
THE MARKUP
<form enctype="multipart/form-data">
<div class="field">
<label for="documents">
<span class="label">Attach file</span>
</label>
<input class="field-file" type="file" id="documents"
name="documents" multiple>
</div>
<input type="submit" value="Upload" name="upload">
</form>
A Drag-and-Drop Enhancement
As noted earlier, the native file input acts as a drop zone to
let users drag and drop files. However, there are two prob-
lems with it.
An Upload Form 325
1 http://smashed.by/dragdropupload
An Upload Form 327
<form class="dropzone">
<div class="field">
<label for="files">Upload file</label>
<input type="file" name="files" id="files" multiple>
</div>
</form>
Notes
• The button has been removed because the files will
be uploaded with Ajax onchange.
• The “dropzone” class exists as a way to target this
particular form for enhancement.
328 Chapter 8
Left: drop zone. Right: drop zone while file is being dragged over it.
Dropzone.prototype. {
e.preventDefault();
this.dropzone.addClass('dropzone-dragover');
};
Dropzone.prototype. {
this.dropzone.removeClass('dropzone-dragover');
};
.dropzone-dragover {
/* styles here */
}
An Upload Form 329
Notes
DROPPING FILES
Next we need to handle the file drop, which we can do by
listening to the ondrop event.
Dropzone.prototype. {
e.preventDefault();
this.dropzone.removeClass('dropzone-dragover');
$('.fileList').removeClass('hidden');
this.uploadFiles(e.originalEvent.dataTransfer.files);
};
Notes
• e.preventDefault() is called to allow the file to
be dropped onto the drop zone. Without this, the
browser will attempt to load the file instead.
330 Chapter 8
Dropzone.prototype.uploadFiles = function(files) {
for(var i = 0; i < files.length; i++) {
this.uploadFile(files[i]);
}
};
Dropzone.prototype.uploadFile = function(file) {
var formData = new FormData();
formData.append('documents', file);
$.ajax({
data: formData
url: '/ajax-upload',
An Upload Form 331
type: 'post',
processData: false,
contentType: false
});
};
Property Description
data The data constructed with FormData.
type Set to “post” because data is being sent.
url The URL/endpoint for which the server will
process the uploaded files.
processData Set to false, which tells jQuery not to
convert the data into a query string. This is
important as we’re sending files, not just text.
contentType Set to false, which tells jQuery not to
override the automatically created header
appropriate for sending files. 2
2 http://smashed.by/multipartform
332 Chapter 8
FEEDBACK
It’s all well and good having uploaded the files to the server,
but at this moment the user hasn’t been given any feedback
as to what’s happened. Perhaps the file couldn’t be uploaded,
for example. There are a number of times we need to give
users feedback: during upload, on success, and on error.
Progress
<ul>
<li>
<span class="file-name">file.pdf</span>
<progress max="100" value="80">80% complete</progress>
</li>
<li>
<span class="file-name">file.pdf</span>
<progress max="100" value="50">50% complete</progress>
</li>
</ul>
$.ajax({
xhr: function() {
var xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) {
var percentComplete = e.loaded / e.total;
percentComplete = parseInt(percentComplete * 100);
li.find('progress')
.prop('value', percentComplete)
.text(percentComplete + '%');
}
}, false);
return xhr;
}
});
334 Chapter 8
The progress bar’s inner text is also set. This is so users with
a browser that lacks support for the <progress> element
can still see it. That’s inclusive.
Success
<li>
<a class="file-name" href="/path/to/file.pdf">file.pdf</
a>
<span class="success">
<svg width="1.5em" height="1.5em">
<use xmlns:xlink="http://www.w3.org/1999/xlink"
xlink:href="#tick"></use>
</svg>
File uploaded
</span>
<input type="submit" name="remove1" value="Remove">
</li>
$.ajax({
success: $.proxy(function(response){
if(response.file) {
li.html(this.getSuccessHtml(response.file));
}
}, this)
});
the file. This is used to create the HTML that is injected into
the list item.
Error
File list with unsuccessfully uploaded files marked with error messages.
3 http://smashed.by/multer
4 http://smashed.by/expressjs
An Upload Form 337
<li>
<span class="file-name">file.pdf</span>
<span class="error">
<svg width="1.5em" height="1.5em">
<use xmlns:xlink="http://www.w3.org/1999/xlink"
xlink:href="#warning-icon"></use>
</svg>
File.pdf is too big.
</span>
<button type="button">Remove</button>
</li>
$.ajax({
success: $.proxy(function(response){
if(response.error) {
li.html(this.getErrorHtml(response.error));
} else if(response.file) {
li.html(this.getSuccessHtml(response.file));
}
}, this)
});
When Description
Upload starts Uploading files. Please wait.
Upload completes [Name of file] has been uploaded.
Upload fails For example: [Name of file] is too big.
The size must be less than 2Mb.
function dragAndDropSupported() {
var div = document.createElement('div');
return typeof div.ondrop != 'undefined';
}
function formDataSupported() {
return typeof FormData == 'function';
}
function fileApiSupported() {
var input = document.createElement('input');
input.type = 'file';
return typeof input.files != "undefined";
};
if(dragAndDropSupported() && formDataSupported() &&
fileApiSupported()) {
var Dropzone = function(container) {
//...
};
}
5 http://smashed.by/ieinput
6 http://smashed.by/selectevent
An Upload Form 341
Other Considerations
There’s a number of additional design considerations for
uploading files, some of which are deep topics in their own
right. Let’s run through them quickly now for completeness.
7 http://smashed.by/ieinput
342 Chapter 8
Upload form with a field hint explaining that “Your photo may be in your
Pictures, Photos, Downloads or Desktop folder. Or in an app like iPhoto.”
For services that don’t require being logged in, such as the
passport renewal service, it’s trickier. In such cases, consider
directing users to their phone with one-time security codes
or unique URLs that they can type easily into their phone.
THIRD-PARTY INTEGRATION
Some digitally savvy users may already use third-party
services, such as Dropbox, to store files. Giving users a way
to upload or provide files from these services may well be
easier for them, especially if your service is already con-
nected with theirs.
iOS (left) and Android (right) dialogs for selecting images on a device when
the accept attribute is used.
Summary
In this chapter we began by looking at the native file picker
as the browser gives us quite a bit of power for free. How-
ever, we also looked at the various problems that crop up
with multiple file uploads.
THINGS TO AVOID
Demos
• Dropzone: http://smashed.by/dropzonedemo
350 Chapter 9
An Expense Form
As a self-employed freelancer I have to submit expenses
for my tax return. It’s a pain but if I do it correctly I get tax
breaks. The problem is that I have so many expenses to
enter and a limited amount of time to enter them.
There are a number of other forms on the web that use the
persistent form pattern. For example, GitHub’s “Add collab-
orators” form and the infamous to-do list form that many
JavaScript frameworks use to demonstrate their approach.1
This pattern works for adding expenses too. Each time the
user submits an expense, it will be added to the list that sits
above the form.
1 http://smashed.by/todomvc
352 Chapter 9
Left: expense form before adding an expense. Right: same form with a list of
added expenses above.
In this case, the one thing per page pattern (first discussed
on page 70) is more suitable. This is because it presents one
question at time, meaning we can show users different
pages depending on their answers. This solves the branch-
ing problem elegantly and simply, but what if users need
to enter multiple expenses and submit them in one go?
We can add an additional screen to the end of the journey
asking users if they’d like to add another expense. Selecting
yes takes the user down the same flow again; selecting no
completes the task.
354 Chapter 9
The add expense flow using “one thing per page,” with a question at the end
of the flow asking if the user wants to add another one.
HOW IT WORKS
The form starts with enough fields to enter one expense.
However, there’s an Add Another button, that when pressed,
will instantly clone the fields so that users can enter the
details of an additional expense.
An Expense Form 355
Left: add expense form with “Add another” button. Right: after having clicked
the button to clone the fields allowing the user to add multiple expenses.
<form>
<div class="addAnother">
<div class="addAnother-item">
<div class="field">
<label for="items[0][description]">
<span class="field-label">Description</span>
</label>
<input
type="text"
id="items[0][description]"
name="items[0][description]">
</div>
<div class="field">
<label for="items[0][amount]">
<span class="field-label">Amount</span>
</label>
<input
type="text"
id="items[0][amount]"
name="items[0][amount]">
</div>
</div>
<input type="submit" name="addAnother" value="Add
another expense">
</div>
<input type="submit" name="submitexpenses" value="Submit
expenses">
</form>
An Expense Form 357
2 http://smashed.by/expressjs
3 http://smashed.by/templateliterals
4 http://smashed.by/template
An Expense Form 359
function AddAnotherForm(container) {
container.on('click', '.addAnother-addButton',
$.proxy(this, 'onAddButtonClick'));
}
AddAnotherForm.prototype. {
var item = this.getNewItem();
this.getItems().last().after(item);
};
AddAnotherForm.prototype.getNewItem = function() {
return this.getItems().first().clone();
};
AddAnotherForm.prototype.getItems = function() {
return this.container.find('.addAnother-item');
};
There are three small functions that have been split out for
readability and maintainability. When the button is clicked,
we get a clone of the first <div class="addAnother-item>
in the form. Then we add the clone to the end of the form.
AddAnotherForm.prototype. {
// previous code
var firstItem = this.getItems().first();
if(!this.hasRemoveButton(firstItem)) {
this.createRemoveButton(firstItem);
}
};
AddAnotherForm.prototype.hasRemoveButton = function(item) {
return item.find('.addAnother-removeButton').length;
};
AddAnotherForm.prototype.createRemoveButton =
function(item) {
item.append('<button type="button" class=" addAnother-
removeButton">Remove</button>');
};
AddAnotherForm.prototype.getNewItem = function() {
var item = this.getItems().first().clone();
if(!this.hasRemoveButton(item)) {
this.createRemoveButton(item);
}
return item;
};
But how can our script know what name and id values to
use? To make this easy, we’ll store the naming convention
inside data attributes.
The reason for both the name and the id data attributes
is because some fields consist of multiple inputs with
the same name. For example, as laid out in chapter 2, “A
Checkout Form,” the name of each radio button is the same
because it identifies the set to which they belong. The id
identifies the individual radio button.
AddAnotherForm.prototype.updateAttributes = function(index,
item) {
item.find('[data-name]').each(function(i, el) {
el.name = $(el).attr('data-name').replace(/%index%/,
index);
el.id = $(el).attr('data-id').replace(/%index%/,
index);
($(el).prev('label')[0] || $(el).parents('label')[0]).
htmlFor = el.id;
});
};
Left: clicking the second description field sets focus to the first description
field erroneously.
Managing Focus
AddAnotherForm.prototype. {
// code
item.find('input, textarea, select').first().focus();
};
REMOVING AN EXPENSE
When the user clicks an item’s remove button, there’s a
number of tasks we need to implement. First, of course, is
that it should be removed from the form.
function AddAnotherForm(container) {
// code
this.container.on('click', '.addAnother-removeButton',
$.proxy(this, 'onRemoveButtonClick'));
}
AddAnotherForm.prototype. > {
$(e.currentTarget).parents('.addAnother-item').remove();
};
AddAnotherForm.prototype. > {
$(e.currentTarget).parents('.addAnother-item').remove();
var items = this.getItems();
if(items.length === 1) {
items.find('.addAnother-removeButton').remove();
}
};
AddAnotherForm.prototype. > {
// code
var items = this.getItems();
// code
items.each($.proxy(function(index, el) {
this.updateAttributes(index, $(el));
}, this));
};
This loops through all the items in the form and invokes the
updateAttributes() method from earlier.
Managing Focus
[...] browsers don’t know where to place focus when it has been
5 http://smashed.by/a11ytodolist
An Expense Form 367
able element. Some flip out completely and default to focusing the
outer document — meaning keyboard users have to crawl [...] back
to where the removed element was.
AddAnotherForm.prototype. > {
// code
this.container.find('.addAnother-heading').focus();
};
<h1 tabindex="-1">Expenses</h1>
FEEDBACK
As it stands, the act of adding and removing expenses
provides sufficient feedback for sighted users: items in
the form can be seen to appear or disappear from the list.
Giving users an additional notification bar would draw
users’ attention in two directions. And as more expenses
are added, the notification bar will be out of the viewport so
users wouldn’t see it anyway.
Screen reader users are also catered for because the act
of adding and removing items moves the users focus and
announces the focused element accordingly. This may not
tell the user explicitly that the item has been added, but it
should be enough. If research shows otherwise, you can add
An Expense Form 369
But all too often, designers want to add animation for the sake
of it. Users just want to get things done. Needless animation
is jarring and actually detracts from the user experience. Like
anything else, animation should only be used if it adds value.
6 http://smashed.by/needlessanimations
370 Chapter 9
Summary
In this chapter we looked at three different patterns that
let users submit multiple expenses into a system. Really
though, expenses were used just for demonstration pur-
poses — these patterns are applicable to all kinds of data, not
just expenses.
The persistent form and one thing per page patterns are more
suitable for infrequent use and users with a lower digital
literacy. The add another pattern is more suitable for frequent
usage that doesn’t require branching.
THINGS TO AVOID
Demos
• Add another: http://smashed.by/todolistdemo
372 Chapter 10
D
ifferent types of tasks take different amounts of
time to complete. The one thing per page pattern
(introduced in chapter 2, “A Checkout Form”) helps
users complete tasks in one sitting, but what about those
that take hours, or even days, to complete?
How can we design forms that play nicely with this complex
and long-form process that can take weeks to complete? And
by different people across digital and non-digital journeys?
The first page of the HSBC mortgage application process explaining what
users need to know before applying.
1 http://smashed.by/renewpassport
A Really Long and Complicated Form 375
Renew Your Passport service asking questions to determine the user’s best
course of action. The final screen tells users what to do having told the service
that their password has been stolen.
If the applicant has lost their passport, for example, then they
aren’t able to use the main service. The Government Digital
Service calls this “Check Before You Start,” which you can
read about in “We’ve published the check before you start
pattern” by Harry Trimble and Rob Le Quesne.2
2 http://smashed.by/govukchecklist
3 http://smashed.by/smallgoals
376 Chapter 10
That’s not all checklists are good for. In The Design of Everyday
Things, Don Norman says:
The GDS task list pattern with all tasks marked as completed.
4 http://smashed.by/govuktasklist
378 Chapter 10
MailChimp’s campaign task list page showing that two tasks still need to be
completed
The exact design details you choose will come down to your
product’s design language and user research, but it’s key to
ensure that:
ADDITIONAL CONSIDERATIONS
The points discussed above are probably applicable to any
very long form you’re designing, but you might also want
to consider:
Summary
In this chapter we looked at two important patterns. The
check before you start pattern makes a process transparent
and saves users a lot of time. It can even be designed for a
range of personal circumstances by asking users a series of
questions about themselves.
These patterns can make a very long and very complex jour-
ney relatively easy to complete.
A Really Long and Complicated Form 381
CHECKLIST
One of the best things about this topic is that there are
always going to be new challenges to solve and new pat-
terns to define. If you can think of a new form problem
you’d like me to look at, please send me a message on
Twitter (@adambsilver). You never know — with enough new
problems, a sequel could be on the cards.
382 Index
• Design Systems
by Alla Kholmatova
• Digital Adaptation
by Paul Boag