Creating Secure Password Resets With JSON Web Tokens

When a user of your application has forgotten their password, it can and should be reset securely. To accomplish a secure password reset, I will demonstrate how to use JSON Web Tokens (JWT) to generate a URL-safe token. The JWT contains encoded information about the user and a signature that, when decoded, is validated to ensure that the token has not been tampered with.

Once the JWT is validated, your application can securely allow the user to generate a new password, instead of sending them their forgotten one.

“Why Can’t I Just Send The User Their Password?”

There was a time when your password was stored in your favorite website’s database just as you typed it. In fact, it still seems to occur far too often. An entire website is dedicated to telling people whether their email address or username has been exposed.

In those days (and I use the past tense loosely), when a user forgot their password, they would arrive on a page that asked for their username or email address. The website would then send them an email “reminding” them of their password. This should be a red flag to you, as both a user of the website and as a developer. Either your password is stored in plain text or it can be decrypted, instead of having the much stronger, more secure one-way encryption.

Because (secure) passwords cannot be decrypted, that leaves us with one of two common choices when a user forgets their password:

  1. Generate a new, temporary password and send it via email.
  2. Generate an email that contains a one-time-use link within the contents of the email, which will take the user to a page where they can enter a new secure password.

Both options send out an email, which in the long term should not be considered a secure storage medium. With the first option, the password is shown in plain text. If the user were to leave this email in their inbox as their method of remembering their password (especially because they didn’t choose it), it would be almost as insecure as writing down their password on a sticky note and leaving it beside their computer. OK, not that bad, but you get the idea.

Another concern with option one is that a malicious user who knows their email address could easily lock out a user from the website by resetting their password. If the malicious user repeated this over and over again, it would make it almost impossible for the user to ever log in again because their password would never remain the same.

Password-Reset Process Overview

The goal of this tutorial isn’t to learn how to secure your users’ passwords in your database; you’ve already done that! This tutorial will show you how to reset the password of a user who has forgotten theirs by generating a special link that enables them to securely reset their password. The link will look similar to the following example:

http://localhost:3000/resetpassword/1/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjF9.uKe3CzH_g6oHxlFstQ1BL_Q8_zJKPyJ0dUvZkJsRKBg

Contained within this link is a special JWT that is used to securely validate the user who is trying to reset their password.

By the end of this tutorial, I will have walked you through creating an application that contains the following functionality:

  • We’ll have a form that accepts the email address of a user who has forgotten their password.
  • We’ll create a link with a JWT token embedded in the URL. The user will click this link and be allowed to reset their password.
  • We’ll create a page for resetting the password. This page will require the token and will decode it to ensure it is valid.
  • When the token has been successfully validated, a form will be displayed allowing the user to reset their password.

The following is an application diagram that demonstrates what the user does and how the server processes and responds to each action initiated by the user.

Password-reset workflow
Application diagram of password-reset workflow

I mentioned earlier that email should not be considered secure for long-term storage. To help prevent this issue with option two, the link contained in the email is to be used once. Once the user has clicked the link and changed their password, if they (or a malicious person) were to click the link again, it would not be valid and the user would be unable to change their password. The user would, thus, be forced through option two again: generating a new email with a new one-time-use link.

This solution also prevents the secondary negative side effect of option one. If a malicious user were to attempt to constantly reset the user’s password, the original password would be unaffected and the user would never be locked out.

Before creating the application, let’s better understand what JWTs are and learn how to create, encode and decode them.

What Are JSON Web Tokens?

A JSON Web Token (JWT), in its simplest form, is a URL-safe string that contains an encoded JSON object. JWTs are an open industry standard that are fully described in RFC 7519, which contains an immense amount of detail, specifically regarding how JWT claims function to ensure the security of a generated token. Feel free to peruse the full RFC specifications at your leisure.

Let’s look at an example token:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjF9.uKe3CzH_g6oHxlFstQ1BL_Q8_zJKPyJ0dUvZkJsRKBg

Notice that the token contains two periods (.) separating the three pieces of the outputted token, those three pieces being the following:

  • header

    The header contains information that identifies what the hashing algorithm is, so that it can be used to properly decrypt and validate the signature.
  • payload

    This contains the information you wish to send with your JWT. Note that the payload is not secure and can be decoded without a secret key. JWTs are not meant to send sensitive information, such as passwords or credit card numbers.
  • signature

    The signature combines the encoded header and the payload with a secret key and securely encodes it using the hashing algorithm defined in the header — for example, HMAC with SHA-256.

To summarize, each time you generate a token:

  • the header will remain constant (assuming you do not change the hashing algorithm);
  • the payload will remain constant when the payload to encode is the same;
  • the signature will encrypt these two pieces of information based on the hashing algorithm with a secret key. This means that if you do not generate a unique secret key or change the payload, then the signature will also remain the same.

Encoding And Decoding JWTs

We are going to create a new application to demonstrate the basics of encoding and decoding tokens. Once we have a solid understanding of JWTs, we are going to recreate the application and I’ll demonstrate how to securely reset a user’s password.

To begin, please ensure you have Node.js installed. If you do not have it installed, I suggest visiting the download page and selecting the appropriate installer for you.

Our new application will be named “passwordreset.” In a command prompt, I ran the following commands to create a basic application. Ensure that you start in the current working directory of where you wish to host your Node.js application.

mkdir passwordresetcd passwordresetnpm init

The npm init process asks a lot of questions to help you customize your final package.json file. In my case, I have left everything as their defaults.

Creating Our First JWT

To make generating JWTs easy, we are going to use an existing npm package named JWT Simple, which will obfuscate a lot of the complexities of encrypting and decrypting a token.

To install the package, in your command prompt where your application resides, enter the following command:

npm install jwt-simple --save

In this first code example, I have created a new index.js file, which creates a JavaScript object that I encrypted into a JWT:

var jwt = require('jwt-simple');var payload = { userId: 1 };var secret = 'fe1a1915a379f3be5394b64d14794932';var token = jwt.encode(payload, secret);console.log(token);

Let’s look at what is happening. The application begins by including the JWT Simple module. We then create a payload object. This object is what we will be encoding inside the token. We have created an object that contains a single property, named userId. I’ve used a hardcoded value of 1.

A token needs to be encrypted (and decrypted) with a secret key. I’ve generated a random string that will be used each time (in this sample application).

With the prerequisites set, we are finally able to create our token. This is done by calling the encode function from the JWT Simple module. This function accepts our payload and the secret key. The result of this function is our URL-friendly token, which contains our encoded header, payload and signature. The final line outputs our token to the console.

Running our application will output the following:

node index.jseyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjF9.uKe3CzH_g6oHxlFstQ1BL_Q8_zJKPyJ0dUvZkJsRKBg

As you might have observed, this is the same token from earlier that I broke apart and whose three parts I described (header, payload and signature). Let’s now update our index.js file to decode the token and log it to the console:

var decode = jwt.decode(token, secret);console.log(decode);

Now, when we run the application, we receive the following output:

node index.jseyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjF9.uKe3CzH_g6oHxlFstQ1BL_Q8_zJKPyJ0dUvZkJsRKBg{ userId: 1 }

Yep, our token was successfully decoded and contains our userId property, with the correct value of 1!

If the token was tampered with and any of the three parts were not able to be decoded and decrypted, then the JWT Simple module would throw exceptions.

Resetting The User’s Password

Let’s put our JWT knowledge to good use and create the final application, allowing the user to reset their password. To focus on the one-time-use password-reset link, we will not implement a database or an email. Nevertheless, our application will contain the following functionality, with several comments about where the application could be enhanced to integrate those features:

  • The application will display a form that accepts the user’s email address.
  • It will handle the form’s POST with the user’s email address.
  • This will create a link, with a JWT token embedded in the URL. The user will click this link and be allowed to reset their password.
  • The application will create a password-reset page. This page will require the token and will decode it to ensure it is valid.
  • If successful, a form will be displayed allowing the user to reset their password.
  • The application will handle the form’s POST with the user’s new password.
  • This page will also decode and validate the token before saving the new password.

It’s now time to create the application to reset the user’s password, leveraging JWTs to validate the user throughout the process.

To handle the HTTP communication, we are going to use the Express module. We will also be using the BodyParser module to parse the content from our form’s POSTs.

These can be installed by running the following commands in your project’s working directory:

npm install express --savenpm install body-parser --save

We will be pseudo-coding the spots where we would be leveraging a database and sending emails, in order to keep this article focused on how JWTs are used throughout the password-reset process. I am going to repurpose my previously created index.js file for the final application.

The following code examples will all be subsets of my full index.js file, allowing me to incrementally demonstrate the process that I am building.

The first thing we need to do is include the required modules and create a web server that allows the user to reset their password:

const express = require('express');const bodyParser = require('body-parser');const jwt = require('jwt-simple');const app = express();app.use(bodyParser.urlencoded({ extended: false }));app.listen(3000, function () { console.log('Node started on port 3000!')});

The first three lines include the modules required to serve the web pages, parse our forms and encode and decode our JWTs.

The next set of lines set up Express to listen on port 3000 for HTTP requests, and they initialize the BodyParser module to decode standard form data.

With our web server set up, the next set of code will display a form that asks the user for their email address. This will begin the password-reset process:

app.get('/forgotpassword', function (req, res) { res.send('<form action="/passwordreset" method="POST">' + '<input type="email" name="email" value="" placeholder="Enter your email address..." />' + '<input type="submit" value="Reset Password" />' + '</form>');});

This page can be accessed via http://localhost:3000/forgotpassword. The form it creates will POST to passwordreset with the user’s email address. Our basic form looks as follows. Once the user has entered their email address and submitted the form, our application needs to handle it:

app.post('/passwordreset', function (req, res) { if (req.body.email !== undefined) { var emailAddress = req.body.email; // TODO: Using email, find user from your database. var payload = { id: 1, // User ID from database email: emailAddress }; // TODO: Make this a one-time-use token by using the user's // current password hash from the database, and combine it // with the user's created date to make a very unique secret key! // For example: // var secret = user.password + ‘-' + user.created.getTime(); var secret = 'fe1a1915a379f3be5394b64d14794932-1506868106675'; var token = jwt.encode(payload, secret); // TODO: Send email containing link to reset password. // In our case, will just return a link to click. res.send('<a href="http://www.smashingmagazine.com/resetpassword/' + payload.id + '/' + token + '">Reset password</a>'); } else { res.send('Email address is missing.'); }});

Quite a bit is going on here, so let’s break it down:

  1. We ensure that the POST’s body contains the email property. If it doesn’t, then a basic error message is returned to the user.
  2. Store the email from the POST’s body in a local variable, emailAddress.
  3. Now our first pseudo-code occurs. I’ve placed a TODO message that says you should search your user database for a valid user with the email address supplied.
  4. Next, we generate the payload for the token. My payload consists of the user’s ID and email address.
  5. To make this token a one-time-use token, I encourage you to use the user’s current password hash in conjunction with the user’s created date (in ticks) as the secret key to generate the JWT. This helps to ensure that if the user’s password was the target of a previous attack (on an unrelated website), then the user’s created date will make the secret key unique from the potentially leaked password.
  6. With the combination of the user’s password hash and created date, the JWT will become a one-time-use token, because once the user has changed their password, it will generate a new password hash invalidating the secret key that references the old password.
  7. Because we don’t have a database, we are simply using a static string.
  8. The token is then generated using our payload and secret key.
  9. The final bit of pseudo-code occurs, to send the password-reset link to the user’s email address in an email.
  10. To continue focusing on how tokens are being used, let’s return the link to the browser. This can be clicked to finish the password-reset process. This link would be the same link that the user clicks in the email they received.

In all cases where you send an email to the user, the response should indicate that an email has been sent to the user and instruct them to click the link in the email.

When the user receives the email, they will click the link that takes them to the password-reset page. This page accepts the user’s ID and token as URL parameters (which were set in the link generated in the previous code example). The following code will handle this page. Upon successful decoding and validation of the token, a form is displayed allowing the user to set their new password:

app.get('/resetpassword/:id/:token', function(req, res) { // TODO: Fetch user from database using // req.params.id // TODO: Decrypt one-time-use token using the user's // current password hash from the database and combine it // with the user's created date to make a very unique secret key! // For example, // var secret = user.password + ‘-' + user.created.getTime(); var secret = 'fe1a1915a379f3be5394b64d14794932-1506868106675'; var payload = jwt.decode(req.params.token, secret); // TODO: Gracefully handle decoding issues. // Create form to reset password. res.send('<form action="/resetpassword" method="POST">' + '<input type="hidden" name="id" value="' + payload.id + '" />' + '<input type="hidden" name="token" value="' + req.params.token + '" />' + '<input type="password" name="password" value="" placeholder="Enter your new password..." />' + '<input type="submit" value="Reset Password" />' + '</form>');});

Similar pseudo-code from the previous example has been included in this example to help secure the application:

  1. Using the ID from the URL parameters, we fetch and validate that the user exists in our database.
  2. We decode the token from the URL parameters. To ensure it is a one-time-use token, I encouraged you in the previous example to encode it with the user’s current password hash in combination with the user’s created date (represented in ticks); thus, it should be decoded with that same hash.
  3. This is how it becomes a one-time-use token. Once the user has successfully changed their password, if they attempt to use the same token again, the token would not decode properly because the password hash would be different for that user.
  4. It would be a good idea to gracefully handle any errors that occur while decoding the token.
  5. Finally, a new form is returned that places the ID and token as hidden form fields and that includes a form field to accept the new password.

This is an example of our basic form for the user to reset their password.

The final part now is to handle the form’s POST with the user’s new password:

app.post('/resetpassword', function(req, res) { // TODO: Fetch user from database using // req.body.id // TODO: Decrypt one-time-use token using the user's // current password hash from the database and combining it // with the user's created date to make a very unique secret key! // For example, // var secret = user.password + ‘-' + user.created.getTime(); var secret = 'fe1a1915a379f3be5394b64d14794932-1506868106675'; var payload = jwt.decode(req.body.token, secret); // TODO: Gracefully handle decoding issues. // TODO: Hash password from // req.body.password res.send('Your password has been successfully changed.');});

The first part of this code is nearly identical to the previous example where the pseudo-code fetches the user and decodes the token with their current password hash, and the user’s created date is converted to ticks.

Notice the minor change in accessing the user’s ID and token. In the previous example, we used req.params. In this example, we are using req.body. The difference is that the first example was a GET request with the variables in the URL. This example is a POST request in which the variables are in the form.

The final TODO is for you to hash the user’s new password once the token has been validated.

This completes our sample application, which uses a single JWT to allow the user to change their password if they have forgotten it.

Additional Password-Reset Security Measures

Our application focuses specifically on securing the password-reset form by generating and validating a special link embedded with a JWT.

This is just the tip of the iceberg to ensure that the entire password process is more secure. Below is a list of several other enhancements that could further secure your website:

  • Limit the number of password-reset attempts to prevent a malicious user from giving your end user a negative experience of flooding their inbox with password-reset emails.
  • Always indicate success when the user enters their email address in the forgotten-password page.
  • Ensure that your website uses HTTPS to prevent any plain-text communication between the user and server when they are entering or resetting their password.
  • Ensure that the user’s new password is secure and is not the same as their last password.
  • Implement a CAPTCHA — the “Are you a human?” test — on both the forgotten-password and password-reset pages. Some websites even implement the CAPTCHA test on the log-in screen.
  • Implement forgotten-password security questions, where the user must answer a security question (that they’ve previously created) before an email is ever sent to reset their password.

“How Else Can I Use JWTs?”

By now, I’ll bet you are addicted to creating and consuming JWTs! Now you want to use them more. Here are a few examples of how else I have used them:

  • Single sign-on

    A friendly third-party website would generate a JWT with information that your website would require to authenticate the user in your application. You and the friendly website would privately share the secret key used to encode and decode the token.
  • Information exchange

    Similar to single sign-on, you or the friendly website would generate a token with a privately shared secret key that contains the information you wish to send or receive. Be sure not to share sensitive data!
  • Tokens required for the “OAuth dance

    Note that, because a generated JWT is a string, it can be decoded by a server other than the one that generated it. For example, you might generate a token with your Node.js server, and I could consume it with my PHP application as long as we use the same secret key and hashing algorithm!

Conclusion

Almost every day, we hear about a new security leak. And, let’s be honest, locks only keep out honest people. This means that, as developers, we need to try harder to make better locks. A JWT provides a URL-safe token that, when generated securely, makes for a more secure password-reset process by ensuring that a malicious user cannot easily generate their own token.

This article focused on the password-reset process by securing the password-reset flow with a URL-safe token that is validated with a signature. If you haven’t already done so, I suggest enhancing your processes further by reviewing the additional password-reset security measures and adding the ones that work for you.

If you have any further security processes, be sure to leave a comment below to help your fellow developers ensure that their password policies are more secure.

Smashing Editorial(al)

10 Simple Tips To Improve User Testing

(This is a sponsored post). Testing is a fundamental part of the UX designer’s job and a core part of the overall UX design process. Testing provides the inspiration, guidance and validation that product teams need in order to design great products. That’s why the most effective teams make testing a habit.

Usability testing involves observing users as they use a product. It helps you find where users struggle and what they like. There are two ways to run a usability test:

  • Moderated, in which a moderator works with a test participant.
  • Unmoderated, in which the test participant completes the test alone.

We’ll focus on the first, but some of the tips mentioned can be applied to both types of testing.

1. Test As Early As Possible

The earlier you test, the easier it is to make changes and, thus, the greater impact the testing will have on the quality of the product. A lot of design teams use the excuse, “The product isn’t done yet. We’ll test it later,” to postpone testing. Of course, we all want our work to be perfect, which is why we try to avoid showing a half-baked design. But if you work too long without a feedback loop, the chances are higher that you’ll need to make a significant change after releasing the product to the market. It’s the classic mistake: thinking you’re the user and designing for yourself. If you can invest energy to learn early and prevent problems from happening in the first place, you will save a tremendous amount of time later.

The good news is that you don’t need to wait for a high-fidelity prototype or fully formed product to start testing. In fact, you should start testing ideas as soon as possible. You can test design mockups and low-fidelity prototypes. You’ll need to set the context for the test and explain to test participants what’s required of them.

An example of a low-fidelity prototype made in Adobe XD.
An example of a low-fidelity prototype made in Adobe XD.)

2. Outline Your Objectives

Before starting usability testing, be crystal clear on your goals. Think of the reason you want to test the product. What are you trying to learn? Ask yourself, “What do I need to know from this session?” Then, once you understand that, identify exactly which features and areas you want feedback on.

Here are a few common objectives:

  • Find out whether users are able to complete specified tasks successfully (e.g. purchase a product, find information),
  • Identify how long it takes to complete specific tasks,
  • Find out whether users are satisfied with a product and identify changes required to improve satisfaction.

3. Carefully Prepare Questions And Tasks

Once you have an objective, you can define which tasks you’ll need to test in order to answer your questions or validate your hypothesis and assumptions. The objective is not to test the functionality itself (that should be a goal of the quality assurance team), but to test the experience with that functionality.

Actionable Tasks

When designing tasks, make them realistic and actionable. These could be specific parts of the product or prototype that you want users to test — for example:

  • Getting started with the product,
  • Completing a checkout,
  • Configuring the product.

Prioritize Tasks

Don’t squeeze in many subjects in your usability testing checklist. Conducting the tests and analyzing the results will take a lot of time. Instead, list the important tasks in your product, and order them by priority.

Clearly Describe Tasks

Testers need to know what to do. Make it easy. Users tend to become discouraged when tasks are unclear.

Have a Goal For Each Task

As a moderator, you should be very clear about the goal of a task (for example, “I expect that users will be able to complete the checkout within two minutes”). However, you don’t need to share that goal with participants.

Limit The Number Of Tasks

Patrick Neeman of Usability Counts recommends assigning five tasks per participant. Considering the time of the session (usually 60 minutes), leave time for your questions, too.

Provide a Scenario, Not Instruction

People tend to perform more naturally if you provide them with a scenario, rather than dry instruction. Instead of asking them something like, “Download a book with recipes,” you could phrase it as a scenario, like, “You’re looking for some new ways to cook beans. Download an ebook with recipes.” A scenario provides some context and makes the task more natural for the user. The more naturally participants perform the task, the better the data you will get as a result.

Test The Set Of Tasks Yourself

Go through the task several times yourself, and work out appropriate questions to ask. It’s hard work but will definitely pay off.

4. Recruit Representative Users

Finding the questions you want to ask is important, but also, the people who participate in your test should be representative of your target audience (user persona). There’s no point in watching people use your product if they don’t match your target audience. Therefore, as soon as you have some idea of what to test, start recruiting. Carefully recruit people based on your goals. Be advised: Finding people for usability tests is not easy. In fact, recruiting is one of the biggest reasons why many companies don’t regularly talk to their users. Thus, put in the extra effort to find people who represent your target audience.

Analyze Existing User Data

If your product already has a customer base, then a quick analysis of available information (for example, analytics data, customer support tickets, surveys, previous usability sessions) will help you assess what you already know or don’t know about your users.

Numbers provided by an analytic tool on how the user interacts with a product (clicks, user session time, search queries, conversion, etc.) will help UX designers to prepare for usability tests. (Image: Ramotion) (Large preview)

Test With Users Who Aren’t Only Friends or Family

Of course, feedback from friends and family is better than nothing, but for better results, you’ll need independent and unbiased users, ones who haven’t used your product before. Your friends and family are too close to the product to know how real people would perceive it for the first time.

Define Your Criteria

Before recruiting users, you’ll need to decide on the type of people to test your product. Define criteria and select testers according to it. For example, if you are testing a mobile app for ordering food, most probably you’ll need feedback from people who order food regularly. Translate this requirement into precise, measurable criteria, so that you can use it to screen prospective participants: people who order food at least once a week from different delivery services (participants should have experience with at least three services).

In addition to specifying the users you want to talk to, think about people you don’t want to see in any of your sessions. As a rule of thumb, avoid testing with tech-savvy users and early adopters, because such testing might not be as revealing as you’d like. Also, avoid participants who have conflicts of interest (such as ones who work for competitors).

Create Screener Questions

Next, create a screener questionnaire to identify people for your testing sessions. As with any good survey or questionnaire, avoid leading questions. An example of a question that would reveal the “right” answer is, “Do you like ordering food using a smartphone?” Most people who want to join a testing session would surely answer yes to that question.

You can prepare a list of questions in the format of a survey and ask potential testers to fill it out. Google Forms is a great tool for creating screeners and collecting the responses in a spreadsheet. Because responses go right into a Google spreadsheet, you can sort and filter them.

Get People to Fill Out the Screener

Next, you’ll need to get people to fill out the screener. One way to achieve this is to create a job description with a link to your survey. In the description, explain your expectations, and offer an incentive to motivate people to show up (such as a $100 Amazon gift card for a 60-minute interview).

Craigslist, Twitter and Facebook are the most obvious places to post the job description.

Things will be a bit harder when you need to recruit very specific and hard-to-find types of users. But even in this case, it’s totally solvable:

  • Talk with your sales or marketing team to see if they have lists of contacts they can share.
  • Find contacts in relevant community groups and professional associations.

Tip: If your product is on the market, you could show a message — “Want to give us more feedback?” — somewhere in the user flow, which leads to your screener form. Also, if you use a service such as Intercom, you could automatically email new users after they have used the product five times, inviting participation in testing.

Think Quality, Not Quantity

Some product teams think they need a lot of participants for usability testing. In fact, testing with five users generally unveils 85% of core usability problems. The most important problems are easy to spot for people who are new to your product, and difficult for you to spot because you no longer have fresh eyes. It turns out that you’ll learn a lot from the first person you talk to, a little less from the next, and so forth.

Once you collect the responses and filter the list of potential participants based on your criteria, select the five candidates who fit your criteria the best.

user tests
(Image: Nielsen Norman Group) (View large version)

Clearly Instruct on How to Join the Session

When you schedule a test session, provide all details in a confirmation email to participants:

  • The time (if you do remote testing, provide the time in the relevant time zone),
  • The location (including building, parking information, etc.),
  • What test participants need to bring with them (for example, personal ID, a mobile device with iOS or Android, etc.),
  • Your phone number (in case they have questions or need to reschedule).

To minimize frustrating no-shows, you could ask users to reply to confirm. For example, your subject line in the confirmation email could be something like, “Usability session scheduled on May 14 at 3 pm. (Please reply to confirm).” You could also call participants to remind them about their appointment on the day before the session.

5. Get The Most Out Of In-Person Testing

Hearing directly from users is one of the fastest ways to learn about and improve your product. By watching someone use your product, you can quickly identify areas where the product isn’t clear enough.

Building a Good Rapport

When a session begins, the participant might be nervous and unsure about what to expect. The quality of a usability session is directly related to the rapport you build with the participant. The deeper the participant’s trust in the moderator, the more frank their feedback will be. Conduct the test in a way that participants will feel comfortable giving you honest feedback.

A few things to remember:

  • In case of failure, people tend to blame themselves, rather than a flaw in the design. Thus, make sure they don’t feel like they’re being tested. (For example, “We’re not testing you; we’re testing our design. So, nothing you say or do is wrong.”)
  • You want participants to be as candid as possible. If they don’t like something or they think it’s silly, make sure they say so. Some participants don’t like to share such thoughts because they are afraid of hurting your feelings. Just tell them something such as, “You won’t be hurting our feelings. We haven’t been involved in designing these screens at all.”
  • Start with easy tasks or questions. They won’t yield any juicy insights, but they will get people talking and will help relax them. Learn a bit about the person. Try to find out what the person likes or doesn’t like, their hobbies, as well as tech habits. This information will help you better evaluate the results of the test.

Listen, Don’t Lead

Once you have presented the task, everything should be led by the participant. Your goal in this session is to understand how users will use the product. For example, if the participant takes an unplanned route through your app, don’t correct them! Wait to see what happens. This is valuable learning.

Don’t Judge Participants

Your participants are there to teach you something, not the other way around! Judging users or trying to educate them during the test would be counterproductive. Your goal is to get as much information as possible in the time available and to understand it all from their perspective.

Thus, avoid phrases like, “That was obvious, right?” and “Do you really think so?” while raising your eyebrows, even if something seems obvious. Instead, ask something like, “How easy or difficult was it for you to complete this task?” or “Why do you think that?” There should never be any judgement or surprise in either your tone or body language.

Don’t Explain

When you explain how the product you’re testing functions, you’ll almost certainly be introducing bias to the test. In the real world, your product will live on its own. You won’t be there to guide users along and tell them exactly what to do and how to use it. Participants should have to figure things out based on the task’s description and what they see in the interface.

Don’t Interrupt

When participants start a task, try your best not to interrupt them. The more you interrupt, the less likely they’ll have the confidence to complete the task. They’ll lose their flow, and you won’t see anything resembling natural behavior.

Don’t Draw Attention to Specific Issues

Drawing attention to specific issues that you care about could cause people to change their behavior and focus their answers on the issues you’re emphasizing. This problem is particularly common in discussions on user interface design: If you were to ask people about a particular design element (such as the color of the primary call-to-action button), they’ll notice it thereafter much more than they would have otherwise. This could lead participants to change their behavior and focus on something that doesn’t matter.

Use the Think-Aloud Technique

The think-aloud method is critical to getting inside the participant’s head. In fact, Jakob Nielsen argues that it’s the best usability tool. Using the think-aloud technique, the moderator asks test participants to use the product while continuously thinking out loud — simply verbalizing their thoughts as they move through the user interface. Using this technique for the food-ordering app, most probably you’d get responses like, “Hm, this looks like a food-ordering app. I’m wondering how to order food. Maybe if I tap here, I’ll see a form to request a meal.” The technique enables you to discover what users really think about your design and will help you turn the usability session into actionable redesign recommendations. Responses like, “Oh, it loads too slowly”, “Why am I seeing this?” and “I expected to see B after A” can be translated into actionable design changes.

Tip: Because most users don’t talk while using a product, the test facilitator will have to prompt them to keep talking. Ask something like, “What’s going on here?” when test participants interact with the product.

Observe Behavior

Mind the distinction between listening and observing. While both methods will provide UX designers with valuable information, many UX designers focus too heavily on listening. Observing users can uncover a lot more in a lot less time. You can learn a lot by listening to people, but you can learn way more by seeing how they react to a product.

Most people want to look smart, which is why during testing sessions, you’ll notice participants struggle through a task but then tell you that it was easy for them. Thus, focus on their behavior, not their opinion.

When in Doubt, Clarify

When you’re not quite sure what a participant is talking about, ask for clarification. A simple question like “When you said… did you mean…?” will make things clear. Don’t leave it to the end of the session. The end of a session is too late to go back and figure out what someone was talking about.

Follow Up With Questions

Be eager and curious to learn as much as you can about the user’s experiences and perspectives. Don’t settle for the first answer you get. Always dig deeper by asking follow-up questions. Follow-up questions will give you a lot of insight into what has really happened. People often can’t clearly state their motivations without being prompted. A simple well-timed follow-up question will usually yield a more thorough explanation or valuable example.

Answer Questions With Questions

During the session, participants will certainly ask you some questions. Here are some of the most common ones:

  • “Should I use it?”
  • “What do you think?”
  • “What did others think about this?”

Resist the temptation to tell them all about it! Ask them a question right back. It’ll reveal a lot.

6. Treat Design As An Iterative Process

A lot of product teams think about the design process as a linear process that starts with user research, has a phase for prototyping and ends with testing. However, treat it as an iterative process.

Testing, as much as coding, designing and gathering requirements, has a place in the iterative loop of product design and development. It’s important to test at each interval of this process, if resources are available.

Feedback Loop

The best way to avoid having to rework a product is to inject feedback into the process. Regular user feedback (not necessarily in the form of usability testing, but also in online surveys or analysis of customer support tickets) should be at the heart of the UX design process.

Learn, build, measure
(Image: Extreme Uncertainty) (View large version)

7. Don’t Limit Yourself To In-Person Sessions

Testing in-person is a great way to understand user behavior; unfortunately, it’s not always possible. What if you need to test only one small feature, or your test participants are dispersed (for example, if your product targets international customers), or you need results fast (ideally, today)? In this case, focus on remote testing. But how do you handle remote sessions?

Use Tools for Unmoderated Tests

Nowadays, a ton of tools are available for you to run remote unmoderated tests. Here are some:

  • Lookback

    This tool allows for both remote live moderated testing and unmoderated testing. Live sessions are automatically recorded in the cloud — no uploading, waiting or managing files.
  • UserTesting

    UserTesting allows for easy remote usability testing. You can run an unmoderated test on your website with a predefined user base.
  • Validately

    With Validately, choose either unmoderated or moderated testing. To test a product, add a link to your website or prototype. Testers will receive a URL to take the test or join an moderated session. After the session, you’ll receive a qualitative report and sharable videos. Pricing starts from $49 per month.
  • Usabilla

    Collect both qualitative and quantitative insights from users to make the right design decisions. Among testing deliverables, you’ll receive nice heat maps.

Conduct Moderated Remote Testing

You could conduct remote moderated sessions using Google Hangouts or Skype. Simply ask users to share their screen, and then see how they interact with your product. Don’t forget to record the session for further analysis. (Record both video and audio; without audio, it might be hard to tell why certain behavior occurred.)

Avoid “Professional” Testers

The downside of remote testing is that many participants get tested so frequently that they’ve learned to focus on certain aspects of a design. To compensate for possible “professional” testers, you’ll need to analyze the test sessions (for example, by watching the video recordings), and exclude results from people who don’t seem to provide genuine feedback.

8. Engage The Whole Team In The Process

Involve the whole product team in the testing process. Having an opportunity to observe users will help the whole team understand the problems with usability and to empathize with users. Testing enables you to build shared understanding, even before the team starts designing.

Discuss the Testing Strategy With the Team

Product design is a team sport. And because testing is an essential part of the design process, it should be discussed with all team players. Direct involvement in preparing the test will make team members more interested in the activity. As the person responsible for UX research, you should make it clear how your team will use the findings from the usability tests.

team process
(Image: General Assembly) (View large version)

Ask Everyone to Watch the Sessions

You can’t expect the entire team to join the testing sessions. In most cases, it isn’t necessary for everyone to observe all usability testing first-hand (although it might be desirable). But you can record the testing sessions on video and share it with colleagues. Video can be extremely helpful during design discussions.

Ask Team to Help With Analysis

One thing that slows down many forms of usability testing is analysis. Extracting findings from the data collected during testing sessions could take days or even weeks. But if the entire team watches the sessions and takes notes, they will be better able to summarize the findings and decide on next steps.

9. Test Before, During And After The Redesign

A common question among many product teams is, “When should we test?” The answer is simple: Test before a design or redesign, test during the design, and then test afterwards, too.

  • Before a design or redesign

    Testing would be conducted during the discovery phase of the UX design process. If you plan to redesign an existing product, usability testing could help you identify the biggest pain points in the current version. Consider testing competitors’ products, to compare results.
  • During a redesign

    If resources exist, do this at every milestone of the project. In the time it takes to build and launch a new product or feature, you could run several testing sessions and improve the prototype after each one.
  • After a redesign

    Knowledge of how real users use the product will help you make it better.

10. Don’t Try To Solve Everything At Once

Trying to solve everything at once is simply impossible. Instead, prioritize your findings. Fix the most important problems first, and then test again. However, if that’s impossible (for example, if the problems are too big to tackle), then prioritize problems according to their impact on revenue.

Conclusion

You can’t afford to skip testing, because even a simple round of testing could make or break your product. Investment in user testing is just about the only way to consistently generate a rich stream of data on user behavior. Thus, test early, test often.

This article is part of the UX design series sponsored by Adobe. Adobe XD tool is made for a fast and fluid UX design process, as it lets you go from idea to prototype faster. Design, prototype and share — all in one app. You can check out more inspiring projects created with Adobe XD on Behance, and also sign up for the Adobe experience design newsletter to stay updated and informed on the latest trends and insights for UX/UI design.

Smashing Editorial(vf, yk, al, il)

Maximizing The Design Sprint

Following a summer of Wonder Woman, Spiderman, and other superhero blockbusters, it’s natural to fantasize about having a superpower of your own. Luckily for designers, innovators, and customer-centric thinkers, a design sprint allows you to see into the future to learn in just five days what customers think about your finished product.

As a UX consultant and in-house design strategist, I have facilitated dozens upon dozens of design workshops (ranging from rapid prototyping sessions to, of course, sprints). The sprint is by far the most effective process I’ve seen to drive customer-first decision making in a design thinky way.

What Is A Sprint?

Because ‘sprint’ is used to refer to a variety of processes, I’ve given a brief description of a few different types of sprints to clarify:

  • Development sprint: a set period of time for software development work and review. (This is not what I’m writing about.)
  • (Regular) design sprint: set period of time for the design team to create functional designs ahead of the development sprint. (This is still not what I’m writing about, but check out a previous Smashing article, Getting Started with Design Sprints.)
  • (Google) design sprints: a 5-day process to understand if an idea maps to a customer need or opportunity without having to launch a minimal product. (Finally, this is what I’m writing about.)
Learn from meaningful customer feedback without having to build and launch a product. (Image: Google Ventures)

Now that we are all on the same page about different kinds of sprints, let’s take a look at an example:

Recently, I participated in a sprint that had the big goal to use our pre-built kit to build an app (coincidentally) in five days or less. Given that this process normally takes months, we assumed the faster, the better, right? We wanted to make sure this assumption was correct and sprinted this idea.

We more or less followed the process suggested on the Google Venture’s website. If you are completely unfamiliar with the design sprint, here’s a handy 90 second intro.

(We are aware that there’s a call to action to buy the book at the end of this video, but if you are not at all familiar with the design sprint, it will provide you with a quick introduction. We are not in any way affiliated with Google Ventures nor are trying to promote the book.)

This is what our process looked like:

  1. Monday, we set our goals, targets, and learning about potential users from customer experts.
  2. Tuesday consisted of drawing from inspirations and sketching a solution.
  3. Wednesday involved choosing a winning sketch and turning it into a storyboard.
  4. Thursday, we prototyped.
  5. Friday, we tested the prototype with customers.
Example of a winning sprint sketch that was turned into a storyboard and prototype.
Example of a winning sprint sketch that was turned into a storyboard and prototype. (Large preview)

By Friday, our prototype reflected the flow of a customer learning about our kit, viewing examples of the types of apps they could build, and launching their own app in a short span of time. We thought we did a great job, as the prototype illustrated our main value propositions:

  • The features that would be available through our kit.
  • The speed with which they could have a fully functional app of their own using our kit.

However, we tested our prototype with customers and learned that our value proposition didn’t really resonate. While it would be great to have speedy deployment, our kit did not allow for the level of customization developers required to meet the needs of their own customers.

Was it a waste of time? Of course, not. If we hadn’t explored and validated the idea with a design sprint, can you imagine the time and effort that would have gone into implementing the wrong thing? Avoiding wrong turns is the superpower of the sprint.

This superpower allows us to make major decisions and sidestep business paralysis. But with so much to pack into so little time, every minute — from prep to during to after — is critical. I’ll share what I’ve learned to maximize the sprint experience and help us flex our new superpower.

  • Before the sprint: setting yourself up for success.
  • During the sprint: maximizing your sprint week.
  • After the sprint: how to keep the momentum.

Before The Sprint: Setting Yourself Up For Success

Successful sprints start with good preparation. First, know that it’s a lot of logistical work. Even with the Sprint book’s explicit guidance, securing the right space, time, and people is a big undertaking, so give yourself 3 weeks. Consider a schedule that looks something like this:

  • Week 1: Confirm the sprint with stakeholders and send out an email getting people to book travel if necessary. Tell them now about the NO DEVICES rule — one of the design sprint’s key ideas, which basically says that the electronic devices shouldn’t be used during the sprint activities. Start scheduling users and customer experts to interview. Book your meeting space. If you can’t get a room for the full week at an office space, consider meeting at a hotel or nearby rental facility. If you haven’t already, start customer research (more on that later).
  • Week 2: Continue to follow up on your interviews.
  • Week 3: Gather supplies (recommend buying sprint kit), schedule interviews, and send a reminder email to participants (remind them about the NO DEVICES rule).
Give yourself three weeks only to prepare the design sprint properly. Note how every week have a strong focus on involving customers. Large preview

Notice that all three weeks involve scheduling participants. This is worth emphasizing because securing quality customer interviews can take time.

Quality interviews are with people who match your target customer. Getting the right people ensures that your feedback at the end of the week will be meaningful and adequate to drive decisions and next steps.

In the sprint example above (the development kit), we were testing an idea aimed at developers. The prototype we created wouldn’t make sense to non-developers, so we would have had difficulty recruiting the right testers on Craigslist or at a local Starbucks (unless we got really lucky!). Instead, we took weeks tracking down the right participants and finding interview times that worked for them. In this particular sprint, we drew from a pool of existing customers.

Other ways to find good participants is to leverage people in your network, or use sites like usertesting.com to screen potential participants. If your value proposition applies to a more general audience, you will not need as much lead time to secure the right people, but making sure you have the right testers is essential for a successful sprint.

If there’s opportunity to do so, I recommend conducting at least some kind of customer research. Get a sense of the day-to-day tasks and goals of your customers through actives such as interviews and ethnographic studies (you will most likely need to start these activities more than 3 weeks in advance). Waiting until the end of the week to hear from customers in your sprint makes you less likely to have a value proposition that meets actual needs or opportunities. Without customer research, you are still relying on your best guess to understand what customers want or what would add value. Even though the sprint process takes into consideration input from customer experts, this is never as effective as hearing from customers directly. Instead, infuse the voice of the customer into your sprint from the start.

Now that you’ve set yourself up for success, let’s look at how you can maximize your sprint week.

During The Sprint: Maximizing Your Sprint Week

Like any good design thinking process, start with your customer. Right after you introduce the sprint and set expectations for the week, present the findings of the customer research. Do this at the very beginning on Monday so that your knowledge of the customer will influence the goal, target, and types of questions you ask your customer experts.

Start with empathy. Who is your customer and what is important to them? (Image: Nielsen Norman Group) (Large preview)

The discussion does not have to be exhaustive, but make sure the team knows enough to begin the sprint by building empathy for your customer. This will be the foundation for the rest of the week’s activities, including sketching on Tuesday and prototyping on Thursday.

When creating a prototype, consider the appropriate scope and fidelity. The Sprint book recommends a high-fidelity prototype for a more realistic testing experience but only allows for prototyping to occur from 10am–3pm on one day.

We’ve found it quite difficult to build a high-fidelity prototype within the suggested five hours. For example, I’ve participated in a sprint that had three makers (makers in a design sprint are participants who create the prototype). Even with three makers, the prototype was not completed until well into the night. This prevented the team from doing a test run through prior to customer interviews, and the output of the sprint suffered.

Is there any way we can avoid this kind of stress and optimize the process? Allow me to share a few tips for maximizing the possibility of producing a viable prototype, as well as how to get the most out of the customer interview on Friday.

The Prototype Maker Should Be Comfortable With The Scope

The maker (again, the person who will build the prototype) has the most realistic view on how long it will take to create the prototype — since they only have one day (Thursday) to do so. The maker should firmly remind the decider (the term the Sprint book uses for the person who will make the major decisions during the design sprint) to focus on validating the value proposition.

People, myself included, get excited about the opportunity to get real feedback from potential customers and try to include non-essential things to test.

For example, we once prototyped a chat feature that was unrelated to our value proposition because our decider wanted to know if people would use it for support. That kind of insight would be interesting, but creating and testing these nice-to-haves can happen later.

When these situations happen, the (prototype) maker should be able to overrule the decider.

Be Realistic About The Level Of Fidelity

The correct level of fidelity is the one you can create in the given time that illustrates your value proposition.

  • Use realistic content and data.
  • Illustrate the essential components of your value proposition in a way that is functional (i.e. for a website, critical path for a task is clickable).
  • Do not build out features that do not illustrate the value proposition (unless you have the time!).

Consider Plant Stops, a fictitious company that sells trees and planting services. Plant Stops has always been a brick and mortar business but wants to expand. They want to sprint to see if customers would be interested in buying trees online. Let’s look at an example page from their prototype in varying levels of fidelity from too low to too much to just about right.

Large preview

Too low. The fidelity of this page is too low. It is not clear what task the customer is trying to accomplish and does not illustrate the idea that Plant Stops is testing.

Large preview

Too much. This example illustrates the idea at a high fidelity level. In order to be realistic, it contains features that are not necessary to validate the value proposition and should only be designed once all other critical components are created.

Large preview

Just about right. While not realistic, this example also conveys the idea and is sufficient to test with customers. Omitting additional features will free up time for more effective flow design.

More Than A Usability Test

After you’ve created a prototype on Thursday, it is time to test your value proposition on Friday. In my experience it is easy to turn customer interviews into usability tests (you don’t want to do that). This is especially easy if your tester is a UX person with experience giving usability tests (do I sound guilty yet?)

It took me a few sprints to un-train myself from asking only usability questions. For example, instead of asking, “where would you expect so and so to be?” I should have been asking, “what did you like or dislike about so and so?” Even more so, the best approach is to avoid specific questions and encourage the customer to think out loud.

Similarly, you might also need to walk users through the concept more than would be needed for a usability test. For instance, rather than watching customers fumble around trying to navigate the menu you created in 3 minutes the day before, get them to the key features as soon as possible to start getting feedback on your value proposition.

Usability feedback is definitely a plus, but you really want to make sure you are getting customer input on your idea. At the end of your interview, ask meaningful wrap-up questions. The answers that you receive will inform your decision whether or not to move forward with the project. For example, ask questions like:

  • What were your overall impressions of X?
  • On a scale from 1-10 (1 being the least useful and 10 being the most useful), how useful is X?
  • Use five adjectives to describe X.

Next steps

At the end of the sprint week, your team needs to decide if you will move forward with your value proposition. If not, you’ll need to discuss if there’s anything that can be salvaged from the project or if it’s better just to cut your losses and move on. The latter rarely happens, though.

Assuming you are moving forward with the validated concept, it’s important to stay focused. After such a successful and insightful sprint week, you wouldn’t want to lose the momentum, would you?

After The Sprint: How To Keep The Momentum

You’ve just had a week jam-packed with making decisions and progressing an idea. At the end of the week, people part ways and return to their day jobs. How do you keep the momentum? Here are some practical next steps and tips:

  1. Someone needs to be in charge. This could be the decider or someone the decider assigns to the role, such as a product owner or project manager. Ideally, this person was also a participant in the sprint.
  2. Decide what’s the minimum marketable product (MMP). What are the essential features and functions needed to deliver your value proposition? The customer feedback gathered during the sprint should drive this decision. All non-critical features (the nice-to-haves) should be saved for later.
  3. Make sure the prototype reflects the critical features of the MMP with UX design best practices applied.
  4. Test your prototype for usability. Now is the time to ask, “where would you expect so and so to be?”
  5. Get to work! Development can start using the customer-validated prototype. Your design team can start incorporating those nice-to-haves into the prototype, starting with the features that the product owner has prioritized based on customer feedback.
  6. Run more sprints. The design team would ideally conduct a few sprints ahead of the development team’s sprints. For example, if the development team is working in two-week sprints, then designers should schedule their sprints accordingly every two weeks, regardless of what type of design sprint they use. At the very least, the design sprints should consist of iterating on the prototype, testing with users, and passing off to the developers, ensuring that only customer-validated features are developed.

Using this process, you have established a continuous customer feedback loop, starting with your sprint.

Test Your New Superpower!

Since embracing the sprint, I have seen ideas — that have had no traction for years — gain new momentum and interest. Months and months of meetings and discussions bypassed by one week of collaboration, design thinking, and customer-centric decision making.

The tips gained from practical experience that I have outlined in the article will only make this effort more successful. So as you plan your next design sprint, remember how you can maximize your experience:

1. Pre-sprint

  • Schedule early
  • Research your customers

2. During-sprint

  • Start with the customer
  • Prototype at the right level
  • Conduct value proposition interview

3. After-sprint

  • Put someone in charge
  • Iterate and validate
  • Get to work

Now it’s time to test out your new superpower!

Right-To-Left Development In Mobile Design

The Middle Eastern market is growing at a rapid pace, and, as a result, demand for various IT products is also booming in the region. What is peculiar, though, is that Middle Eastern countries require design that is not only compatible with their needs and comfortable for their users, but that is also suitable to their language standards, making a serious adaptation process very important. Given that most languages spoken in the Middle East are written and read from right to left (such as Arabic, Hebrew and Urdu), developers often face a range of problems when creating products in those languages.

Although this might seem like not that big of a deal, IT development for right-to-left (RTL) languages entails paying attention to a number of peculiarities. This is only further complicated by the fact that the RTL market is relatively new, and not many resources are available to help developers.

Our experience with RTL development has enabled us to compile a thorough list of tips that are useful for anyone developing an RTL product (such as a website or mobile app). By following the tips closely, you should be able to navigate the challenging waters of RTL development and deliver a functional, user-friendly result.

Flipping The Interface

First and foremost, the interface must be flipped from right to left. You might think this is rather “D’uh” advice, but we simply could not disregard it, because it is in fact the very first thing to do.

Here’s an example of Facebook’s left-to-right (LTR) design:

Facebook’s log-in page in LTR design. (Image source: Facebook) (View large version)

And here is the RTL version of Facebook:

Facebook’s log-in page in RTL design. (Image source: Facebook) (View large version)

There are several ways to achieve this.

1. Using the dir Attribute or CSS

If the basic markup is built with floating blocks, it will look something like the example below.

For LTR design, pay attention to the styles. In this case, the link with logo will be fixed to the left, with the login-container to the right.

 <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Example</title> <style> .float-left{ float: left; } .float-right{ float: right; } body[dir="rtl"] .float-left{ float: right; } body[dir="rtl"] .float-right{ float: left; } </style> </head> <body> <header> <a href="#"><img src="static/logo.png"></a> <nav></nav> </header> <main></main> <footer></footer> </body> </html> 
Facebook’s top log-in bar in LTR design. (Image source: Facebook) (View large version)

For RTL design, once you’ve assigned the body tag to the dir="rtl attribute, the whole markup will be mirrored, placing the logo to the right and login-container to the left. The opposite properties will be then applied to blocks with the float-left or float-right class.

<body dir="ltr">

The dir attribute specifies the text’s direction and display (either left to right or right to left). Normally, browsers recognize the direction because it’s assigned in the Unicode character set, but with the dir attribute, you can set whatever direction you want.

The possible variants are dir="ltr", dir="rtl" and dir="auto". This attribute can also be rewritten with the CSS direction property.

Because text direction is semantically tied to content and not to presentation, we recommend that, whenever possible, web developers use the dir attribute and not the CSS properties related to it. That way, the text will be displayed properly even in browsers that don’t support CSS (or in those where CSS support is turned off).

Facebook’s top log-in bar in RTL design. (Image source: Facebook) (View large version)

With this attribute, you can horizontally mirror images, reassign fonts, align text to either side of the page, etc. However, the manual way is fairly labor-intensive. There are tools to automate the assembly of RTL styles, which we’ll get to a bit later in this post. In the meantime, we’ll describe another way of dealing with markup directional changes.

2. Using Flexbox

Flexbox is popular among developers for a couple of good reasons. Not only does it provide flexibility when adjusting the alignment of page elements and other bits, but it also eliminates the need to reassign styles for RTL development. See the code snippets below for more details.

For LTR:

 <div> <div>1</div> <div>2</div> <div>3</div> <div> 
An example of flexbox layout in LTR design. (Image source: SteelKiwi) (View large version)

For RTL:

 <div> <div>1</div> <div>2</div> <div>3</div> <div> 
An example of flexbox layout in RTL design. (Image source: SteelKiwi) (View large version)

The grid layout is also useful here. We set grid-template-areas in the containing block with {display: grid;} property (imagine it as a table with two columns and three rows). From the top: The header occupies the entire width of the screen, the sidebar is on the left, while the main content and the footer are on the right, one under another.

For LTR:

 <divheader header"; "sidebar main" "sidebar footer";"> <div>header</div> <div>sidebar</div> <div>main</div> <div>footer</div> </div> 
An example of basic direction of grid layout in LTR design. (Image source: SteelKiwi)

For RTL, by changing the dir attribute, the horizontal axis with the grid class and the {display: grid;} property will adjust to the specified direction. The layout matrix is then inverted:

 <divheader header" "sidebar main" "sidebar footer"; dir="rtl"> <div>header</div> <div>sidebar</div> <div>main</div> <div>footer</div> </div> 
An example of inverted grid layout matrix in RTL design. (Image source: SteelKiwi)

Even though using tables to build layouts hasn’t been popular for a while now, they would still build flow direction as a component, just like in the previous examples, meaning that when you add the dir="rtl" tag, columns will start from the right and move from right to left. All table elements will follow this, unless the direction is clearly predefined beforehand.

Character Encoding

Save your file using a character encoding that includes the characters you need (UTF-8 is a good bet). Declare that you are going to use such characters in the head tag of your HTML page with:

<meta charset="utf-8"/> 

UTF-8 is a character encoding capable of encoding all possible Unicode code points, and it covers almost all languages, characters and punctuation symbols. Using UTF-8 also removes the need for server-side logic when individually determining each page’s character encoding or each incoming form submission. Coding helps to convert everything into binary numbers. For instance, “hello” in UTF-8 would be stored like this (in binary): 01101000 01100101 01101100 01101100 01101111.

Formatted Text

Try as hard as you can to avoid using bold and italics. Bold text would make readability extremely difficult in most RTL languages (especially Arabic), and italics is simply never used. As well, capital letters are almost always disregarded in RTL languages.

An example of omitting capital letters in RTL languages. (Image source: SteelKiwi) (View large version)

If you need to highlight a certain part of text in Arabic, overline it instead of underlining, interspacing or italicizing. Here is how we do it:

<h1>مثال</h1> h1 { text-decoration: overline; } 
An example of overlining Arabic text. (Image source: SteelKiwi)

Make sure that all text is aligned to the right, and that font and font size are adjusted properly (better yet, customize them), because Latin fonts tend to affect Arabic readability rather poorly.

Actually, the best way to deal with fonts is to speak to your client (assuming, of course, they speak the language). The thing is that languages such as Arabic tend to have a great number of spoken and written variations, and if you’re making a product catered specifically to some region, it should really display the language version used there. If you find this matter interesting, information about the history of Arabic writing systems is available here.

In case your client isn’t a native speaker of the product’s language or isn’t able to help you, there is always the simple option of using one of the Google Noto fonts (there is one for every language, and all of them are free). Alternatively, Arabnet recommends 10 Arabic fonts (the list is from 2014, but things haven’t changed much in this area over the last three years). However, do keep in mind that your client always knows best which language variant is used most in their particular region, and if you have a chance to consult with them about it, do so right away.

Also, remember that words in RTL languages are often much shorter than words in English. So, adjust to keep a balance in how text is displayed on the page.

Icons

Using icons in RTL development can be tricky. Keep in mind that some of them might have to be mirrored, and some could be considered offensive to people of some nationalities, so double-check that the icons you’re using make sense.

Icons that are symmetrical and that don’t point in a particular direction should not be flipped.

There is no need to mirror symmetrical icons. (Image source: SteelKiwi)

On the other hand, icons that clearly point in a particular direction should be mirrored.

Mirror icons that point to a side. (Image source: SteelKiwi)
<span></span> body[dir="rtl"] { transform: scale(-1, 1); } 

The scale (x, y) CSS function used in the example above modifies the size of an element. If set to 1, it does nothing, but when negative, it performs as a “point reflection” and can modify size.

Icons that have characters or words from non-RTL languages don’t need to be mirrored. You should, however, localize them if necessary.

Icons containing words or characters from non-RTL languages don’t need be translated, but should be localized if necessary. (Image source: SteelKiwi)

Ensure that the icons you’re using are appropriate. For example, using a wine glass as a symbol for a restaurant or bar could be misunderstood because alcohol consumption is prohibited in Islam. Cultural peculiarities need to be taken into account, and developers should double-check that the symbols and icons they’re using are appropriate for the target market.

Avoid culturally inappropriate icons. (Image source: SteelKiwi)

Navigation Menus

Logos, navigation buttons and menus should be located in the upper-right corner for RTL design. The two latter elements also need to be displayed in reverse order. You don’t, however, need to mirror all of the stuff related to controlling media content (such as play and pause buttons).

Example of YouTube’s usage of media-content-management buttons in RTL design. (Image source: YouTube) (View large version)

Digit Ordering

The order of digits in numbers should not be changed for RTL. Note the phone number below. The digits are displayed in the same order in both LTR and RTL, but the telephone icon changes position. The same rule applies to other digits (such as addresses and other numeric strings).

The order of digits shouldn’t change for RTL design. (Image source: SteelKiwi) (View large version)

Position Of Control Buttons

Even though people speaking RTL languages perceive and process information from right to left, many of them are right-handed. Thus, it would be a good idea not to mirror control buttons, so that users can interact with them comfortably. Instead, center them to resolve any issues. For instance, if the orange button shown below was located to the left, it’d be extremely difficult for people to reach it with their right thumb while holding their device in one hand:

Placing the orange button to the left would be uncomfortable for users. (Image source: SteelKiwi) (View large version)

In this case, it would be much more convenient for users if such important elements of interaction were large and located in the middle of the screen.

Placing the orange button in the middle of the page would solve all convenience-related problems. (Image source: SteelKiwi) (View large version)

The elements in the bottom tab bar below should be positioned from right to left. RTL also exists in the Persian language — see example below:

In RTL design, the elements in the bottom tab bar should be positioned from right to left. (Image source: SteelKiwi)

Navigation drawers should appear from the right side.

In RTL design, drawers should appear from the right side. (Image source: SteelKiwi)

Position Of Other Symbols

The position of symbols that can be used for both RTL and LTR (such as punctuation marks) will depend on the direction of the text as a whole. This is because browsers process RTL words in the RTL direction, even though the data format starts from the beginning. Punctuation is converted only towards the specified direction.

The following example should illustrate the issue better:

You need to convert RTL and LTR strings into separate elements in order to have punctuation symbols appear in the right direction. (Image source: SteelKiwi) (View large version)

To fix this problem, you can convert RTL and LTR strings (or text fragments) into separate elements. Then, specify their direction with either the dir attribute or the CSS direction property. In the first case, you would do this:

 <p dir="rtl">?سوف أعطي مثالا على ذلك. لا تمانع</p> <p dir="ltr">I will give an example. Don’t you mind?</p> 

And in the second, this:

.rtl-text { direction: rtl;}.ltr-text { direction: ltr;}

Alternatively, you could also use the bdi tag to avoid this type of issue. However, it is only supported in Chrome (16) and Firefox (10).

Separate RTL CSS File

For basic CSS styles, you should create a separate RTL file and set the horizontal properties there (floating left and right, padding left and right, margins and so on), while also redefining them appropriately:

div.class { width: 150px; height: 100px; float: left; padding: 0 15px 0 10px;}

And in the rtl.css file:

html[dir="rtl"] div.class { float: right; padding: 0 10px 0 15px;}

If you need to eliminate some other LTR-directed features, you can always create and attach another separate rtl.css file.

In case this approach doesn’t meet your needs well enough, you can create two separate style files for LTR and RTL. Various utilities can be applied to automate their assembly. One such program is css-flip (created by Twitter). With its help, you can easily compile a file with properties redefined for RTL from an existing source file.

In input.css:

p { padding-left: 1em;}

And in output.rtl.css:

p { padding-right: 1em;}

You can also use replacements and exceptions, based on directives in the source file. For example, in input.css:

p { /@replace: -32px -32px/ background-position: -32px 0; /@replace: ">"/ content: "<"; /@noflip/ float: left;}

And in output.rtl.css:

p { background-position: -32px -32px; content: ">"; float: left;}

RTLCSS is another tool that supports replacements (exceptions) and makes it possible for developers to rename selectors, add local configurations of exception, and delete or add properties.

You could also use plugins for assembly instruments, including for Gulp, Grunt and Webpack.

For example, in input.css:

.example { transform: translateY(10px) /*rtl:append:scaleX(-1)*/; font-family: "Helvetica Neue", Arial/*rtl:prepend:"Arabic fonts", */; font-size:1.3em/*rtl:1.2em*/; /*rtl:remove*/ text-decoration:underline; /*rtl:begin:ignore*/ margin-left:15px; padding-left:20px; /*rtl:end:ignore*/}

And in output.rtl.css:

.example { transform: translateY(10px) scaleX(-1); font-family: "Arabic fonts","Helvetica Neue", Arial; font-size:1.2em; margin-left:15px; padding-left:20px;}

You can also make configurations for renaming selectors. Again, in input.css:

/*rtl:begin:options: { "autoRename": true, "stringMap":[ { "name" : "prev-next", "search" : ["prev", "previous"], "replace" : ["next", "next"], "options" : {"ignoreCase":false} } ]} */slide-prev, .card-previous { content: ‘<’;}/*rtl:end:options*/

And in output.rtl.css:

.slide-prev, .card-previous { content: ‘>’;}

Calendars

Calendars are probably one of the most important and complicated aspects of RTL design, because calendar years are different between LTR and RTL geographic regions.

Hijri

Hijri, the Islamic calendar, is lunar-based, which means that a year in the Gregorian calendar is longer than in the Islamic calendar. As a result, Hijri always has to shift according to the Gregorian calendar.

Hebrew Calendar

The Hebrew calendar, which has 12 lunar months, with an extra month added every few years, also differs from the Gregorian calendar. These differences make it hard to find an adequate tool for working with both Gregorian calendars in LTR scripts and non-Gregorian calendars in RTL scripts.

Tools for Displaying Calendars

One popular tool is FullCalendar, because it calculates time based on Moment.js. However, it cannot convert between different types of calendars and is only useful for localizing dates and displaying RTL content.

dijit/Calendar is able to display non-Gregorian calendars but has a rather limited range of tasks.

The DateTimeFormat constructor is an invaluable property for international objects. It makes it possible to pass additional options to the argument string when specifying the exact formatting for time and date.

Below, you can see how a date should be converted from the Gregorian calendar to the Islamic one:

var sampleDate = new Intl.DateTimeFormat("ru-RU-u-ca-islamicc").format(new Date()); // => "26.03.2017" console.log(sampleDate); // => "27.06.1438 AH"

Abbreviations (Days Of Week)

Although abbreviating the names of the days of the week is standard in many languages, this isn’t possible in Arabic because all of them start with the same letter. Instead, simply display their whole names and reduce the font size.

All names of weekdays start with the same letter in Arabic, meaning that you cannot abbreviate them like you would in English. (Image source: SteelKiwi) (View large version)

Internationalization

If your product requires internationalization, consider the ECMAScript Internationalization API. It comes with a language-sensitive string comparison and relevant formatting for numbers, dates and time.

Another important point is that ECMAScript supports not only Arabic, but also a wide range of other combinations, such as Arabic-Arabic and Arabic-Tunisian.

Also, keep in mind that the use of Eastern and Western Arabic numerals might depend on the language variant. Some regions might use Eastern Arabic numerals (123), while others use Western ones (١٢٣).

Formatting Arabic-Egyptian Numerals

var sampleNumber = new Intl.NumberFormat(‘ar-EG’).format(12345); console.log(sampleNumber); // => ١٢٬٣٤٥

In Tunisia, for instance, Eastern Arabic numerals are usually used:

var sampleNumber = new Intl.NumberFormat(‘ar-TN’).format(12345); console.log(sampleNumber); // => 12345

Examples Of Native Arabic Websites

Arageek is dedicated to all hip geek news around the world. (Image source: Arageek) (View large version)
Hawaa Forum is an online community for women. (Image source: Hawaaworld.com) (View large version)
Saudileague
Saudi League is dedicated to football in Saudi Arabia, with news, tournament days, info about teams and more. (Image source: Saudileague.com) (View large version)

Share Your Experience!

Cultural and linguistic peculiarities can be a hassle when you’re developing for different regions and markets. When it comes to the RTL market, developers must use their knowledge to adhere to a completely different set of rules, making the whole process more challenging and potentially frustrating. Using the 12 tips above, we hope you’re able to overcome some of the most common problems with RTL development.

If you have encountered any obstacles related to RTL development, please describe them (along with the solutions you’ve found) in the comments section below. The more that developers share their knowledge and experience, the easier it will be for all of us to deal with the peculiarities of RTL development.

Smashing Editorial(da, vf, yk, al, il)

Level-Up Email Campaigns With Customer Journey Mapping

I became a huge fan of customer journey mapping (CJM) the first time I was introduced to it. And after a few years of mapping, tweaking and presenting maps, my team and I started looking for other more exotic uses of this technique. The law of the instrument at its best, I suppose. Well, seek and ye shall find.

Customer journey mapping is a visualization technique that helps marketing specialists, user experience designers, and product and business owners see the journey people take when interacting with products and services. It is a great way to put on your customer’s shoes and see where your business fails to deliver a great user experience.

The way CJM works is pretty straightforward: You collect user research data, break down the entire funnel into steps (i.e. stages) and describe each stage from multiple points of view, such as your business goal, the customer’s goals, touchpoints (the very moments of interaction), customers expectations and pain points, their thoughts and feelings, etc. In the end, you have a table that looks something like this:

an example of customer journey map.
A customer journey map example (Image: UXPressia) (View large version)

From this table, you can tell at which points customers are not happy, and you can come up with some ideas to improve the situation.

Сustomer journey mapping is mainly used to find flaws in the entire path of the user, but I was curious if there was some unconventional way to use this technique. Turns out there is, and here the story of how it found me.

It’s Not A Journey Map… Or Is It?

After reading Baremetrics CEO Josh Pigford’s brilliant article about an email campaign that Baremetrics created to reduce churn and convert customers, our team at UXPressia decided that we needed something similar for our app.

Fast-forward a few weeks, and we had a sequence of emails ready to fly to our users’ mailboxes. They looked somewhat like this:

A chain of printed emails that we hung on our board
A chain of printed emails that we hung on our board (Image: UXPressia) (View large version)

These printed emails stuck around on our whiteboard for a while. Then, one day, while we were having coffee after a long and tedious CJM workshop, one of the participants glanced at the emails still hanging on the board and asked, “What’s this journey map for, guys?”

“Oh no, that’s not a journey…” — I was about to say that this was not a journey map, but I suddenly stopped. Our guys looked at each other. “Are you thinking what I am thinking?” Yes, our email campaign had stages and our business goals, so it could be. After all, we made a tool for mapping customer journeys, so it was a great opportunity for us to put it to the test. The question was: Is it OK to just cut out one channel from the entire user journey and focus on it solely?

On the one hand, customer journey mapping is all about a holistic approach, so it isn’t entirely right to focus on just one channel. On the other hand, we want to follow the “individuals and interactions over processes and tools” principle from the agile manifesto.

Besides, we tried our best to make our emails as personal as possible. Today, email campaigns are no longer carpet-bombing monologues. They are more of ongoing conversations in which we try to bond with our users. And customer journey mapping is all about finding a better and more personal approach.

So, why not try?

Everyone in the room started pitching ideas. Someone noticed that we had our goals linked to every email. “If we could add our user goals and see if both goals match…” he said.

At this point, it was clear that this was going to become a map. But two CJM sessions on the same day? You have got to be kidding. We took a break and agreed to sleep on this idea.

Doubts, The First Draft

The next day, after rebooting our brains, we gathered in the same room and asked ourselves, “What is the problem we are trying to solve here?” And is there any problem in the first place?

Well, have you ever seen how email campaigns are stored, organized and manipulated? We had a Google Doc with text and pictures, and it was kind of fine, although it was not easy to get a bird’s-eye view of the whole campaign all at once.

Email campaign in Google Docs
A screenshot from Google Docs, where we built the initial email campaign (Image: UXPressia) (View large version)

Our campaign was not very long and complex. It was a sequence of about 12 emails in which we welcome our users, give them tips and do some upselling.

Now, imagine if you had a longer campaign consisting of 50 emails triggered at different moments. I remembered my friend telling me how his company had an enormous spreadsheet file linking to different sources with multiple emails.

And there is no way to evaluate each letter out of the context. Setting up your campaign in some tool like MailChimp or Intercom would make your campaign a lot less messy, but you would still have to open each email to see the details.

Email campaign set up in Intercom
This is how our campaign looked in Intercom (Image: UXPressia) (View large version)

Turns out that hundreds of people working on email campaigns have terrible experiences themselves while crafting a better experience for others. Trying to unweave webs of interrelated email letters scattered over a spreadsheet would drive anyone crazy. This had to stop.

So, we rolled up our sleeves and drafted the first map using emails from our campaign.

The first draft of our email journey
The first draft of our email journey (Image: UXPressia) (View large version)

By mapping out the whole chain of emails on a single canvas, we could finally see everything in one place. Timing, email texts, business goals at each stage, as well as goals of each letter — it was all there. Having it all aligned in such a way instantly raised (and even answered) questions like:

  • “Are we bombarding our users with a number of emails from the same person? Would it be more appropriate to introduce someone new?”
  • “Is the timing correct and in line with the overall experience?”

And these questions were way easier to answer once we saw the whole picture. This alone was valuable enough because this clarity turned out to be a huge time-saver.

For example, shortly after the launch of our campaign, we noticed a pretty high unsubscribe rate from our emails. We tried to understand why this was happening and what we could do to fix it. Then, we looked at our email map and realized that the time gap between the first two emails was quite short, so we increased it. Guess what? The unsubscribe rate slowed down. This would have been more difficult to troubleshoot without the clear picture we had from customer journey mapping.

But we decided to take it up a few notches.

Leveraging Personas

Persona example
Persona example (Image: UXPressia) (View large version)

Remember I said we were trying to find a better and more personal approach? That’s what personas are best at. And having a well-researched persona when creating this email campaign was a game-changer for us.

By that time, we had already defined our customer personas, so it was no biggie to take each email and read it as if the reader was our persona.

A Brief Example

In one of our letters, we asked our users to tell us about themselves, so that we could make some suggestions and offer personalized help just in case. We expected them to drop us emails with some really short stories. So we “read” this email to our personas. Hey, picture a bunch of fellows reading to a poster on a wall. Bonkers!

Our team reading email to personas
Our team reading email to personas (Image: UXPressia) (View large version)

We tried to understand why this or that persona wouldn’t answer, and we realized that what we had in mind was not the way to go. What if our business-owner persona didn’t have time to sit there and compose emails? What could we offer to eliminate this objection? A quick call? Meh. Maybe. An online poll with predefined answers? Better!

So, using personas certainly had a great impact on our email campaign in the end.

Campaigns For Different Personas And A/B Tests

Example of a CJM for A/B tests or multiple personas
Example of a CJM for A/B tests or multiple personas (Image: UXPressia) (View large version)

By the way, what if you have multiple personas in your email campaign? That poor spreadsheet! Unless, again, you use customer journey mapping. In this case, we’d be able to easily map different letters to corresponding personas — and even find where these emails intersect!

Example

In her case study, one of our customers told us an interesting story. She was working on a complicated email campaign for multiple personas. The tricky part was to bring together all possible scenarios and see which email she should write for each specific case.

And she was quite amazed by how customer journey mapping saved her a lot of time and effort. Once all emails had been mapped out, it became apparent which letters repeated, so she could merge them into one.

This applies not only to scenarios like this one, but also to A/B tests. Imagine doing the same without customer journey mapping. Ugh! But wait, the best part is yet to come.

Email Campaign On CJM Steroids

And here is it. Once we started putting our campaign on CJM steroids, there was no going back. Customer journey mapping offers a ton of sections that we could use to take our email campaign to a whole new level. We tried some of them, and the results were quite surprising.

User Expectations and Goals

User goals on our email journey map
User goals on our email journey map (Image: UXPressia) (View large version)

Adding user goals and expectations to our map cocktail changed the way we saw our email campaign for the better. When sending an upsell email, is this what our user expects from us at that very moment? Does the goal of this letter match the goal of our customers?

By that moment, we had already rolled out our campaign, so we had some stats on hand. And adding these sections and answering these questions made us realize why the unsubscribe rate for some of our emails was so high. Speaking of which…

Key Performance Indicators and Other Metrics

KPI section in our email journey campaign
KPI section in our email journey campaign (Image: UXPressia) (View large version)

Now, what if we had real statistics under each email? Seeing how this or that letter performed enabled us to instantly find where our campaign hit the dirt. It did require some maintenance, but in the end, it was totally worth all the effort.

Quote or User Response

Quote section in our email journey map
Quote section in our email journey map (Image: UXPressia) (View large version)

Because we believe that email campaigns are conversations rather than monologues, we expect our users to say something back. Why not add some of their responses to our map? They could be from a single quote or an entire response. And based on their reactions, we were able to draw an…

Experience Graph

Experience graph based on KPI and user responses
Experience graph based on KPI and user responses (Image: UXPressia) (View large version)

The experience graph made it so easy for us to see the whole flow of our email campaign. Tracking performance enabled us to see which emails failed most and which did the best job. For us, this was priceless.

Problems and Ideas

Finally, once we had identified problematic emails in our campaign, it was time to think about what caused fails and how we could improve their performance. We pitched some ideas and started testing them ASAP!

Wrapping Up

An email journey map we ended up with
An email journey map we ended up with (Image: UXPressia) (View large version)

When we finally called it a day (or, rather, a night), everyone was so inspired. Using customer journey mapping to map our email campaign turned out to be not just a huge timesaver, but a well of insights, too. Not to mention that we were able to achieve a 40% open rate! Not a bad result in today’s world, where users develop email-blindness syndrome.

Of course, using CJM for mapping email campaigns will not work for all cases, but it was a lifesaver — and not just for us.

One of our customers transformed their existing email campaign the same way shortly after our debut. What they did was compare the email journey they created with the customer journey map they already had. Once they saw all emails on a single CJM canvas right next to the customer journey map, they got quite a few insights, like:

  • The first email in the campaign promoted the web application heavily right after a user downloaded the mobile app. The business goal at this stage was to decrease the number of users leaving the mobile app, but they were encouraging people to do just that!
  • The second email was pushing people towards providing more personal data. But from looking at the CJM as a whole, it was obvious that the timing was completely wrong: It happened at the stage when the majority of users were not yet ready to share anything — they simply hadn’t yet perceived any value from using the app.
  • The third email promoted the blog, which indeed had some great content. But the content was focused on just two personas, whereas the email campaign was sent to everyone. The majority of users were obviously not interested, so they kept unsubscribing.

These were not all of the insights they had, but even with these, it was pretty clear that the campaign needed some rethinking. Even more importantly, they already knew what had to be changed.

Anyway, here are some ideas about when transforming an email campaign into an email journey map will work for you as well:

  • You are working on a massive email campaign that you want to be consistent and well crafted as much as sympathetic and humane.
  • You believe that your team should try CJM, but people hesitate to engage because of the time commitment and unclear value. Seeing how it works for one channel would be less time-consuming and might help to convince your team to try a full-blown customer journey map after all.
  • You want to present campaign content to clients or stakeholders (which would be way more attractive than the bunch of separate files mentioned before).

The worst-case scenario here is that you would put your emails in order and save a lot of time in the long run.

Plus, you can do the same thing not just with emails but with virtually anything, be it call scripts for support or sales, alongside postal or face-to-face interactions.

Oh, and one more thing. We created a free template you can use to start mapping your email journey now! It has a predefined persona and all the sections we used in our own journey map.

But what about you? Have you tried using CJM for email campaigns? What insights can you share? Do you know of any unusual uses of CJM? Share your ideas in comments!

Links and Resources

(al)

Understanding The Vary Header

The Vary HTTP header is sent in billions of HTTP responses every day. But its use has never fulfilled its original vision, and many developers misunderstand what it does or don’t even realize that their web server is sending it. With the coming of the Client Hints, Variants and Key specifications, varied responses are getting a fresh start.

What’s Vary?

The story of Vary starts with a beautiful idea of how the web should work. In principle, a URL represents not a web page, but a conceptual resource, like your bank statement. Imagine that you want to see your bank statement: You go to bank.com and send a GET request for /statement. So far so good, but you didn’t say what format you want the statement in. This is why your browser will also include something like Accept: text/html in your request. In theory, at least, this means you could say Accept: text/csv instead and get the same resource in a different format.

Illustration of content-negotiated conversation between user and bank
(View large version)

Because the same URL now produces different responses based on the value of the Accept header, any cache that stores this response needs to know that that header is important. The server tells us that the Accept header is important like this:

Vary: Accept

You could read this as, “This response varies based on the value of the Accept header of your request.”

This basically doesn’t work on today’s web. So-called “content negotiation” was a great idea, but it failed. This doesn’t mean that Vary is useless, though. A decent portion of the pages you visit on the web carry a Vary header in the response — maybe your websites have them, too, and you don’t know it. So, if the header doesn’t work for content negotiation, why is it still so popular, and how do browsers deal with it? Let’s take a look.

I’ve previously written about Vary in relation to content delivery networks (CDNs), those intermediary caches (such as Fastly, CloudFront and Akamai) that you can put between your servers and the user. Browsers also need to understand and respond to Vary rules, and the way they do this is different from the way Vary is treated by CDNs. In this post, I’ll explore the murky world of cache variation in the browser.

Today’s Use Cases For Varying In The Browser

As we saw earlier, the traditional use of Vary is to perform content negotiation using the Accept, Accept-Language and Accept-Encoding headers, and, historically, the first two of these have failed miserably. Varying on Accept-Encoding to deliver Gzip- or Brotli-compressed responses, where supported, mostly works reasonably well, but all browsers support Gzip these days, so that isn’t very exciting.

How about some of these scenarios?

  • We want to serve images that are the exact width of the user’s screen. If the user resizes their browser, we would download new images (varying on Client Hints).
  • If the user logs out, we want to avoid using any pages that were cached while they were logged in (using a cookie as a Key).
  • Users of browsers that support the WebP image format should get WebP images; otherwise, they should get JPEGs.
  • When using a browser on a high-density screen, the user should get 2x images. If they move the browser window onto a standard-density screen and refresh, they should get 1x images.

Caches All The Way Down

Unlike edge caches, which act as one gigantic cache shared by all users, the browser is just for one user, but it has a lot of different caches for distinct, specific uses:

Illustration of caches in the browser
(View large version)

Some of these are quite new, and understanding exactly which cache the content is being loaded from is a complex calculation that is not well supported by developer tools. Here’s what these caches do:

  • image cache

    This is a page-scoped cache that stores decoded image data, so that, for example, if you include the same image on a page multiple times, the browser only needs to download and decode it once.
  • preload cache

    This is also page-scoped and stores anything that has been preloaded in a Link header or a <link rel="preload"> tag, even if the resource is ordinarily uncacheable. Like the image cache, the preload cache is destroyed when the user navigates away from the page.
  • service worker cache API

    This provides a cache back end with a programmable interface; so, nothing is stored here unless you specifically put it there via JavaScript code in a service worker. It will also only be checked if you explicitly do so in a service worker fetch handler. The service worker cache is origin-scoped, and, while not guaranteed to be persistent, it’s more persistent than the browser’s HTTP cache.
  • HTTP cache

    This is the main cache that people are most familiar with. It is the only cache that pays attention to HTTP-level cache headers such as Cache-Control, and it combines these with the browser’s own heuristic rules to determine whether to cache something and for how long. It has the broadest scope, being shared by all websites; so, if two unrelated websites load the same asset (for example, Google Analytics), they might share the same cache hit.
  • HTTP/2 push cache (or “H2 push cache”)

    This sits with the connection, and it stores objects that have been pushed from the server but have not yet been requested by any page that is using the connection. It is scoped to pages using a particular connection, which is essentially the same as being scoped to a single origin, but it is also destroyed when the connection closes.

Of these, the HTTP cache and service worker cache are best defined. As for the image and preload caches, some browsers might implement them as a single “memory cache” tied to the render of a particular navigation, but the mental model I’m describing here is still the right way to think about the process. See the specification note on preload if you’re interested. In the case of the H2 server push, discussion over the fate of this cache remains active.

The order in which a request checks these caches before venturing out onto the network is important, because requesting something might pull it from an outer layer of caching into an inner one. For example, if your HTTP/2 server pushes a style sheet along with a page that needs it, and that page also preloads the style sheet with a <link rel="preload"> tag, then the style sheet will end up touching three caches in the browser. First, it will sit in the H2 push cache, waiting to be requested. When the browser is rendering the page and gets to the preload tag, it will pull the style sheet out of the push cache, through the HTTP cache (which might store it, depending on the style sheet’s Cache-Control header), and will save it in the preload cache.

HTTP/2 PUSH flow through browser caches
(View large version)

Introducing Vary As A Validator

OK, so what happens when we take this situation and add Vary to the mix?

Unlike intermediary caches (such as CDNs), browsers typically do not implement the capability to store multiple variations per URL. The rationale for this is that the things we typically use Vary for (mainly Accept-Encoding and Accept-Language) do not change frequently within the context of a single user. Accept-Encoding might (but probably doesn’t) change upon a browser upgrade, and Accept-Language would most likely only change if you edit your operating system’s language locale settings. It also happens to be a lot easier to implement Vary in this way, although some specification authors believe this was a mistake.

It’s no great loss most of the time for a browser to store only one variation, but it is important that we don’t accidentally use a variation that isn’t valid anymore if the “varied on” data does happen to change.

The compromise is to treat Vary as a validator, not a key. Browsers compute cache keys in the normal way (essentially, using the URL), and then if they score a hit, they check that the request satisfies any Vary rules that are baked into the cached response. If it doesn’t, then the browser treats the request as a miss on the cache, and it moves on to the next layer of cache or out to the network. When a fresh response is received, it will then overwrite the cached version, even though it’s technically a different variation.

Demonstrating Vary Behavior

To demonstrate the way Vary is handled, I’ve made a little test suite. The test loads a range of different URLs, varying on different headers, and detects whether the request has hit the cache or not. I was originally using ResourceTiming for this, but for greater compatibility, I ended up switching to just measuring how long the request takes to complete (and intentionally added a 1-second delay to server-side responses to make the difference really clear).

Let’s look at each of the cache types and how Vary should work and whether it actually works like that. For each test, I show here whether we should expect to see a result from the cache (“HIT” versus “MISS”) and what actually happened.

Preload

Preload is currently supported only in Chrome, where preloaded responses are stored in a memory cache until they are needed by the page. The responses also populate the HTTP cache on their way to the preload cache, if they are HTTP-cacheable. Because specifying request headers with a preload is impossible, and the preload cache lasts only as long as the page, testing this is hard, but we can at least see that objects with a Vary header do get preloaded successfully:

Test results for link rel=preload in Google Chrome
(View large version)

Service Worker Cache API

Chrome and Firefox support service workers, and in developing the service worker specification, the authors wanted to fix what they saw as broken implementations in browsers, to make Vary in the browser work more like CDNs. This means that while the browser should store only one variation in the HTTP cache, it is supposed to hold onto multiple variations in the Cache API. Firefox (54) does this correctly, whereas Chrome uses the same vary-as-validator logic that it uses for the HTTP cache (the bug is being tracked).

Test results for service worker cache in Google Chrome
(View large version)

HTTP Cache

The main HTTP cache should observe Vary and does so consistently (as a validator) in all browsers. For much, much more on this, see Mark Nottingham’s post “State of Browser Caching, Revisited.”

HTTP/2 Push Cache

Vary should be observed, but in practice no browser actually respects it, and browsers will happily match and consume pushed responses with requests that carry random values in headers that the responses are varying on.

Test results for H2 push cache in Google Chrome
(View large version)

The “304 (Not Modified)” Wrinkle

The HTTP “304 (Not Modified)” response status is fascinating. Our “dear leader,” Artur Bergman, pointed out to me this gem in the HTTP caching specification (emphasis mine):

The server generating a 304 response must generate any of the following header fields that would have been sent in a 200 (OK) response to the same request: Cache-Control, Content-Location, Date, ETag, Expires, and Vary.

Why would a 304 response return a Vary header? The plot thickens when you read about what you’re supposed to do upon receiving a 304 response that contains those headers:

If a stored response is selected for update, the cache must […] use other header fields provided in the 304 (Not Modified) response to replace all instances of the corresponding header fields in the stored response.

Wait, what? So, if the 304’s Vary header is different from the one in the existing cached object, we’re supposed to update the cached object? But that might mean it no longer matches the request we made!

In that scenario, at first glance, the 304 seems to be telling you simultaneously that you can and cannot use the cached version. Of course, if the server really didn’t want you to use the cached version, it would have sent a 200, not a 304; so, the cached version should definitely be used — but after applying the updates to it, it might not be used again for a future request identical to the one that actually populated the cache in the first place.

(Side note: At Fastly, we do not respect this quirk of the specification. So, if we receive a 304 from your origin server, we will continue to use the cached object unmodified, other than resetting the TTL.)

Browsers do seem to respect this, but with a quirk. They update not just the response headers but the request headers that pair with them, in order to guarantee that, post-update, the cached response is a match for the current request. This seems to make sense. The specification doesn’t mention this, so the browser vendors are free to do what they like; luckily, all browsers exhibit this same behavior.

Client Hints

Google’s Client Hints feature is one of the most significant new things to happen to Vary in the browser in a long time. Unlike Accept-Encoding and Accept-Language, Client Hints describe values that might well change regularly as a user moves around your website, specifically the following:

  • DPR

    Device pixel ratio, the pixel density of the screen (might vary if the user has multiple screens)
  • Save-Data

    Whether the user has enabled data-saving mode
  • Viewport-Width

    Pixel width of the current viewport
  • Width

    Desired resource width in physical pixels

Not only might these values change for a single user, but the range of values for the width-related ones is large. So, we can totally use Vary with these headers, but we risk reducing our cache efficiency or even rendering caching ineffective.

The Key Header Proposal

Client Hints and other highly granular headers lend themselves to a proposal that Mark has been working on, named Key. Let’s look at a couple of examples:

Key: Viewport-Width;div=50

This says that the response varies based on the value of the Viewport-Width request header, but rounded down to the nearest multiple of 50 pixels!

Key: cookie;param=sessionAuth;param=flags

Adding this header into a response means that we’re varying on two specific cookies: sessionAuth and flags. If they haven’t changed, we can reuse this response for a future request.

So, the main differences between Key and Vary are:

  • Key allows varying on subfields within headers, which suddenly makes it feasible to vary on cookies, because you can vary on just one cookie — this would be huge;
  • individual values can be bucketed into ranges, to increase the chance of a cache hit, particularly useful for varying on things such as viewport width.
  • all variations with the same URL must have the same key. So, if a cache receives a new response for a URL for which it already has some existing variants, and the new response’s Key header value doesn’t match the values on those existing variants, then all the variants must be evicted from the cache.

At time of writing, no browser or CDN supports Key, although in some CDNs you might be able to get the same effect by splitting incoming headers into multiple private headers and varying on those (see our post, “Getting the Most Out of Vary With Fastly”), so browsers are the main area where Key can make an impact.

The requirement for all variations to have the same key recipe is somewhat limiting, and I’d like to see some kind of “early exit” option in the specification. This would enable you to do things like, “Vary on authentication state, and if logged in, also vary on preferences.”

The Variants Proposal

Key is a nice generic mechanism, but some headers have more complex rules for their values, and understanding those values’ semantics can help us to find automated ways of reducing cache variation. For example, imagine that two requests come in with different Accept-Language values, en-gb and en-us, but although your website does have support for language variation, you only have one “English.” If we answer the request for US English and that response is cached on a CDN, then it can’t be reused for the UK English request, because the Accept-Language value would be different and the cache isn’t smart enough to know better.

Enter, with considerable fanfare, the Variants proposal. This would enable servers to describe which variants they support, allowing caches to make smarter decisions about which variations are actually distinct and which are effectively the same.

Right now, Variants is a very early draft, and because it is designed to help with Accept-Encoding and Accept-Language, its usefulness is rather limited to shared caches, such as CDNs, rather than browser caches. But it nicely pairs up with Key and completes the picture for better control of cache variation.

Conclusion

There’s a lot to take in here, and while it can be interesting to understand how the browser works under the hood, there are also some simple things you can distil from it:

  • Most browsers treat Vary as a validator. If you want multiple separate variations to be cached, find a way to use different URLs instead.
  • Browsers ignore Vary for resources pushed using HTTP/2 server push, so don’t vary on anything you push.
  • Browsers have a ton of caches, and they work in different ways. It’s worth trying to understand how your caching decisions impact performance in each one, especially in the context of Vary.
  • Vary is not as useful as it could be, and Key paired with Client Hints is starting to change that. Follow along with browser support to find out when you can start using them.

Go forth and be variable.

Smashing Editorial(al)

SmashingConf 2018: Fetch Those Early-Bird Tickets!

Great conferences are all about learning new skills and making new connections. That’s why we’ve set up a couple of new adventures for SmashingConf 2018 — just practical sessions, new formats, new lightning talks, evening sessions and genuine, interesting conversations — with a dash of friendly networking! Taking place in London, San Francisco, Toronto. Tickets? Glad you asked!

SmashingConf London / #perfmatters / Feb 7–8

Performance matters. Next year, we’re thrilled to venture to London for our brand new conference fully dedicated to everything front-end performance. Dealing with ads, third-party scripts, A/B testing, HTTP/2, debugging, JAM stack, PWA, web fonts loading, memory/CPU perf, service workers. Plus lightning community talks. Schedule and details.

A queen cat welcoming you a Smashing Conference in London, February 7 to 8, 2018

SmashingConf London: everything web performance. Feb 7–8.

Speakers and Topics

Over the two days, we’ll cover pretty much every aspect of front-end performance: from rendering to debugging. Two days, one track, 16 speakers and 7 hands-on workshops. 50 early-bird-tickets now on sale.

£379 £459

Conference TicketAll taxes included. Only 50 early-bird tickets.

Save £76!

Conference & WorkshopAll taxes included. Only 50 early-bird tickets.

SmashingConf San Francisco / #breakout / Apr 17–18

The classic. With our third annual conference in San Francisco, we want to explore ways and strategies for breaking out: leaving behind generic designs and understanding the new techniques and capabilities available today. We care about the solutions we come up with, and the approaches that failed along the way. In San Francisco, we want to find out the why, and how, and what we all — as designers and developers — need to know today to be more productive and make smarter decisions tomorrow. Schedule and details.

A queen cat welcoming you a Smashing Conference in San Francisco, April 17 to 18, 2018

SmashingConf SF: breaking out of the box. Apr 17–18.

Speakers and Topics

A wide range of everything web-related, covered in 2 days, with a single track, 16 speakers and 8 hands-on workshops. CSS Grid, Design systems, new frontiers of CSS and JavaScript, accessibility, performance, lettering, graphic design, UX, multi-cultural design, among other things. 100 super early-bird-tickets now on sale.

US$499 $599

Conference TicketAll taxes included. Only 100 early-bird tickets.

Save US$100!

Conference & WorkshopAll taxes included. Only 100 early-bird tickets.

SmashingConf Toronto / #noslides / Jun 26–27

What’s the best way to learn? By observing designers and developers working live. For our new conference in Toronto, all speakers aren’t allowed to use any slides at all. Welcome SmashingConf #noslides, a brand new conference, full of interactive live sessions, showing how web designers design and how web developers build — including setup, workflow, design thinking, naming conventions and everything in-between. Schedule and details.

A queen cat welcoming you a Smashing Conference in Toronto, June 26 to 27, 2018

SmashingConf Toronto: no slides, live sessions only. Jun 26–27.

Speakers and Topics

Interactive live sessions on everything from organizing layers in Photoshop to naming conventions in CSS. Live workflow in Sketch and Photoshop, design systems setup, lettering, new frontiers of CSS and JavaScript, CSS Grid Layout, live debugging, performance audits, accessibility audits, sketching, graphic design, data visualization and creative coding. 100 super early-bird-tickets now on sale.

CAD$640 $705

Conference TicketAll taxes included. Only 100 early-bird tickets.

Save CAD$128!

Conference & WorkshopAll taxes included. Only 100 early-bird tickets.

Tickets!

To give everybody a chance to buy ticket in time, we are releasing all tickets in batches this time. The first batch of super early-birds are available right away: fetch them before they fly out!

Ah, and just in case you’re wondering: we’re planning on running a conference in our hometown Freiburg, Germany on September 10–11, and we will be coming back to New York, USA on October 23–24 — with a slightly different format, too. We can’t wait to see you there! 😉

Smashing Editorial(vf ms)

Inspiring Desktop Wallpapers To Make November Even More Colorful (2017 Edition)

November is a rather gray month in many parts of the world, so what could be better as some colorful inspiration to start it off with the right foot? To tickle your creativity, artists and designers from across the globe once again challenged their artistic abilities and designed desktop wallpapers for you to indulge in. Wallpapers that are a bit more distinctive as the usual crowd, bound to breathe some fresh life into your routine.

All artworks in this collection come in versions with and without a calendar for November 2017, so it’s up to you to decide if you want to have the month always in sight or just some distraction-free inspiration. A big thank-you to everyone who shared their wallpapers this time around! Enjoy!

Please note that:

  • All images can be clicked on and lead to the preview of the wallpaper,
  • You can feature your work in our magazine by taking part in our Desktop Wallpaper Calendars series. We are regularly looking for creative designers and artists to be featured on Smashing Magazine. Are you one of them?

Colorful Autumn

“Autumn can be dreary, especially in November, when rain starts pouring every day. We wanted to summon better days, so that’s how this colourful November calendar was created. Open your umbrella and let’s roll!” — Designed by PopArt Studio from Serbia.

Colorful Autumn

The Kind Soul

“Kindness drives humanity. Be kind. Be humble. Be humane. Be the best of yourself! Here is presenting to you an inspiration in form of a calendar for November.” — Designed by Color Mean Creative Studio from Dubai.

The Kind Soul

November Fun

Designed by Xenia Latii from Germany.

November Fun

Autumn Darkness

“‘When autumn darkness falls, what we will remember are the small acts of kindness: a cake, a hug, an invitation to talk, and every single rose. These are all expressions of a nation coming together and caring about its people.’ (Jens Stoltenberg)” — Designed by Dipanjan Karmakar from India.

Autumn Darkness

Tempestuous November

“By the end of autumn, ferocious Poseidon will part from tinted clouds and timid breeze. After this uneven clash, the sky once more becomes pellucid just in time for imminent luminous snow.” — Designed by Ana Masnikosa from Belgrade, Serbia.

Tempestuous November

Fall Breeze

“The colorful leaves and cool breeze that make you want to snuggle in your couch at home inspired me to create this fall design.” — Designed by Allison Coyle from the United States.

Fall Breeze

Music From Nature

Designed by StarAdmin from India.

Music From Nature

No Shave Movember

“The goal of Movember is to ‘change the face of men’s health.’” — Designed by Suman Sil from India.

No Shave Movember

Welcome November

“Dear November, be awesome!” — Designed by PlusCharts from India.

Welcome November

Peanut Butter Jelly Time!

“November is the Peanut Butter Month so I decided to make a wallpaper around that. As everyone knows peanut butter goes really well with some jelly so I made two sandwiches, one with peanut butter and one with jelly. Together they make the best combination. I also think peanut butter tastes pretty good so that’s why I chose this for my wallpaper.” — Designed by Senne Mommens from Belgium.

Peanut Butter Jelly Time!

Autumn In November

Designed by Dielan Ophals from Belgium.

Autumn In November

Coco Chanel

“Beauty begins the moment you decide to be yourself – Coco Chanel” — Designed by Tazi from Australia.

Coco Chanel

Movember

“Movember is an annual event involving the growing of mustaches during the month of November to raise awareness of men’s health issues, such as prostate cancer, testicular cancer and men’s suicide. This wallpaper is to support men’s health.” — Designed by Hemangi Rane from Gainesville, FL.

Movember

Fantastic Dreams

“No dream is too big. No challenge is too great. The whole universe is friendly to us and conspires only to give the best to those who dream and work.” — Designed by BootstrapDash from India.

Fantastic dreams

Curious Squirrel

Designed by Saul Wauters from Belgium.

Curious Squirrel

Give Thanks

“‘Feeling gratitude and not expressing it is like wrapping a present and not giving it.’ (William Arthur Ward)” — Designed by TemplateWatch from India.

Give Thanks

Happy Thanksgiving

“This Thanksgiving, we wish that God showers you and your family with, peace, love, warmth, and joy.” — Designed by Mozilor from India.

Happy Thanksgiving

Happy Birthday C.S.Lewis!

“It’s C.S. Lewis’s birthday on the 29th November, so I decided to create this ‘Chronicles of Narnia’ inspired wallpaper to honour this day.” — Designed by Safia Begum from the United Kingdom.

Happy Birthday C.S.Lewis!

Howling At The Moon

“The short days of the autumn and the early nights of winter. The image gives you the feeling of a cold night in autumn.” — Designed by Lars Pauwels from Belgium.

Howling At The Moon

Maple Leaf Globe

Designed by Hannah Joy Patterson from the USA.

Maple Leaf Globe

Autumn In The Park

“November is great for having a walk in the park, being out of your house, watching the few colored leaves that are still hanging on the trees. Enjoy the fresh but cold air from a blue sky on a nice November day.” — Designed by Arne Ameye from Belgium.

Autumn In The Park

Melancholy

“November brings us deeper into Autumn, when all leaves are brown and yellow, and Summer is just a distant memory to which we look back with a sweet melancholy.” — Designed by Pedro Vaz from Portugal.

Melancholy

Armistice Day

“In Belgium we have Armistice Day on November the 11th. This is a Bank Holiday. We remember the end of the War and all the weapons were put down.” — Designed by Ruben Annaert from Belgium.

Armistice Day

Success Is A State Of Mind

Designed by Metrovista from Orlando, FL.

Success Is A State Of Mind

Meteor Shower

“Since I was a kid I’ve always been extremely inspired by astronomy. Starting this project I found out that many meteor showers occur during November. I had never made a proper space-related illustration before and I definitely took my chance now.” — Designed by Yannis Wellemans from Belgium.

Meteor Shower

Stop Being So Hammer-Headed

“A single hammer headed person around can simply ruin your entire peace and pleasure when you are working on something great. As it’s an empty vessel, nothing much to expect but loud noise. It’s easy to identify those from outside and being hammer headed is a choice. So, consider people around, stop hitting yourself into others and let them do whatever they feel like.” — Designed by Sweans from London.

Stop Being So Hammer-Headed

Someone Sleeps More

Designed by UrbanUI from India.

Someone Sleeps More

Welcome A Season Of Chilling Joy

“The season of plenty of warmth and love has just stepped in… Enjoy the chill with the joy of sharing.” — Designed by Areva Digital from India.

Welcome A Season Of Chilling Joy

Welcome Winter

“When it’s cold outside, bring out the warmth and love in your hearts; to enjoy what the season has kept in store for you. Let’s welcome the onset of winter this November with open arms.” — Designed by Acodez from India.

Welcome Winter

Join In Next Month!

Please note that we respect and carefully consider the ideas and motivation behind each and every artist’s work. This is why we give all artists the full freedom to explore their creativity and express emotions and experience throughout their works. This is also why the themes of the wallpapers weren’t anyhow influenced by us, but rather designed from scratch by the artists themselves.

A big thank you to all designers for their participation. Join in next month!

What’s Your Favorite?

What’s your favorite theme or wallpaper for this month? Please let us know in the comment section below.

SmashingConf 2018: Fetch Those Early-Bird Tickets! ?? ?? ??

SmashingConf 2018: Fetch Those Early-Bird Tickets! ?? ?? ??

Great conferences are all about learning new skills and making new connections. That’s why we’ve set up a couple of new adventures for SmashingConf 2018 — just practical sessions, new formats, new lightning talks, evening sessions and genuine, interesting conversations — with a dash of friendly networking! Taking place in London, San Francisco, Toronto. Tickets? Glad you asked!1

SmashingConf London52 / #perfmatters / Feb 7–8 Link

Performance matters. Next year, we’re thrilled to venture to London for our brand new conference fully dedicated to everything front-end performance. Dealing with ads, third-party scripts, A/B testing, HTTP/2, debugging, JAM stack, PWA, web fonts loading, memory/CPU perf, service workers. Plus lightning community talks. Schedule and details.3

4
SmashingConf London52: everything web performance. Feb 7–8.

Speakers and Topics Link

Over the two days, we’ll cover pretty much every aspect of front-end performance: from rendering to debugging. Two days, one track, 16 speakers and 7 hands-on workshops. 50 early-bird-tickets now on sale.6

£379 £459

Conference Ticket15All taxes included. Only 50 early-bird tickets.

Save £76!

Conference & Workshop16All taxes included. Only 50 early-bird tickets.

SmashingConf San Francisco17 / #breakout / Apr 17–18 Link

The classic. With our third annual conference in San Francisco, we want to explore ways and strategies for breaking out: leaving behind generic designs and understanding the new techniques and capabilities available today. We care about the solutions we come up with, and the approaches that failed along the way. In San Francisco, we want to find out the why, and how, and what we all — as designers and developers — need to know today to be more productive and make smarter decisions tomorrow. Schedule and details.18

A queen cat welcoming you a Smashing Conference in San Francisco, April 17 to 18, 201819
SmashingConf SF20: breaking out of the box. Apr 17–18.

Speakers and Topics Link

A wide range of everything web-related, covered in 2 days, with a single track, 16 speakers and 8 hands-on workshops. CSS Grid, Design systems, new frontiers of CSS and JavaScript, accessibility, performance, lettering, graphic design, UX, multi-cultural design, among other things. 100 super early-bird-tickets now on sale.21

US$499 $599

Conference Ticket28All taxes included. Only 100 early-bird tickets.

Save US$100!

Conference & Workshop29All taxes included. Only 100 early-bird tickets.

SmashingConf Toronto3330 / #noslides / Jun 26–27 Link

What’s the best way to learn? By observing designers and developers working live. For our new conference in Toronto, all speakers aren’t allowed to use any slides at all. Welcome SmashingConf #noslides, a brand new conference, full of interactive live sessions, showing how web designers design and how web developers build — including setup, workflow, design thinking, naming conventions and everything in-between. Schedule and details.31

A queen cat welcoming you a Smashing Conference in Toronto, June 26 to 27, 201832
SmashingConf Toronto3330: no slides, live sessions only. Jun 26–27.

Speakers and Topics Link

Interactive live sessions on everything from organizing layers in Photoshop to naming conventions in CSS. Live workflow in Sketch and Photoshop, design systems setup, lettering, new frontiers of CSS and JavaScript, CSS Grid Layout, live debugging, performance audits, accessibility audits, sketching, graphic design, data visualization and creative coding. 100 super early-bird-tickets now on sale.34

CAD$640 $705

Conference Ticket45All taxes included. Only 100 early-bird tickets.

Save CAD$128!

Conference & Workshop46All taxes included. Only 100 early-bird tickets.

Tickets! Link

To give everybody a chance to buy ticket in time, we are releasing all tickets in batches this time. The first batch of super early-birds are available right away: fetch them before they fly out!

Ah, and just in case you’re wondering: we’re planning on running a conference in our hometown Freiburg, Germany on September 10–11, and we will be coming back to New York, USA on October 23–24 — with a slightly different format, too. We can’t wait to see you there! 😉

(vf ms)

Footnotes Link

  1. 1 #tickets
  2. 2 https://smashingconf.com
  3. 3 https://smashingconf.com/
  4. 4 https://smashingconf.com
  5. 5 https://smashingconf.com
  6. 6 https://shop.smashingmagazine.com/products/smashingconf-london-2018?variant=990650269721
  7. 7 https://www.twitter.com/
  8. 8 https://www.twitter.com/
  9. 9 https://www.twitter.com/
  10. 10 https://www.twitter.com/
  11. 11 https://www.twitter.com/
  12. 12 https://www.twitter.com/
  13. 13 https://www.twitter.com/
  14. 14 https://www.twitter.com/
  15. 15 https://shop.smashingmagazine.com/products/smashingconf-london-2018
  16. 16 https://smashingconf.com/registration
  17. 17 https://smashingconf.com/sf-2018/
  18. 18 https://smashingconf.com/sf-2018/
  19. 19 https://smashingconf.com/sf-2018/
  20. 20 https://smashingconf.com/sf-2018/
  21. 21 https://shop.smashingmagazine.com/products/smashingconf-san-francisco-2018?variant=1012441088025on
  22. 22 https://smashingconf.com/sf-2018/speakers/jessica-hische
  23. 23 https://smashingconf.com/sf-2018/speakers/trent-walton
  24. 24 https://www.twitter.com/
  25. 25 https://smashingconf.com/sf-2018/speakers/yiying-lu
  26. 26 https://smashingconf.com/sf-2018/speakers/patrick-hamann
  27. 27 https://smashingconf.com/sf-2018/speakers/rachel-andrew
  28. 28 https://shop.smashingmagazine.com/products/smashingconf-san-francisco-2018
  29. 29 https://smashingconf.com/sf-2018/registration
  30. 30 https://smashingconf.com/toronto-2018/
  31. 31 https://smashingconf.com/toronto-2018/schedule
  32. 32 https://smashingconf.com/toronto-2018/
  33. 33 https://smashingconf.com/toronto-2018/
  34. 34 https://shop.smashingmagazine.com/products/smashingconf-toronto?variant=1012127203353
  35. 35 https://smashingconf.com/toronto-2018/speakers/lea-verou
  36. 36 https://smashingconf.com/toronto-2018/speakers/seb-lee-delisle
  37. 37 https://smashingconf.com/toronto-2018/speakers/sarah-drasner
  38. 38 https://smashingconf.com/toronto-2018/speakers/joe-leech
  39. 39 https://smashingconf.com/toronto-2018/speakers/gemma-obrien
  40. 40 https://smashingconf.com/toronto-2018/speakers/tim-kadlec
  41. 41 https://smashingconf.com/toronto-2018/speakers/dan-mall
  42. 42 https://smashingconf.com/toronto-2018/speakers/nadieh-bremer
  43. 43 https://smashingconf.com/toronto-2018/speakers/aaron-draplin
  44. 44 https://smashingconf.com/toronto-2018/speakers/yiying-lu
  45. 45 https://shop.smashingmagazine.com/products/smashingconf-toronto?variant=1012127203353
  46. 46 https://smashingconf.com/toronto-2018/registration
  47. 47 https://smashingconf.com/registration
  48. 48 https://smashingconf.com/sf-2018/registration
  49. 49 https://smashingconf.com/toronto-2018/registration

↑ Back to topTweet itShare on Facebook

A Swift Transition From iOS To macOS Development

Today started just like any other day. You sat down at your desk, took a sip of coffee and opened up Xcode to start a new project. But wait! The similarities stop there. Today, we will try to build for a different platform! Don’t be afraid. I know you are comfortable there on your iOS island, knocking out iOS applications, but today begins a brand new adventure. Today is the day we head on over to macOS development, a dark and scary place that you know nothing about.

The good news is that developing for macOS using Swift has a lot more in common with iOS development than you realize. To prove this, I will walk you through building a simple screen-annotation application. Once we complete it, you will realize how easy it is to build applications for macOS.

The Concept

The idea comes from two unlikely sources. The first source is my boss, Doug Cook. He came over and asked if I knew how to make a circular floating app on macOS for prototyping purposes. Having never really done anything on macOS, I started to do some digging. After a little digging, I found Apple’s little gem of RoundTransparentWindow. Sure, it was in Objective-C, and it was pre-ARC to boot, but after reading through the code, I saw that figuring out how to do it in Swift wasn’t very difficult. The second source was pure laziness. I recently picked up a side project making tutorial videos on YouTube. I wanted to be able to describe what I was saying by drawing directly on the screen, without any post-production.

I decided to build a macOS app to draw on the computer screen:

Behold the app in all its glory!
Behold the app in all its glory! (View large version)

OK, it doesn’t look like much — and, honestly, it shouldn’t because I haven’t drawn anything. If you look closely at the image above, you will see a little pencil icon in the upper-right bar. This area of macOS contains items called “menu extras.” By clicking the pencil icon, you will enable drawing on screen, and then you can draw something like this below!

Behold the app in all its glory, again!
Behold the app in all its glory, again! (View large version)

I wanted drawing on the screen to be enabled at all times, but not to take over the screen when not in use. I preferred that it not live in the dock, nor change the contents of macOS’ menu bar. I knew that it was possible because I’d seen it in other apps. So, over lunch one day, I decided to take a crack at building this drawing tool, and here we are! This is what we will build in this tutorial.

The Soapbox

At this point, you might be saying to yourself, “Why build this? It’s just a simple drawing app!” But that isn’t the point. If you are anything like me, you are a little intimidated by the thought of making a macOS app. Don’t be. If you program for your iPhone on your MacBook, shouldn’t you also be able to program for your MacBook on your MacBook? What if Apple actually does merge iOS and macOS? Should you be left behind because you were intimidated by macOS development? ChromeOS already supports Android builds. How long before macOS supports iOS builds? There are differences between Cocoa and UIKit, which will become apparent, but this tutorial will get your feet wet and (hopefully) challenge you to build something bigger and better.

Caveats and Requirements

There are some caveats to this project that I want to get out of the way before we start. We will be making an app that draws over the entire screen. This will work on only one screen (for now) and will not work as is over full-screen apps. For our purpose, this is enough, and it leaves open room for plenty of enhancements in future iterations of the project. In fact, if you have any ideas for enhancements of your own, please leave them in the comments at the bottom!

For this tutorial, you’ll need a basic understanding of Swift development and familiarity with storyboards. We will also be doing this in Xcode 9 because that is the latest and greatest version at the time of writing.

1. Begin A macOS Project

Open Xcode and create a new macOS project. At this point, you are probably still in the “iOS” style project. We have to update this so that we are building for the correct system. Hit the “macOS” icon at the top, and then make sure that “Cocoa App” is selected. Hit “Next.”

Create a new macOS project
Create a new macOS project.

Enter in the product name as you normally would. We will call this one ScreenAnnotation, and then do the normal dance with “organization name” and “team” that you’d normally do. Make sure to select Swift as the language, and again hit “Next.” After saving it in the directory of your choosing, you will have your very own macOS app. Congratulations!

At first glance, you will see most everything you get in an iOS app. The only differences you might notice right now are the entitlements file, Cocoa in place of UIKit in each of the .swift files, and (naturally) the contents of the storyboard.

2. Clean Up

Our next step is to go into the storyboard and delete anything we don’t need. First, let’s get rid of the menu; because our app will live as a menu extra, instead of as a dock app, it is unnecessary. Beneath where the menu existed is the window. We need to subclass this and use it to determine whether we are drawing within our app or toying with windows beneath it. Looking below the window, we can also see the familiar view controller. We already have a subclass of this, provided by Xcode. It is aptly named ViewController because it is a subclass of NSViewController, but we still need a NSWindow subclass, so that we can decorate it as a clear window. Head over to the left pane, right-click, select “New file,” then select “Swift” file. When prompted, name this ClearWindow. Replace the one line with the following:

import Cocoaclass ClearWindow : NSWindow { override init(contentRect: NSRect, styleMask style: NSWindow.StyleMask, backing backingStoreType: NSWindow.BackingStoreType, defer flag: Bool) { super.init(contentRect: contentRect, styleMask: StyleMask.borderless, backing: backingStoreType, defer: flag) level = NSWindow.Level.statusBar backgroundColor = NSColor.blue } override func mouseDown(with event: NSEvent) { print("Mouse down: (event.locationInWindow)") } override func mouseDragged(with event: NSEvent) { print("Mouse dragged: (event.locationInWindow)") } override func mouseUp(with event: NSEvent) { print("Mouse up: (event.locationInWindow)") }}

In this code snippet, we are importing Cocoa, which is to macOS as UIKit is to iOS development. This is the main API we will use to control our app. After importing Cocoa, we subclass NSWindow, and then we update our super-call in the init method. In here, we keep the same contentRect but will modify this later. We change the styleMask to borderless, which removes the standard application options: close, minimize and maximize. It also removes the top bar on the window. You can also do this in the storyboard file, but we are doing it here to show what it would look like to do it programmatically. Next, we pass the other variables right on through to the constructor. Now that we have that out of the way, we need to tell our window where to draw. Looking at the NSWindow documentation, we see that we can set our window at different levels. We will set the window level to NSStatusWindowLevel because it will draw above all other normal windows.

3. Our First Test

We are using NSResponder methods in the same way that we’d use the UIResponder method to respond to touches on iOS. On macOS, we are interested in mouse events. Later on, we will be using these methods to draw in our ViewController.

Finally, we’ll change the color of our view to blue, just to make sure things are running smoothly; if we went straight to a transparent view, we wouldn’t know where the window was drawn yet! Next, we need to set up the storyboard to use our new ClearWindow class, even though it isn’t living up to its name yet. Go back to the storyboard, click the window, and edit its subclass under the “Custom Class” area in the right pane. Type in ClearWindow here, and we can now run our app.

Type in ClearWindow
Type in ClearWindow

Lo and behold, we have a blue rectangle on our screen! Nothing impressive, yet. We can click and drag around, and we can spam the console. Let’s stop running the app at this point because it will only get in the way.

4. Let’s Start Drawing!

Next, we can update our implementation of ViewController. The bulk of our work will now happen here and in Main.storyboard. Right now, the important part is to piggyback on the methods that we created in ClearWindow to capture mouse gestures. Replace the contents of ViewController with the following code:

import Cocoaclass ViewController: NSViewController { override func viewDidLoad() { super.viewDidLoad() view.frame = CGRect(origin: CGPoint(), size: NSScreen.main!.visibleFrame.size) } func startDrawing(at point: NSPoint) { } func continueDrawing(at point: NSPoint) { } func endDrawing(at point: NSPoint) { }}

This does not look like much yet, but it is the foundation of our drawing code. In our modified viewDidLoad() method, we’ve resized the view to equal our main screen’s dimension. We do this because we can only draw within our NSViewController. If our view covered everything, then we’d be able to draw over anything! Finally, we’ve created hooks that, for ClearWindow, will call for the ViewController to draw. More on that in a bit.

The next thing we need to do is define how we will draw onto the screen. Add the following code above our viewDidLoad method.

let lineWeight: CGFloat = 10let strokeColor: NSColor = .redvar currentPath: NSBezierPath?var currentShape: CAShapeLayer?

These four variables define our drawing. Currently, our line thickness is 10 points, and we will be drawing in red — nothing revolutionary here, but that needs to be defined. Next, we have a NSBezierPath and a CAShapeLayer. These should look pretty familiar if you have ever played with UIBezierPath. Note that these two are optional (they will come up again later).

Now for the fun part: We can start implementing our drawing methods.

Start Drawing

Update startDrawing with the following code:

func startDrawing(at point: NSPoint) { currentPath = NSBezierPath() currentShape = CAShapeLayer() currentShape?.lineWidth = lineWeight currentShape?.strokeColor = strokeColor.cgColor currentShape?.fillColor = NSColor.clear.cgColor currentShape?.lineJoin = kCALineJoinRound currentShape?.lineCap = kCALineCapRound currentPath?.move(to: point) currentPath?.line(to: point) currentShape?.path = currentPath?.cgPath view.layer?.addSublayer(currentShape!)}

This is the most complicated of the three drawing methods. The reason for this is that we need to set up a new NSBezierPath and CAShapeLayer each time we start drawing. This is important because we don’t want to have one continuous line all over our screen — that wouldn’t do at all. This way, we can have one layer per line, and we will be able to make any kind of drawing we want. Then, we set up the newly created CAShapeLayer’s properties. We send in our lineWeight and then the stroke color to our nice red color. We set the fill color to clear, which means we will only be drawing with lines, instead of solid shapes. Then, we set the lineJoin and lineCap to use rounded edges. I chose this because the rounded edges make the drawing look nicer in my opinion. Feel free to play with these properties to figure out what works best for you.

Then, we move the point where we will start drawing to the NSPoint that will be sent to us. This will not draw anything, but it will give the UIBezierPath a reference point for when we actually give it instructions to draw. Think of it as if you had a pen in your hand and you decided to draw something in the middle of a sheet of paper. You move the pen to the location you want to draw, but you’re not doing anything yet, just hovering over the paper, waiting to put the ink down. Without this, nothing can be drawn because the next line requires two points to work. The next line, aptly named line(to: point), draws a line from the current position to wherever you specify. Currently, we’re telling our UIBezierPath to stay in the same position and touch down on our sheet of paper.

The last two lines pull the path data out of our UIBezierPath in a usable format for CAShapeLayer. Note that currentPath?.cgPath will be marked as an error at the moment. Don’t fret: We will take care of that after we cover the next two methods. Just know that when it does work, this function will have our CAShapeLayer draw its path, even if it is currently a dot. Then, we add this layer to our view’s sublayer. At this point, the user will be able to see that they are now drawing.

Continue Drawing

Update continueDrawing with the following code:

func continueDrawing(at point: NSPoint) { currentPath?.line(to: point) if let shape = currentShape { shape.path = currentPath?.cgPath }}

Not much going on here, but we are adding another line to our currentPath. Because the CAShapeLayer is already in our view’s sublayer, the update will show on screen. Again, note that these are optional values; we are guarding ourselves just in case they are nil.

End Drawing

Update endDrawing with the following code:

func endDrawing(at point: NSPoint) { currentPath?.line(to: point) if let shape = currentShape { shape.path = currentPath?.cgPath } currentPath = nil currentShape = nil}

We update the path again, just the same way as we did in continueDrawing, but then we also nil out our currentPath and currentShape. We do this because we are done drawing and no longer need to talk to this shape and path. The next action we can take is startDrawing again, and we start this process all over again. We nil out these values so that we cannot update them again; the view’s sublayer will still hold a reference to the line, and it will stay on screen until we remove it.

If we run the app with what we have, we’ll get errors! Almost forgot about that. One thing you will definitely notice when moving over to macOS development is that not every API between iOS and macOS are equivalent. One such issue here is that NSBezierPath doesn’t have the handy cgPath property that UIBezierPath has. We use this property to easily convert the NSBezierPath path to the CAShapeLayer path. This way, CAShapeLayer will do all the heavy lifting to display our line. We can sit back and reap the reward of a nice-looking line with none of the work! StackOverflow has a handy answer that I’ve used to handle this absence of cgPath by updating it to Swift 4 (see below). This code lets us create an extension to NSBezierPath and returns to us a handy CGPath to play with. Create a file named NSBezierPath+CGPath.swift and add the following code to it.

import Cocoaextension NSBezierPath { public var cgPath: CGPath { let path = CGMutablePath() var points = [CGPoint](repeating: .zero, count: 3) for i in 0 ..< self.elementCount { let type = self.element(at: i, associatedPoints: &points) switch type { case .moveToBezierPathElement: path.move(to: points[0]) case .lineToBezierPathElement: path.addLine(to: points[0]) case .curveToBezierPathElement: path.addCurve(to: points[2], control1: points[0], control2: points[1]) case .closePathBezierPathElement: path.closeSubpath() } } return path }}

At this point, everything will run, but we aren’t drawing anything quite yet. We still need to attach the drawing functions to actual mouse actions. In order to do this, we go back into our ClearWindow and update the NSResponder mouse methods to the following:

 override func mouseDown(with event: NSEvent) { (contentViewController as? ViewController)?.startDrawing(at: event.locationInWindow) } override func mouseDragged(with event: NSEvent) { (contentViewController as? ViewController)?.continueDrawing(at: event.locationInWindow) } override func mouseUp(with event: NSEvent) { (contentViewController as? ViewController)?.endDrawing(at: event.locationInWindow) }

This basically checks to see whether the current view controller is our instance of ViewController, where we will handle the drawing logic. If you run the app now, you should see something like this:

The app as it is now
The app as it is now (View large version)

This is not completely ideal, but we are currently able to draw on the blue portion of our screen. This means that our drawing logic is correct, but our layout is not. Before correcting our layout, let’s create a good way to quit, or disable, drawing on our app. If we make it full screen right now, we would either have to “force quit” or switch to another space to quit our app.

5. Create A Menu

Head back over to our Main.storyboard file, where we can add a new menu to our ViewController. In the right pane, drag “Menu” under the “View Controller Scene” in our storyboard’s hierarchy.

Setting up the menu
Setting up the menu (View large version)

Edit these menu items to say “Clear,” “Toggle” and “Quit.” For extra panache, we can add a line separator above our “Exit” item, to deter accidental clicks:

Creating menu options
Creating menu options (View large version)

Next, open up the “Assistant Editor” (the Venn diagram-looking button near the top right of Xcode), so that we can start hooking up our menu items. For both “Clear” and “Toggle,” we want to create a “Referencing Outlet” so that we can modify them. After this, we want to hook up “Sent Action” so that we can get a callback when the menu item is selected.

Hooking up the code to the buttons
Hooking up the code to the buttons (View large version)

For “Exit,” we will drag our “Sent Action” to the first responder, and select “Terminate.” “Terminate” is a canned action that will quit the application. Finally, we need a reference to the menu itself; so, right-click on the menu under “View Controller Scene,” and create a reference named optionsMenu. The newly added code in ViewController should look like this:

@IBOutlet weak var clearButton: NSMenuItem!@IBOutlet weak var toggleButton: NSMenuItem!@IBOutlet var optionsMenu: NSMenu!@IBAction func clearButtonClicked(_ sender: Any) {}@IBAction func toggleButtonClicked(_ sender: Any) {}

We have the building blocks for the menu extras for our app; now we need to finish the process. Close out of “Assistant Editor” mode and head over to ViewController so that we can make use of these menu buttons. First, we will add the following strings to drive the text of the toggle button. Add the following two lines near the top of the file.

private let offText = "Disable Drawing"private let onText = "Enable Drawing"

We need to update what happens when the clear and toggle buttons are clicked. Add the following line to clear the drawing in clearButtonClicked(_ sender):

view.window!.ignoresMouseEvents = !view.window!.ignoresMouseEventstoggleButton.title = view.window!.ignoresMouseEvents ? onText : offText

This toggles the flag on our window to ignore mouse events, so that we will click “through” our window, in order to use our computer as intended. We’ll also update the toggleButton’s text to let the user know that drawing is either enabled or disabled.

Now we need to finally put our menu to use. Let’s start by adding an icon to our project. You can find that in the repository. Then, we can override the awakeFromNib() method because, at this point, our view will be inflated from the storyboard. Add the following code to ViewController.

let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)override func awakeFromNib() { statusItem.menu = optionsMenu let icon = NSImage(named: NSImage.Name(rawValue: "pencil")) icon?.isTemplate = true // best for dark mode statusItem.image = icon toggleButton.title = offText}

Make sure to put the statusItem near the top, next to the rest of the variables. statusItem grabs a spot for our app to use menu extras in the top toolbar. Then, in awakeFromNib, we set the menu as our optionsMenu, and, finally, we give it an icon, so that it is easily identifiable and clickable. If we run our app now, the “menu extra” icon will appear at the top! We aren’t quite finished yet. We need to ensure that the drawing space is placed correctly on screen; otherwise, it will be only partially useful.

6. Positioning The Drawing App

To get our view to draw where we want it, we must venture back into Main.storyboard. Click on the window itself, and then select the attributes inspector, the icon fourth from the left in the right-hand pane. Uncheck everything under the “Appearance,” “Controls” and “Behavior” headings, like so:

Attributes inspector
Attributes inspector

We do this to remove all extra behaviors and appearances. We want a clear screen, with no bells and whistles. If we run the app again, we will be greeted by a familiar blue screen, only larger. To make things useful, we need to change the color from blue to transparent. Head back over to ClearWindow and update the blue color to this:

backgroundColor = NSColor(calibratedRed: 1, green: 1, blue: 1, alpha: 0.001)

This is pretty much the same as NSColor.clear. The reason why we are not using NSColor.clear is that if our entire app is transparent, then macOS won’t think our app is visible, and no clicks will be captured in the app. We’ll go with a mostly transparent app — something that, at least to me, is not noticeable, yet our clicks will be recorded correctly.

The final thing to do is remove it from our dock. To do this, head over to Info.plist and add a new row, named “Application is agent (UIElement)” and set it to “Yes.” Once this is done, rerun the app, and it will no longer appear in the dock!

Conclusion

This app is pretty short and sweet, but we did learn a few things. First, we figured out some of the similarities and differences between UIKit and Cocoa. We also took a tour around what Cocoa has in store for us iOS developers. We also (hopefully) know that creating a Cocoa-based app is not difficult, nor should it be intimidating. We now have a small app that is presented in an atypical fashion, and it lives up in the menu bar, next to the menu extras. Finally, we can use all of this stuff to build bigger and better Cocoa apps! You started this article as an iOS developer and grew beyond that, becoming an Apple developer. Congratulations!

I will be updating the public repository for this example to add extra options. Keep an eye on the repository, and feel free to add code, issues and comments.

Good luck and happy programming!

Attributes inspector
Thank you!

This does not look like much yet, but it is the foundation of our drawing code. In our modified viewDidLoad() method, we’ve resized the view to equal our main screen’s dimension. We do this because we can only draw within our NSViewController. If our view covered everything, then we’d be able to draw over anything! Finally, we’ve created hooks that, for ClearWindow, will call for the ViewController to draw. More on that in a bit.

The next thing we need to do is define how we will draw onto the screen. Add the following code above our viewDidLoad method.

let lineWeight: CGFloat = 10let strokeColor: NSColor = .redvar currentPath: NSBezierPath?var currentShape: CAShapeLayer?

These four variables define our drawing. Currently, our line thickness is 10 points, and we will be drawing in red — nothing revolutionary here, but that needs to be defined. Next, we have a NSBezierPath and a CAShapeLayer. These should look pretty familiar if you have ever played with UIBezierPath. Note that these two are optional (they will come up again later).

Now for the fun part: We can start implementing our drawing methods.

Start Drawing

Update startDrawing with the following code:

func startDrawing(at point: NSPoint) { currentPath = NSBezierPath() currentShape = CAShapeLayer() currentShape?.lineWidth = lineWeight currentShape?.strokeColor = strokeColor.cgColor currentShape?.fillColor = NSColor.clear.cgColor currentShape?.lineJoin = kCALineJoinRound currentShape?.lineCap = kCALineCapRound currentPath?.move(to: point) currentPath?.line(to: point) currentShape?.path = currentPath?.cgPath view.layer?.addSublayer(currentShape!)}

This is the most complicated of the three drawing methods. The reason for this is that we need to set up a new NSBezierPath and CAShapeLayer each time we start drawing. This is important because we don’t want to have one continuous line all over our screen — that wouldn’t do at all. This way, we can have one layer per line, and we will be able to make any kind of drawing we want. Then, we set up the newly created CAShapeLayer’s properties. We send in our lineWeight and then the stroke color to our nice red color. We set the fill color to clear, which means we will only be drawing with lines, instead of solid shapes. Then, we set the lineJoin and lineCap to use rounded edges. I chose this because the rounded edges make the drawing look nicer in my opinion. Feel free to play with these properties to figure out what works best for you.

Then, we move the point where we will start drawing to the NSPoint that will be sent to us. This will not draw anything, but it will give the UIBezierPath a reference point for when we actually give it instructions to draw. Think of it as if you had a pen in your hand and you decided to draw something in the middle of a sheet of paper. You move the pen to the location you want to draw, but you’re not doing anything yet, just hovering over the paper, waiting to put the ink down. Without this, nothing can be drawn because the next line requires two points to work. The next line, aptly named line(to: point), draws a line from the current position to wherever you specify. Currently, we’re telling our UIBezierPath to stay in the same position and touch down on our sheet of paper.

The last two lines pull the path data out of our UIBezierPath in a usable format for CAShapeLayer. Note that currentPath?.cgPath will be marked as an error at the moment. Don’t fret: We will take care of that after we cover the next two methods. Just know that when it does work, this function will have our CAShapeLayer draw its path, even if it is currently a dot. Then, we add this layer to our view’s sublayer. At this point, the user will be able to see that they are now drawing.

Continue Drawing

Update continueDrawing with the following code:

func continueDrawing(at point: NSPoint) { currentPath?.line(to: point) if let shape = currentShape { shape.path = currentPath?.cgPath }}

Not much going on here, but we are adding another line to our currentPath. Because the CAShapeLayer is already in our view’s sublayer, the update will show on screen. Again, note that these are optional values; we are guarding ourselves just in case they are nil.

End Drawing

Update endDrawing with the following code:

func endDrawing(at point: NSPoint) { currentPath?.line(to: point) if let shape = currentShape { shape.path = currentPath?.cgPath } currentPath = nil currentShape = nil}

We update the path again, just the same way as we did in continueDrawing, but then we also nil out our currentPath and currentShape. We do this because we are done drawing and no longer need to talk to this shape and path. The next action we can take is startDrawing again, and we start this process all over again. We nil out these values so that we cannot update them again; the view’s sublayer will still hold a reference to the line, and it will stay on screen until we remove it.

If we run the app with what we have, we’ll get errors! Almost forgot about that. One thing you will definitely notice when moving over to macOS development is that not every API between iOS and macOS are equivalent. One such issue here is that NSBezierPath doesn’t have the handy cgPath property that UIBezierPath has. We use this property to easily convert the NSBezierPath path to the CAShapeLayer path. This way, CAShapeLayer will do all the heavy lifting to display our line. We can sit back and reap the reward of a nice-looking line with none of the work! StackOverflow has a handy answer that I’ve used to handle this absence of cgPath by updating it to Swift 4 (see below). This code lets us create an extension to NSBezierPath and returns to us a handy CGPath to play with. Create a file named NSBezierPath+CGPath.swift and add the following code to it.

import Cocoaextension NSBezierPath { public var cgPath: CGPath { let path = CGMutablePath() var points = [CGPoint](repeating: .zero, count: 3) for i in 0 ..< self.elementCount { let type = self.element(at: i, associatedPoints: &points) switch type { case .moveToBezierPathElement: path.move(to: points[0]) case .lineToBezierPathElement: path.addLine(to: points[0]) case .curveToBezierPathElement: path.addCurve(to: points[2], control1: points[0], control2: points[1]) case .closePathBezierPathElement: path.closeSubpath() } } return path }}

At this point, everything will run, but we aren’t drawing anything quite yet. We still need to attach the drawing functions to actual mouse actions. In order to do this, we go back into our ClearWindow and update the NSResponder mouse methods to the following:

 override func mouseDown(with event: NSEvent) { (contentViewController as? ViewController)?.startDrawing(at: event.locationInWindow) } override func mouseDragged(with event: NSEvent) { (contentViewController as? ViewController)?.continueDrawing(at: event.locationInWindow) } override func mouseUp(with event: NSEvent) { (contentViewController as? ViewController)?.endDrawing(at: event.locationInWindow) }

This basically checks to see whether the current view controller is our instance of ViewController, where we will handle the drawing logic. If you run the app now, you should see something like this:

The app as it is now
The app as it is now (View large version)

This is not completely ideal, but we are currently able to draw on the blue portion of our screen. This means that our drawing logic is correct, but our layout is not. Before correcting our layout, let’s create a good way to quit, or disable, drawing on our app. If we make it full screen right now, we would either have to “force quit” or switch to another space to quit our app.

5. Create A Menu

Head back over to our Main.storyboard file, where we can add a new menu to our ViewController. In the right pane, drag “Menu” under the “View Controller Scene” in our storyboard’s hierarchy.

Setting up the menu
Setting up the menu (View large version)

Edit these menu items to say “Clear,” “Toggle” and “Quit.” For extra panache, we can add a line separator above our “Exit” item, to deter accidental clicks:

Creating menu options
Creating menu options (View large version)

Next, open up the “Assistant Editor” (the Venn diagram-looking button near the top right of Xcode), so that we can start hooking up our menu items. For both “Clear” and “Toggle,” we want to create a “Referencing Outlet” so that we can modify them. After this, we want to hook up “Sent Action” so that we can get a callback when the menu item is selected.

Hooking up the code to the buttons
Hooking up the code to the buttons (View large version)

For “Exit,” we will drag our “Sent Action” to the first responder, and select “Terminate.” “Terminate” is a canned action that will quit the application. Finally, we need a reference to the menu itself; so, right-click on the menu under “View Controller Scene,” and create a reference named optionsMenu. The newly added code in ViewController should look like this:

@IBOutlet weak var clearButton: NSMenuItem!@IBOutlet weak var toggleButton: NSMenuItem!@IBOutlet var optionsMenu: NSMenu!@IBAction func clearButtonClicked(_ sender: Any) {}@IBAction func toggleButtonClicked(_ sender: Any) {}

We have the building blocks for the menu extras for our app; now we need to finish the process. Close out of “Assistant Editor” mode and head over to ViewController so that we can make use of these menu buttons. First, we will add the following strings to drive the text of the toggle button. Add the following two lines near the top of the file.

private let offText = "Disable Drawing"private let onText = "Enable Drawing"

We need to update what happens when the clear and toggle buttons are clicked. Add the following line to clear the drawing in clearButtonClicked(_ sender):

view.window!.ignoresMouseEvents = !view.window!.ignoresMouseEventstoggleButton.title = view.window!.ignoresMouseEvents ? onText : offText

This toggles the flag on our window to ignore mouse events, so that we will click “through” our window, in order to use our computer as intended. We’ll also update the toggleButton’s text to let the user know that drawing is either enabled or disabled.

Now we need to finally put our menu to use. Let’s start by adding an icon to our project. You can find that in the repository. Then, we can override the awakeFromNib() method because, at this point, our view will be inflated from the storyboard. Add the following code to ViewController.

let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)override func awakeFromNib() { statusItem.menu = optionsMenu let icon = NSImage(named: NSImage.Name(rawValue: "pencil")) icon?.isTemplate = true // best for dark mode statusItem.image = icon toggleButton.title = offText}

Make sure to put the statusItem near the top, next to the rest of the variables. statusItem grabs a spot for our app to use menu extras in the top toolbar. Then, in awakeFromNib, we set the menu as our optionsMenu, and, finally, we give it an icon, so that it is easily identifiable and clickable. If we run our app now, the “menu extra” icon will appear at the top! We aren’t quite finished yet. We need to ensure that the drawing space is placed correctly on screen; otherwise, it will be only partially useful.

6. Positioning The Drawing App

To get our view to draw where we want it, we must venture back into Main.storyboard. Click on the window itself, and then select the attributes inspector, the icon fourth from the left in the right-hand pane. Uncheck everything under the “Appearance,” “Controls” and “Behavior” headings, like so:

Attributes inspector
Attributes inspector

We do this to remove all extra behaviors and appearances. We want a clear screen, with no bells and whistles. If we run the app again, we will be greeted by a familiar blue screen, only larger. To make things useful, we need to change the color from blue to transparent. Head back over to ClearWindow and update the blue color to this:

backgroundColor = NSColor(calibratedRed: 1, green: 1, blue: 1, alpha: 0.001)

This is pretty much the same as NSColor.clear. The reason why we are not using NSColor.clear is that if our entire app is transparent, then macOS won’t think our app is visible, and no clicks will be captured in the app. We’ll go with a mostly transparent app — something that, at least to me, is not noticeable, yet our clicks will be recorded correctly.

The final thing to do is remove it from our dock. To do this, head over to Info.plist and add a new row, named “Application is agent (UIElement)” and set it to “Yes.” Once this is done, rerun the app, and it will no longer appear in the dock!

Conclusion

This app is pretty short and sweet, but we did learn a few things. First, we figured out some of the similarities and differences between UIKit and Cocoa. We also took a tour around what Cocoa has in store for us iOS developers. We also (hopefully) know that creating a Cocoa-based app is not difficult, nor should it be intimidating. We now have a small app that is presented in an atypical fashion, and it lives up in the menu bar, next to the menu extras. Finally, we can use all of this stuff to build bigger and better Cocoa apps! You started this article as an iOS developer and grew beyond that, becoming an Apple developer. Congratulations!

I will be updating the public repository for this example to add extra options. Keep an eye on the repository, and feel free to add code, issues and comments.

Good luck and happy programming!

Attributes inspector
Thank you!

(al)

Quick Wins For Improving Performance And Security Of Your Website

When it comes to building and maintaining a website, one has to take a ton of things into consideration. However, in an era when people want to see results fast, while at the same time knowing that their information online is secure, all webmasters should strive for a couple of things:

  • Improving the performance of their website,
  • Increasing their website’s security.

Both of these goals are vital in order to run a successful website.

So, we’ve put together a list of five technologies you should consider implementing to improve both the performance and security of your website. Here’s a quick overview of the topics we’ll cover:

  • Let’s Encrypt (SSL) A free way to obtain an SSL certificate for improved security and better performance.
  • HTTP/2 The successor to the HTTP 1.1 protocol, which introduces many performance enhancements.
  • Brotli compression A compression method that outperforms Gzip, resulting in smaller file sizes.
  • WebP images An image format that renders images smaller than a typical JPEG or PNG, resulting in faster loading times.
  • Content delivery network A collection of servers spread out across the globe, with the aim of caching and delivering your website’s assets faster.

If you aren’t aware of the benefits of improving your website’s performance and security, consider the fact that Google loves speed and, since 2010, has been using website speed as a ranking factor. Furthermore, if you run an e-commerce shop or a simple blog with an opt-in form, a faster website will increase your conversions. According to a study by Mobify, for every 100-millisecond decrease in home-page loading speed, Mobify saw a 1.11% lift in session-based conversions for its customer base, amounting to an average annual revenue increase of $376,789.

The web is also quickly moving towards SSL to provide users with better security and improved overall performance. In fact, for a couple of the technologies mentioned in this article, having an SSL-enabled website is a prerequisite.

Before jumping in, note that even if you can’t (or decide not to) apply each and every one of the suggestions mentioned here, your website would still benefit from implementing any number of the methods outlined. Therefore, try to determine which aspects of your website could use improvement and apply the suggestions below accordingly.

The Front-End Performance Challenge

In case you missed it, we’re currently running a front-end performance challenge to tickle your brains! A perfect opportunity to apply everything you know about Service Workers, HTTP/2, Brotli and Zopfli, and other optimization techniques in one project. Join in! →

Let’s Encrypt (SSL)

If your website is still being delivered over HTTP, it’s time to migrate now. Google already takes HTTPS into consideration as a ranking signal and according to Google’s Security blog, all non-secure web pages will eventually display a dominant “Not Secure” message within the Chrome browser.

That’s why, to start off this list, we’ll go over how you can complete the migration process with a free SSL certificate, via Let’s Encrypt. Let’s Encrypt is a free and automated way to obtain an SSL certificate. Before Let’s Encrypt, you were required to purchase a valid certificate from a certificate-issuing authority if you wanted to deliver your website over HTTPS. Due to the additional cost, many web developers opted not to purchase the certificate and, therefore, continued serving their website over HTTP.

However, since Let’s Encrypt’s public beta launched in late 2015, millions of free SSL certificates have been issued. In fact, Let’s Encrypt stated that, as of late June 2017, over 100 million certificates have been issued. Before Let’s Encrypt launched, fewer than 40% of web pages were delivered over HTTPS. A little over a year and a half after the launch of Let’s Encrypt, that number has risen to 58%.

If you haven’t already moved to HTTPS, do so as soon as possible. Here are a few reasons why moving to HTTPS is beneficial:

  • increased security (because everything is encrypted),
  • HTTPS is required in order for HTTP/2 and Brotli to work,
  • HTTPS is a ranking signal,
  • SSL-secured websites build visitor trust.

How to Obtain a Let’s Encrypt Certificate

You can obtain an SSL certificate in a few ways. Although the SSL certificates that Let’s Encrypt provides satisfy most use cases, there are certain things to be aware of:

  • There is currently no option for wildcard certificates. However, this is planned to be supported in January 2018.
  • Let’s Encrypt certificates are valid for a period of 90 days. You must either renew them manually before they expire or set up a process to renew them automatically.

Of course, if one or both of these points are a deal-breaker, then acquiring a custom SSL certificate from a certificate authority is your next best bet. Regardless of which provider you choose, having an HTTPS-enabled website should be your top priority.

To obtain a Let’s Encrypt certificate, you have two methods to choose from:

  • With shell access: Run the installation and obtain a certificate yourself.
  • Without shell access: Obtain a certificate through your hosting or CDN provider.

The second option is pretty straightforward. If your web host or CDN provider offers Let’s Encrypt support, you basically just need to enable it in order to start delivering assets over HTTPS.

However, if you have shell access and want or need to configure Let’s Encrypt yourself, then you’ll need to determine which web server and operating system you’re using. Next, go to Certbot and select your software and system from the dropdown menus to find your specific installation instructions. Although the instructions for each combination of software and OS are different, Certbot provides simple setup instructions for a wide variety of systems.

Let's Encrypt Certbot home page
Certbot home page (View large version)

HTTP/2

Thanks to Let’s Encrypt (or any other SSL certificate authority), your website should now be running over HTTPS. This means you can now take advantage of the next two technologies we’ll discuss, which would otherwise be incompatible if your website was delivered over HTTP. The second technology we’ll cover is HTTP/2.

HTTP 1.1 was released more than 15 years ago, and since then some major improvements have occurred. One of the most welcome improvements of HTTP/2 is that it allows browsers to parallelize multiple downloads using only one connection. With HTTP 1.1, most browsers were able to handle only six concurrent downloads on average. HTTP/2 now renders methods such as domain-sharding obsolete.

Apart from requiring only one connection per origin and allowing multiple requests at the same time (multiplexing), HTTP/2 offers other benefits:

  • Server push Pushes additional resources that it thinks the client will require in the future.
  • Header compression Reduces the size of headers by using HPACK header compression.
  • Binary Unlike in HTTP 1.1, which was textual, binary reduces the time required to translate text to binary and makes it easier for a server to parse.
  • Prioritization Priority levels are associated with requests, thereby allowing resources of higher importance to be delivered first.

Enabling HTTP/2

Regardless of how you’re delivering the majority of your content, whether from your origin server or a CDN, most providers now support HTTP/2. Determining whether a provider supports HTTP/2 should be fairly easy by going to its features page and checking around. As for CDN providers, Is TLS Fast Yet? provides a comprehensive list of CDN services and marks whether they support HTTP/2.

If you want to check for yourself whether your website currently uses HTTP/2, then you’ll need to get the latest version of cURL and run the following command:

curl --http2 http://yourwebsite.com

Alternatively, if you’re not comfortable using the command line, you can open up Chrome’s Developer Tools and navigate to the “Network” tab. Under the “Protocol” column, you should see the value h2.

Chrome's Developer Tools
Chrome’s Developer Tools h2 (View large version)

Enabling HTTP/2 on nginx

If you’re running your own server and are using an outdated software version, then you’ll need to upgrade it to a version that supports HTTP/2. For nginx users, the process is pretty straightforward. Simply ensure that you’re running nginx version 1.9.5 or higher, and add the following listen directive within the server block of your configuration file:

listen 443 ssl http2;

Enabling HTTP/2 on Apache

For Apache users, the process involves a few more steps. Apache users must update their system to version 2.4.17 or higher in order to make use of HTTP/2. They’ll also need to build HTTPS with the mod_http2 Apache module, load the module, and then define the proper server configuration. An outline of how to configure HTTP/2 on an Apache server can be found in the Apache HTTP/2 guide.

No matter which web server you’re using, your website will need to be running on HTTPS in order to take advantage of the benefits of HTTP/2.

HTTP/2 Vs. HTTP 1.1: Performance Test

You can test the performance of HTTP/2 compared to HTTP 1.1 manually by running an online speed test before and after enabling HTTP/2 or by checking your browser’s development console.

Based on the structure and number of assets that your website loads, you might experience different levels of improvement. For instance, a website with a large number of resources will require multiple connections over HTTP 1.1 (thus increasing the number of round trips required), whereas on HTTP/2 it will require only one.

The results below are the findings for a default WordPress installation using the 2017 theme and loading 18 image assets. Each setup was tested three times on a 100 Mbps connection, and the average overall loading time was used as the final result. Firefox was used to examine the waterfall structure of these tests.

The first test below shows the results over HTTP 1.1. In total, the entire page took an average of 1.73 seconds to fully load, and various lengths of blocked time were incurred (as seen by the red bars).

HTTP 1.1 speed test results
HTTP 1.1 loading time and waterfall (View large version)

When testing the exact same website, only this time over HTTP/2, the results were quite different. Using HTTP/2, the average loading time of the entire page took 1.40 seconds, and the amount of blocked time incurred was negligible.

HTTP/2 speed test results
HTTP/2 loading time and waterfall (View large version)

Just by switching to HTTP/2, the average savings in loading time ended up being 330 milliseconds. That being said, the more resources your website loads, the more connections must be made. So, if your website loads a lot of resources, then implementing HTTP/2 is a must.

3. Brotli Compression

The third technology is Brotli, a compression algorithm developed by Google back in 2015. Brotli continues to grow in popularity, and currently all popular web browsers support it (with the exception of Internet Explorer). Compared to Gzip, Brotli still has some catching up to do in global availability (i.e. in CMS plugins, server support, CDN support, etc.).

However, Brotli has shown some impressive compression results compared to other methods. For instance, according to Google’s algorithm study, Brotli outperformed Zopfli (another modern compression method) by 20 to 26% in compression ratio.

Enabling Brotli

Depending on which web server you’re running, implementation of Brotli will be different. You’ll need to use the method appropriate to your setup. If you’re using nginx, Apache or Microsoft IIS, then the following modules are available to enable Brotli.

Once you’ve finished downloading and installing one of the modules above, you’ll need to configure the directives to your liking. When doing this, pay attention to three things:

  • File type The types of files that can be compressed with Brotli include CSS, JavaScript, XML and HTML.
  • Compression quality The quality of compression will depend on the amount of compression you want to achieve in exchange for time. The higher the compression level, the more time and resources will be required, but the greater the savings in size. Brotli’s compression value can be defined anywhere from 1 to 11.
  • Static versus dynamic compression The stage at which you would like Brotli compression to take place will determine whether to implement static or dynamic compression:
    • Static compression pre-compresses assets ahead of time — before the user actually makes a request. Therefore, once the request is made, there is no need for Brotli to compress the asset — it will already have been compressed and, hence, can be served immediately. This feature comes built-in with the nginx Brotli module, whereas implementing static compression with Apache requires some configuration.
    • Dynamic compression occurs on the fly. In other words, once a visitor makes a request for a Brotli-compressible asset, the asset is compressed on the spot and subsequently delivered. This is useful for dynamic content that needs to be compressed upon each request, the downside being that the user must wait for the asset to be compressed before it is delivered.

A Brotli configuration for nginx users might look similar to the snippet below. This example sets compression to occur dynamically (on the fly), defines a quality level of 5 and specifies various file types.

brotli on;brotli_comp_level 5;brotli_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;

To verify that Brotli is enabled on your server, you can open up Chrome’s Developer Tools, navigate to the “Network” tab, select an asset, and check the Content-Encoding header. This should now be br. Note that Brotli requires HTTPS, so if you’ve correctly gone through the installation and configuration process but still don’t see the br value, then you’ll need to migrate to HTTPS.

Chrome Developer Tools Network tab showing the br encoding
Chrome’s Developer Tools br (View large version)

Otherwise, you can run a simple cURL command, such as:

curl -I https://yourwebsite.com/path/to/your/asset.js

This will return a list of response headers, where you can also check for the Content-Encoding header value. If you’re using WordPress and want to take things a step further by delivering a Brotli-compressed HTML document, check out my WordPress guide to Brotli to learn how.

Brotli Vs. Gzip: Performance Test

To compare Brotli and Gzip compression, we’ll take three compressible web assets and compare them in size and loading speed. Both compression methods were defined with a level 5 compression value.

Having tested the assets three times and taking the average loading speed of each, the results were as follows:

Asset name Gzip size Gzip loading time Brotli size Brotli loading time
jquery.js 33.4 KB 308 ms 32.3 KB 273 ms
dashicons.min.css 28.1 KB 148 ms 27.9 KB 132 ms
style.css 15.7 KB 305 ms 14.5 KB 271 ms

Overall, the Gzipped assets were 77.2 KB in total size, while the Brotli assets were 74.7 KB. That’s a 3.3% reduction in overall page size just from using Brotli compression on three assets. As for loading time, the Gzip assets had a combined total time of 761 milliseconds, while the Brotli assets took 676 milliseconds to load — an improvement of 12.6%.

4. WebP Images

Our fourth suggestion is to use the image format that goes by the name of WebP. Like Brotli, WebP was developed by Google for the purpose of making images smaller. Like JPEG and PNG, WebP is an image format. The primary advantage of serving WebP images is that they are much smaller in size than JPEGs and PNGs. Typically, savings of up to 80% can be achieved after converting a JPEG or PNG to WebP.

The downside of the WebP image format is that not all browsers support it. At the time of writing, only Chrome and Opera do. However, with proper configuration, you can deliver WebP images to supporting browsers, while delivering a fallback image format (such as JPEG) to non-supporting browsers.

WebP still has a way to go before becoming as widespread as JPEG and PNG. However, thanks to its impressive savings in size, it stands a good chance of continued growth. Overall, WebP reduces total page size, speeds up website loading and saves bandwidth.

How to Convert to and Deliver WebP

A few options are available to convert images to WebP format. If you use a popular CMS, such as WordPress, Joomla or Magento, plugins are available that enable you to convert images directly within the CMS’ dashboard.

On the other hand, if you want to take a manual approach, online WebP image converters are available, and certain image-processing apps even come with a WebP format option that you can export to, thereby saving you from having to convert anything at all.

Lastly, if you prefer a more integrated approach, certain image-processing services provide an API that you can use to integrate directly in your web project, enabling you to convert images automatically.

As mentioned, not all browsers currently support WebP images. Therefore, if you serve an image on your website with only a .webp extension, non-supporting browsers will return a broken image. That’s why a fallback is important. Let’s go over three ways to achieve this.

1. Use the picture Element

This method allows you to define the path of a WebP image, as well as the path of the original JPEG within the website’s HTML. With this method, supporting browsers will display the WebP images, while all other browsers will display the default image defined in the last nested child tag within the picture block. Consider the following example:

<picture> <source srcset="images/my-webp-image.webp" type="image/webp"> <img src="images/my-jpg-image.jpg" alt="My image"></picture>

This method implements WebP functionality most widely, while ensuring that a fallback mechanism is in place. However, it might require a lot of modification to the HTML, depending on how large your application is.

2. Modify the Server’s Config File

This method uses rewrite rules defined in the server’s config file to fall back to a supported image format if the browser doesn’t support WebP. Use the appropriate snippet for Apache or nginx according to your web server, and adjust the path/images directory accordingly.

For Apache:

RewriteEngine OnRewriteCond %{HTTP_ACCEPT} image/webpRewriteCond %{DOCUMENT_ROOT}/$1.webp -fRewriteRule ^(path/images.+).(jpe?g|png)$ $1.webp [T=image/webp,E=accept:1] Header append Vary Accept env=REDIRECT_acceptAddType image/webp .webp

For nginx:

# http config blockmap $http_accept $webp_ext { default ""; "~*webp" ".webp";}# server config blocklocation ~* ^(path/images.+).(png|jpg)$ { add_header Vary Accept; try_files $1$webp_ext $uri =404;}

The downside of this method is that it is not recommended if you are going to be using WebP images in conjunction with a CDN. The reason is that the CDN will cache a WebP image if a WebP-supported browser is the first one to request the asset. Therefore, any subsequent requests will return the WebP image, whether the browser supports it or not.

3. Use a WordPress Caching Plugin

If you’re a WordPress user and need a solution that will deliver WebP images to supporting browsers while falling back to JPEGs and PNGs for others, all the while being compatible with a CDN, then you can use a caching plugin such as Cache Enabler. If you define within the plugin that you want to create an additional cached version for WebP, then the plugin will deliver a WebP-cached version to supporting browsers, while falling back to HTML or HTML Gzip for other browsers.

WebP Vs. JPEG: Performance Tests

To demonstrate the difference in size between a WebP and JPEG image, we’ll take three JPEG images, convert them to WebP, and compare the output to the originals. The three images are shown below and carry a size of 2.1 MB, 4.3 MB and 3.3 MB, respectively.

JPEG test image 1
Test JPEG image 1 (View large version)
JPEG test image 2
Test JPEG image 2 (View large version)
JPEG test image 3
Test JPEG image 3 (View large version)

When converted to WebP format, each image reduced significantly in size. The table below outlines the sizes of the original images, the sizes of the WebP versions, and how much smaller the WebP images are than the JPEGs. The images were converted to WebP using lossy compression, with a quality level of 80.

Image name JPEG size WebP size Percentage smaller
test-jpg-1 2.1 MB 1.1 MB 48%
test-jpg-2 4.3 MB 1 MB 77%
test-jpg-3 3.3 MB 447 KB 85.9%

You can compress WebP images using either a lossless (i.e. no quality loss) or lossy (i.e. some quality loss) method. The tradeoff for quality is a smaller image size. If you want to implement lossy compression for additional savings in size, doing so with WebP will render a better quality picture at a smaller size, as opposed to a lossy JPEG at the same level of quality. David Walsh has written a comprehensive post outlining the size and quality differences between WebP, JPEG and PNG.

5. Content Delivery Network

The last suggestion is to use a content delivery network (CDN). A CDN accelerates web assets globally by caching them across a cluster of servers. When a website uses a CDN, it essentially offloads the majority of its traffic to the CDN’s edge servers and routes its visitors to the nearest CDN server.

CDNs store a website’s resources for a predefined period of time thanks to caching. With caching, a CDN’s server creates a copy of the origin server’s web asset and store it on its own server. This process makes web requests much more efficient, given that visitors will be accessing your website from multiple geographic regions.

If no CDN has been configured, then all of your visitors’ requests will go to the origin server’s location, wherever that may be. This creates additional latency, especially for visitors who are requesting assets from a location far away from the origin server. However, with a CDN configured, visitors will be routed to the CDN provider’s nearest edge server to obtain the requested resources, thus minimizing request and response times.

Setting up a CDN

The process for setting up a CDN will vary according to the CMS or framework you’re using. However, at a high level, the process is more or less the same:

  1. Create a CDN zone that points to your origin URL (https://yourwebsite.com).
  2. Create a CNAME record to point a custom CDN URL (cdn.yourwebsite.com) to the URL provided by your CDN service.
  3. Use your custom CDN URL to integrate the CDN with your website (make sure to follow the guide appropriate to your website’s setup).
  4. Check your website’s HTML to verify that the static assets are being called using the CDN’s URL that you defined and not the origin URL.

Once this is complete, you’ll be delivering your website’s static assets from the CDN’s edge servers instead of your own. This will not only improve website speed, but will also enhance security, reduce the load on your origin server and increase redundancy.

Before and After Using a CDN: Performance Test

Because a CDN, by nature, has multiple server locations, performance tests will vary according to where you are requesting an asset from and where the CDN’s closest server is. Therefore, for the sake of simplicity, we’ll choose three locations from which to perform our tests:

  • Frankfurt, Germany
  • New York, United States
  • Toronto, Canada.

If you’re a WordPress user and need a solution that will deliver WebP images to supporting browsers while falling back to JPEGs and PNGs for others, all the while being compatible with a CDN, then you can use a caching plugin such as Cache Enabler. If you define within the plugin that you want to create an additional cached version for WebP, then the plugin will deliver a WebP-cached version to supporting browsers, while falling back to HTML or HTML Gzip for other browsers.

WebP Vs. JPEG: Performance Tests

To demonstrate the difference in size between a WebP and JPEG image, we’ll take three JPEG images, convert them to WebP, and compare the output to the originals. The three images are shown below and carry a size of 2.1 MB, 4.3 MB and 3.3 MB, respectively.

JPEG test image 1
Test JPEG image 1 (View large version)
JPEG test image 2
Test JPEG image 2 (View large version)
JPEG test image 3
Test JPEG image 3 (View large version)

When converted to WebP format, each image reduced significantly in size. The table below outlines the sizes of the original images, the sizes of the WebP versions, and how much smaller the WebP images are than the JPEGs. The images were converted to WebP using lossy compression, with a quality level of 80.

Image name JPEG size WebP size Percentage smaller
test-jpg-1 2.1 MB 1.1 MB 48%
test-jpg-2 4.3 MB 1 MB 77%
test-jpg-3 3.3 MB 447 KB 85.9%

You can compress WebP images using either a lossless (i.e. no quality loss) or lossy (i.e. some quality loss) method. The tradeoff for quality is a smaller image size. If you want to implement lossy compression for additional savings in size, doing so with WebP will render a better quality picture at a smaller size, as opposed to a lossy JPEG at the same level of quality. David Walsh has written a comprehensive post outlining the size and quality differences between WebP, JPEG and PNG.

5. Content Delivery Network

The last suggestion is to use a content delivery network (CDN). A CDN accelerates web assets globally by caching them across a cluster of servers. When a website uses a CDN, it essentially offloads the majority of its traffic to the CDN’s edge servers and routes its visitors to the nearest CDN server.

CDNs store a website’s resources for a predefined period of time thanks to caching. With caching, a CDN’s server creates a copy of the origin server’s web asset and store it on its own server. This process makes web requests much more efficient, given that visitors will be accessing your website from multiple geographic regions.

If no CDN has been configured, then all of your visitors’ requests will go to the origin server’s location, wherever that may be. This creates additional latency, especially for visitors who are requesting assets from a location far away from the origin server. However, with a CDN configured, visitors will be routed to the CDN provider’s nearest edge server to obtain the requested resources, thus minimizing request and response times.

Setting up a CDN

The process for setting up a CDN will vary according to the CMS or framework you’re using. However, at a high level, the process is more or less the same:

  1. Create a CDN zone that points to your origin URL (https://yourwebsite.com).
  2. Create a CNAME record to point a custom CDN URL (cdn.yourwebsite.com) to the URL provided by your CDN service.
  3. Use your custom CDN URL to integrate the CDN with your website (make sure to follow the guide appropriate to your website’s setup).
  4. Check your website’s HTML to verify that the static assets are being called using the CDN’s URL that you defined and not the origin URL.

Once this is complete, you’ll be delivering your website’s static assets from the CDN’s edge servers instead of your own. This will not only improve website speed, but will also enhance security, reduce the load on your origin server and increase redundancy.

Before and After Using a CDN: Performance Test

Because a CDN, by nature, has multiple server locations, performance tests will vary according to where you are requesting an asset from and where the CDN’s closest server is. Therefore, for the sake of simplicity, we’ll choose three locations from which to perform our tests:

  • Frankfurt, Germany
  • New York, United States
  • Toronto, Canada.

As for the assets to be tested, we chose to measure the loading times of an image, a CSS file and a JavaScript file. The results of each test, both with and without a CDN enabled, are outlined in the table below:

Frankfurt, Germany New York, United States Toronto, Canada
Image, no CDN 222 ms 757 ms 764 ms
Image, with CDN 32 ms 81 ms 236 ms
JavaScript file, no CDN 90 ms 441 ms 560 ms
JavaScript file, with CDN 30 ms 68 ms 171 ms
CSS file, no CDN 96 ms 481 ms 553 ms
CSS file, with CDN 31 ms 77 ms 148 ms

In all cases, the loading times for assets loaded through a CDN were faster than without a CDN. Results will vary according to the location of the CDN and your visitors; however, in general, performance should be boosted.

Conclusion

If you’re looking for ways to increase your website’s performance and security, these five methods are all great options. Not only are they all relatively easy to implement, but they’ll also modernize your overall stack.

Some of these technologies are still in the process of being globally adopted (in terms of browser support, plugin support, etc.); however, as demand increases, so will compatibility. Thankfully, there are ways to implement some of the technologies (such as Brotli and WebP images) for browsers that support them, while falling back to older methods for browsers that do not.

As a final note, if you haven’t already migrated your website to HTTPS, do so as soon as possible. HTTPS is now the standard and is required in order to use certain technologies, such as HTTP/2 and Brotli. Your website will be more secure overall, will perform faster (thanks to HTTP/2) and will look better in the eyes of Google.

Smashing Editorial(rb, vf, yk, al, il)

The Role Of Storyboarding In UX Design

To come up with a proper design, UX designers use a lot of different research techniques, such as contextual inquires, interviews and workshops. They summarize research findings into user stories and user flows and communicate their thinking and solutions to the teams with artifacts such as personas and wireframes. But somewhere in all of this, there are real people for whom the products are being designed for.

In order to create better products, designers must understand what’s going on in the user’s world and understand how their products can make the user’s life better. And that’s where storyboards come in.

In this article, we’ll focus on storyboards as a means to explore solutions to UX issues, as well as to communicate these issues and solutions to others. In case you’ve been looking for a way to go from idea to prototype much faster than you usually do, you can download and test Adobe XD, the all-in-one UX/UI solution for designing websites, mobile apps, and more.

What Is A Storyboard?

A storyboard is a linear sequence of illustrations, arrayed together to visualize a story. As a tool, storyboarding comes from motion picture production. Walt Disney Studios is credited with popularizing storyboards, having used sketches of frames since the 1920s. Storyboards enable Disney animators to create the world of the film before actually building it.

Storyboards have long been used as a tool in the visual storytelling media. Here is a Peter Pan storyboard. (Image: Wikia) (View large version)

Stories are the most powerful form of delivering information for a number of reasons:

  • Visualization

    A picture is worth a thousand words. Illustrating a concept or idea helps people to understand it more than anything else. An image speaks more powerfully than just words by adding extra layers of meaning.
  • Memorability

    Stories are 22 times more memorable than plain facts.
  • Empathy

    Storyboards help people relate to a story. As human beings, we often empathize with characters who have challenges similar to our own real-life ones. And when designers draw storyboards, they often imbue the characters with emotions.
  • Engagement

    Stories capture attention. People are hardwired to respond to stories: Our sense of curiosity immediately draws us in, and we engage to see what will happen next.

What Is A Storyboard In UX Design?

A storyboard in UX is a tool that visually predicts and explores a user’s experience with a product. It presents a product very much like a movie in terms of how people will use it. It can help UX designers understand the flow of people’s interaction with a product over time, giving the designers a clear sense of what’s really important for users.

Why Does Storytelling Matter in UX?

Stories are an effective and inexpensive way to capture, convey and explore experiences in the design process. In UX design, this technique has the following benefits:

  • Design approach is human-centered

    Storyboards put people at the heart of the design process. They put a human face on analytics data and research findings.
  • Forces thinking about user flow

    Designers are able to walk in the shoes of their users and see the products in a similar light. This helps designers to understand existing scenarios of interaction, as well as to test hypotheses about potential scenarios.
  • Prioritizes what’s important

    Storyboards also reveal what you don’t need to spend money on. Thanks to them, you can cut out a lot of unnecessary work.
  • Allows for “pitch and critique” method

    Storyboarding is a team-based activity, and everyone on a team can contribute to it (not just designers). Similar to the movie industry, each scene should be critiqued by all team members. Approaching UX with storytelling inspires collaboration, which results in a clearer picture of what’s being designed. This can spark new design concepts.
  • Simpler iteration

    Storyboarding relies heavily on an iterative approach. Sketching makes it possible for designers to experiment at little or no cost and to test multiple design concepts at the same time. Designers can be shot down, move on and come up with a new solution relatively quickly. Nobody gets too attached to the ideas generated because the ideas are so quick and rough.

Storyboarding in the UX Design Process

A storyboard is a great instrument for ideation. In UX design, storyboards shape the user journey and the character (persona). They help designers to string together personas, user stories and various research findings to develop requirements for the product. The familiar combination of images and words makes even the most complex ideas clear.

When Is Storyboarding Useful?

Storyboarding is useful for participatory design. Participatory design involves all parties (stakeholders, UI and UX designers, developers, researchers) in the design process, to ensure that the result is as good as possible. With a compelling storyboard that shows how the solution addresses the problem, the product is more likely to be compelling to the target audience.

It can also be helpful during design sprints and hackathons, when the prototype is being built by multiple people in a very short time. Communicating design decisions with a storyboard really comes in handy.

When Is There No Need for a Storyboard?

If everyone involved in creating a product already shares a solid understanding of how the product should be designed and agrees on the direction of the design and development, then there’s no need for a storyboard.

Use Storyboarding To Illustrate Experiences

Before you start creating a storyboard, it’s important to know exactly why you want to do it. If you don’t have a clear goal in mind, you might end up with a few attractive storyboards, but they won’t give you important insights into the user’s experience.

The Primary Purpose of Storyboards Is Communication

When you search for storyboards online, they always look really nice. You might think that in order to do them properly, you have to be really good at drawing. Good news: You don’t. A great storyboard artist isn’t necessary the next Leonardo da Vinci. Rather, a great storyboard artist is a great communicator.

Thus, it doesn’t matter whether you’re a skilled illustrator. What is far more important is the actual story you want to tell. Clearly conveying information is key. Keep in mind that a designer’s main skill isn’t in Photoshop or Sketch, but rather is the ability to formulate and describe a scenario.

When thinking about storyboarding, most people focus on their ability (or inability) to draw. The good news is that you don’t need to be good at drawing in order to create storyboards. This example is a storyboard frame from Martin Scorsese’s film Goodfellas. (View large version)

How to Work Out a Story Structure?

Before drawing a single line on a piece of paper or whiteboard, prepare to make your story logical and understandable. By understanding the fundamentals of the story and deconstructing it to its building blocks, you can present the story in a more powerful and convincing way.

Each story should have following elements:

  • Character

    A character is the persona featured in your story. Behavior, expectations, feelings, as well as any decisions your character makes along the journey are very important. Revealing what is going on in the character’s mind is essential to a successful illustration of their experience. Each story should have at least one character.
  • Scene

    This is the environment inhabited by the character (it should have a real-world context that includes a place and people).
  • Plot

    The plot should start with a specific event (a trigger) and conclude with either the benefit of the solution (if you’re proposing one) or the problem that the character is left with (if you’re using the storyboard to highlight a problem the user is facing).
  • Narrative

    The narrative in a storyboard should focus on a goal that the character is trying to achieve. All too often, designers jump right into explaining the details of their design before explaining the backstory. Avoid this. Your story should be structured and should have an obvious beginning, middle and end. Most stories follow a narrative structure that looks a lot like a pyramid — often called a Gustav Freytag pyramid, after the person who identified the structure. Freytag broke down stories into five acts: exposition, rising action, climax, falling action (resolution) and denouement (conclusion).
Freytag’s pyramid, showing the five parts, or acts: exposition, rising action, climax, falling action and denouement. Ben Crothers has drawn in a quick story about a guy whose phone doesn’t work.

To make your story powerful, account for these things:

  • Clarity

    The main thing is to make the character, their goal and what happens in their experience as clear as possible. The outcome of the story should be clear for anyone who sees it: If you use a storyboard to communicate an existing problem, end with the full weight of the problem; if you use a storyboard to present a solution that will make the character’s life better, end with the benefits of that solution.
  • Authenticity

    Honor the real experiences of the people for whom you’re designing. If you’re writing a story that isn’t faithful to the product, it won’t bring any value to you and your users. Thus, the more realistic the storyboard is, the better will be the outcome.
  • Simplicity

    Each detail in the story should be relevant to experience. Cut out any unnecessary extras. No matter how good a phrase or picture may be, if it doesn’t add value to the overall message, remove it.
  • Emotion

    Bake emotion into the story. Communicate the emotional state of your character throughout their experience.

Step-by-Step Guide to Creating Your Own Storyboard

With so many things to take into account, creating a storyboard might seem like an impossible task. Don’t worry, the following guide will help you turn out a good one:

  1. Grab a pen and paper.

    You don’t have to use special software to leverage storyboards in the design process. Start with a pen or whiteboard marker, and be ready to experiment.
  2. Start with a plain text and arrows.

    Break up the story into individual moments, each of which should provide information about the situation, a decision the character makes and the outcome of it, whether a benefit or a problem.
  3. Lay out each story as a sequence of moments.
  4. Bake emotion into the story.

    Next, convey what the character feels during each step. I add emoticons at each step, to give a feeling for what’s going on in the character’s head. You can draw in each emotional state as a simple expression.
  5. The same sequence of moments but with emoticons added will give the viewer a sense of what’s going on with the character’s emotional state.
  6. Translate each step into a frame.

    Roughly sketch a thumbnail in each frame of the storyboard to tell the story. Emphasize each moment, and think of how your character feels about it. Visuals are a great way to bring a story to life, so use them wherever possible. You can leave a comment on the back of each frame to give more context. You can also show a character’s thinking with thought bubbles.
  7. Storyboard frames
    Story told in frames (Image: Elena Marinelli) (View large version)
  8. Show it to teammates.

    After you’ve drawn the storyboard, show it to other team members to make sure it’s clear to them.

A Few Notes on Fidelity

High-fidelity storyboards (like the one in the example below) can look gorgeous.

A smile or frown can add emotion to the story and make it come alive for the audience. (Image: Chelsea Hostetter, Austin Center for Design)

However, in most cases, there’s no need for high-fidelity illustration. The level of fidelity will determine how expensive the storyboard will be to create. As I said before, conveying information is what’s important. A more schematic illustration can do that perfectly, while saving a lot of time.

Real-Life Storyboard In Action

Airbnb is a great example of how storyboarding can help a company understand the customer experience and shape a product strategy. To shape the future of Airbnb, CEO Brian Chesky borrowed a strategy from Disney animators. Airbnb created a list of the emotional moments that comprise an Airbnb stay, and it built the most important of those moments into stories. One of the first insights the team gained from storyboarding is that their service isn’t the website — most of the Airbnb experience happens offline, in and around the homes it lists on the website. This understanding steered Airbnb’s next move: to focus on the mobile app as a medium that links online and offline.

(View large version)

Conclusion

Dieter Rams once said, “You cannot understand good design if you do not understand people; design is made for people.” Storyboarding in UX helps you better understand the people you’re designing for. Every bit you can do to understand the user will be tremendously helpful.

This article is part of the UX design series sponsored by Adobe. Adobe XD tool is made for a fast and fluid UX design process, as it lets you go from idea to prototype faster. Design, prototype and share — all in one app.You can check out more inspiring projects created with Adobe XD on Behance, and also sign up for the Adobe experience design newsletter to stay updated and informed.

Further Reading

Smashing Editorial(vf, yk, al, il)

The Front-End Performance Challenge: Make Your Site Blazingly Fast And Win Some Smashing Prizes

Not too long ago, front-end performance was a mere afterthought. Something that was postponed to the end of a project and that didn’t go much beyond minification, asset optimization, and maybe a few adjustments on the server’s config file. But things have changed. We have become more conscious of the impact performance has on the user experience, and the tools and techniques that help us cater for snappy experiences have improved and are widely supported now as well.

It's time for a new challenge! Are you ready?
It’s time for a new challenge! Are you ready?

Time to roll up your sleeves and make use of these possibilities! A while ago, we challenged your coding skills in the CSS Grid Challenge, now we have something new to tickle your brains: The Front-End Performance Challenge. A perfect opportunity to apply everything you’ve learned about Service Workers, HTTP/2, Brotli and Zopfli, resource hint and other optimization techniques in one project. And, of course, there’ll be a smashing prize waiting for one lucky winner in the end.

The Challenge

Show off the performance of your site or your project — use everything you can to make your website perform blazingly fast! Please note that the final visual appearance should be identical before and after (font loading might differ and reflows are acceptable but should be kept to a minimum). You can use this checklist as a guideline and dive into performance optimization for everything from image assets and web fonts delivery to HTTP/2 and Service Workers.

The deadline is the 24th of November, 2017.

Here are a few things you can do to enhance your chances of winning:

  • Optimize as much as you can: We’ll be looking into Lighthouse and WebPageTest as well as the complexity of the site you’re working on.
  • You don’t have to optimize a personal blog: The more advanced the project is, the better chances of winning you have.
  • The most critical metrics are the first meaningful paint and the time to interactive.

So, What Can You Win?

After the deadline has ended, we’ll award a smashing prize to one lucky winner. It has to do with web performance, but see for yourself:

  • A roundtrip flight to London,
  • Full accommodation in a fancy hotel,
  • A ticket to SmashingConf London 2018, a new front-end, performance-focused conference, taking place Feb 7–8, 2018,
  • A Smashing workshop of your choice.

Join In!

Ready to take on the challenge? We’d love to see how you’ll tackle it!

What You Need To Deliver

  • Performance results before and after (using WebPageTest and Lighthouse).
  • A brief description/strategy of the work you did.

Once you have everything together, please send us your entry to challenge@smashingmagazine.com. The deadline is the 24th of November. The winner will be announced on the 4th of December, 2017.

Resources To Get Started

Last but not least, here are some resources to kick-start your performance optimization endeavor. Have fun!

  • Improving Web Fonts Delivery

    Zach Leatherman’s “Comprehensive Guide To Font Loading Strategies” explains the ins and outs of approaches such as FOUT with a Class and Critical FOFT.
  • Improving CSS Delivery

    Dean Hume summarized an easy way to inline critical CSS into the <head> of your pages, even when your site contains different templates.
  • Getting Started With Service Workers

    Lyza Danger Gardner wrote up her gotchas and the bugs she ran into when making a Service Worker. Also be sure to check out her “Pragmatist’s Guide to Service Workers,” a GitHub repository with Service Worker code examples.
  • Dealing With Third-Party Scripts

    Damien Jubeau shares useful tips and techniques to deal with third-party content such as social network widgets, advertising and tracking scripts, and explains their impact on performance.
  • Moving To HTTP/2

    HTTPS is a must for websites. Vladislav Denishev’s complete guide to switching from HTTP to HTTPS helps you master the transition.
  • HTTP/2 Server Push

    Server Push allows you to send site assets to the user before they’ve even asked for them. Jeremy Wagner’s comprehensive guide to Server Push explains everything from how it works to the problems it solves.
  • Progressive Web App

    Progressive Web Apps can replace all of the functions of native apps and websites at once. Ada Rose Edwards summarized do’s and don’ts on how to make them.
  • Brotli/Zopfli Compression

    Do you already use Brotli or Zopfli compression? The lossless data format Brotli appears to be more effective than Gzip and Deflate, while Zopfli is a good solution for resources that don’t change much and are designed to be compressed once and downloaded many times.
  • Resource Hints

    Resource hints are a good way to warm up the connection and speed up delivery, saving time on dns-prefetch, preconnect, prefetch, prerender and preload.
  • CDNs Supporting HTTP/2

    Concerns over performance have long been a common excuse to avoid security obligations. In reality, TLS has only one performance problem: It’s not used widely enough. Everything else can be optimized.
  • Responsive Images

    Eric Portis wrote up how to get responsive images right — with <picture> and srcset.
  • Caching

    If you need a little refresher on caching, Ilya Grigorik and Jake Archibald have got you covered.
  • Optimizing For First Meaningful Paint

    First Meaningful Paint is the paint after which the biggest above-the-fold layout change has happened. The lower its score, the faster that the page appears to display its primary content.

With all of this, you should be well-equipped for the challenge. Need a checklist? Here we go. We’re already looking forward to your submissions and to hearing your optimization stories!

Smashing Editorial(il)