The myth of the Happy path
Our job as designers, developers, QAs and product people is to make sure that the experiences we build are communicative with users. We need to build things that make users feel that they are meaningfully interacting with a system, and that they can trust that system to guide them through the complex tasks they are performing.
We often set out to design interactions, taking an idealistic mindset — where we imagine users passing through our interface as we wish they would. We focus on the ‘happy path’ and underplay the likelihood that users will venture out on their own, ignoring the careful flow we’ve mapped for them. They’ll skip our beautiful onboarding wizard, do things in the order they want and discover the interface as they see fit.
The combination of unpredictable user behaviour and the system’s data itself will inevitably create a wide variety of states that need to be represented in your system — whether that’s on an individual component level, a page level or a system level.
This is where robust designs which consider the diversity of behaviour and underlying data become essential to creating a seamless experience.
Designing for so much entropy is hard! This article is meant to help those who need a solid understanding (or refresher) of the depth and breadth of interaction states in digital experiences.
Definition of Interaction States
States in a user interface are visual representations and behaviours which capture and communicate the situation happening ‘in the computer’. Designers build interactions by layering and sequencing interaction states to tell the user what is happening and what’s next. For example, an error state shows the user there is a problem in the value of their data entry.
States are opportunities to catch users wherever they’ve been wandering and nudge them back to a clear course of action. They’re checkpoints to communicate with users and constantly provide them with a layer of context.
To create a robust user experience, you need to understand all of these states and make sure you’ve designed for them. This article is an overview of #allthethings to get your brain going and your checklist growing.
Things to consider
The typical UI element we associate with having different states is buttons (button state: active, focus, hover, disabled) but states serve a higher purpose that can be applied on any UI element: input fields, cards, menu items, whole pages, etc.
Here are some thinking prompts to wrap your head around states:
What types of components are needing some state TLC?
The principles covered in this article apply to the component level as well as the page level. Take some time to audit your design system, take inventory of what you need to work on by looking at both the micro and macro levels. These levels have different design languages, ranging from subtle cursor changes, all the way to full-page overlays, but similar principles apply.
What situation is your user in when navigating these states?
Here, it’s important to give some thought to the possible user journeys to cover. States are the perfect place to show some empathy.
- A power user might have been getting the same frustrating error three times in a row.
- Someone else might have been experiencing connectivity issues for the whole day.
- An occasional user might be pasting in something wrong and wasn’t expecting an error.
- A hurried user who might not have the patience to read through all your interface’s explanations.
Try to map out these possible scenarios and put your team and yourself in their shoes for this exercise.
What do you want to say?
Something that’s true for all states is that they answer the following questions:
- What’s happening
- Why it’s happening
- What to do about it
When you’re mapping out the states your product will need, they should all clearly provide those 3 answers.
How can you leverage states as communication devices?
States provide a place to infuse your brand personality and set tone and emotion.
The goal is to reduce confusion so keeping it straightforward and professional with a light touch of humour tends to work well for dedicated users as well as new ones.
The states of CRUD
Computer science has an acronym for managing data called CRUD — that is; create read, update and delete.
Within interfaces (all of which are built from data), users are essentially doing one (or a variant) of the four following actions: creating, reading, updating and deleting. Let’s use this framework to address all possible states you could encounter in an interface.
In the context of enterprise software, users will either create new stuff in an empty place, fill up existing fields like in a form or table, or bring data over from somewhere else.
Normally a Create flow goes something like this:
- Initiate creation → Provide data → Save → Entity creation confirmed
Let’s start with the state users would initially see when they go to begin that creation process – the ‘empty state’.
The goal of states here is to situate users facing a blank canvas and support them as they create something out of nothing.
First up, the empty state.
Also known as ‘Blank state’, ‘Nothing state’ or ‘Zero State’
Use Case: First time use
This is a core part of the onboarding; that first impression, a moment full of opportunity, your UI’s time to shine. Utilizing empty states comes into play when you want to expose an interface to let people see all the possibilities before they launch into the product.
Here, you want to set the tone and clearly lay out exactly what can be done. You have some control over what users should do first. Of course, you can’t force them into a prescribed direction, but you generally have a lot of real estate to give them actionable prompts.
(Actually you can force them with, say, a step-by-step wizard, but that’s not exactly an empty state)
CTA: Provide starter content
This might not work for all enterprise applications, but providing starter content is a great way to give users something to explore instead of anxiety-inducing white space.
This way, you reduce friction and get them to break the ice while providing in-context examples of all the functionality you offer.
Whimsical provides two example boards when you first sign up
Use Case: No results (search/filter)
Empty states also occur when a search query returns no results.
When no results show up on Slack, the empty state proposes ways to adjust your search query
CTA: Guide user towards action
Similar principles apply here. You want to avoid users from feeling like they’ve hit a wall and have to start over. Empty search results should never actually be empty.
You have the opportunity here to present options for “the next best thing” (if you’re able to analyze their search query and suggest related content).
You can also guide them towards other types of resources like your support documentation or an FAQ.
In any case, don’t just leave them hanging.
Use Case: User cleared (Following an action that cleared the content)
Another type of empty state happens when it’s the direct result of the user clearing the content. For example, if they select all rows of a table and bulk-delete them, we’re back to square A.
Wherever you provide an option to “Clear All” or “Reset”, you are allowing the possibility for this component or page to jump back to a default or empty state.
But! Keeping in mind the context of this empty state’s occurrence, we can react differently:
CTA: Reward them if goal is to be empty
In some cases, a user-triggered empty state is actually an intended goal. Let’s say they’ve completed all tasks of a complex workflow, or they’ve finished reviewing all the attachments of a specific project. This is an opportunity to positively reinforce their work. This is a good place to consider some gamification principles.
The use of imagery (especially images of nature, read more about the biophilia effect) does a great job to reduce stress and put a smile on people’s faces, even if for a short second. It acts as a nice surprise after a complex cognitive task.
When you reach inbox zero, Spark displays a soothing illustration
CTA: Allowing to revert changes
If, on the other hand, clearing everything might not have been the best move from the user’s part, make sure you provide a way to undo that action within the same context. This can be done effectively with a “fake loader“, giving users the chance to hit “Undo” right after the fact.
Once some information has been created, the user can now navigate around and read it.
The goal of states here is to provide helpful guidance as they are consulting the information on the page.
We’ll look at loading states and default states.
Use Case: System loading upon pageload
This is what happens when your software is first loading a page for the user to consult. Depending on the structure of your page, there are many loading ux patterns that you can use.
For example, you can load the different rows of a table and display them one by one, or wait until they’re all loaded before you show them. This depends on the data of your table, the context of your user and what they want to do on that page.
CTA: Reduce perceived wait time
A widespread best practice in this situation is to temporarily show what we call a skeleton screen. Here, you display a grayed-out abstracted version of the screen itself before showing the actual content. This gives the user something to look at and sets expectations as to what the screen is going to look like once it’s done loading.
Use Case: System responding to a user-triggered action
Loading also happens as a consequence of the user asking the system to do something. There are many considerations to take into account here. How long is the task? How critical or destructive is the task? How much real estate should it afford?
CTA: Leverage loading states as a communication/feedback device
The key here is to be clear as to what’s going on and what the user can do while they wait. Your chosen loading state needs to reflect the considerations mentioned above.
A critical task might deserve a full page screen that intentionally disrupts the user flow.
Whereas something that’s running in the background like a file upload can live in a small toast at the bottom of the screen.
Google Drive’s file upload passive drawer
🔗 We’ve gone in depth about many other user-triggered loading use cases in this Pattern Analysis Article.
Also known as ‘initial’ state or ‘zero’ state
Use Case: User landing on a new view or page for the first time
After the screen has finished loading, you need to carefully consider what’s the default, the most useful stuff to show?
Here, we can think of a new team member joining an existing workspace, or a team lead landing for the first time on another team’s project dashboard, for example.
CTA: Keep defaults neutral ☝️👵
Picture this. Maybe the default filters for a chart are set to show only positive values, but this specific user came in to view negative stuff. Make sure your default state is as neutral and global as possible.
The same applies for the default columns shown and the sort order of a table. To increase discoverability, make sure the most content is shown, and that it’s displayed in an expected way (Sorting by most recent on top is typical).
Use Case: User logging back in to their customized view
Now for users logging back in to their usual workspace, they expect to be familiar with the setup and be able to get to work quickly. For software where the UI is customizable (think reordering table columns, filtering the data shown on charts, etc), you need to assume the users have taken advantage of those features and might have gone to great lengths to create a screen that works for them.
CTA: Preserve their custom setup
Here you want to make sure you preserve that state for them across browser sessions to honour the steps they’ve taken to get context.
After the user has read and navigated around a couple pages, they might want to update the content. The goal of states here is to accompany the user in filling up the page and communicate how the system is receiving their data as it is being input.
We’ll be covering interaction states and partial states.
Also known as ‘micro-interactions’ (hover, focused, active, clickable or disabled)
Use Case: Clickable stuff
Interaction states tend to happen on the small scale of all the clickable elements that make up your UI. These can be buttons, input fields, cells of a table, card elements, filter dropdowns, etc.
The default state needs to be informative.
In the case of buttons, the colour needs to reflect the level of importance of its associated action. For example, the “Save” button deserves a brighter accent colour while the “Cancel” button can afford less visibility.
For input fields, the default needs to clearly instruct what type of data is needed. Here, you can leverage placeholders to educate the user.
(For cards or other groups of components, the default needs to be distinct from the active or disabled state.)
Your UI elements’ hover state is a way to indicate exactly what can be interacted with. For example, in a table, you can have a hover state on each individual cell versus a row-wide hover state. This affords different kinds of interactions; inline cell editing as opposed to a clickable row leading to a secondary view or a selecting interaction.
The focused state happens when a user first lands on the UI element via keyboard navigation or when they’re actively typing in a field.
Stripe’s password field only displays instructions and feedback when the field is in focus
When a UI element is in focus, it’s sometimes useful to provide additional in-context instructions. That can be, for example, the requirements for password creation.
When designing a disabled state, you need to find the delicate balance between affordance and accessibility. While you want to make it clear that this specific element isn’t clickable, it still needs to be contrasting enough to be legible. You can use this handy Contrast Checker on webaim.org.
CTA: Provide clarity and guidance
The rule of thumb here is to make it clear what they’re interacting with and how they can interact with it. These small-scale states can be leveraged to add a layer of instruction at the micro level.
Use Case: User has just begun entering data
A typical partial state is when the user has just begun updating a page, or, for example, is in the middle of filling up a complex form.
💡 Did you know?
A “Clean” form is a blank form whereas a “Dirty” form is one where data has been input but it hasn’t been submitted to the system yet.
CTA: Do the work for them
In this case, there are probably many ways you can help them out. If you already have some of their personal details because they’re logged into their account, why not pre-populate the form with that existing information?
Use Case: Too little/few or too many/much
Another situation can be that they’ve put in too little or too much information. Even if you’ve set proper character limits and file size upload limits, there’s always a chance for the user to find loopholes and reach a suboptimal filled state.
CTA: Inform users of improvements that can be made
Mailchimp’s password field informs users of requirements met as they are typing
To avoid them assuming the system has been poorly designed, find smart ways to point out improvements that can be made to their data.
If you’re allowing a large number of characters for an input field, make sure it’s formatted as a text area that auto-expands, this will inform the users how much they should aim to input.
Or if on the contrary, you want to limit them to a smaller number of characters, make sure you indicate that with a real-time character count below the field.
Once they’ve confirmed their input, what does that field now look like? Are you allowing it to wrap on multiple lines or are you forcing it into a smaller sized area that will need an ellipsis? Make sure you consider the context surrounding that information; Does it need to be visible in full? How easy is it to click through and view the whole string?
If you have an optional image field in the interaction state, what will the content look like if the user doesn’t provide said image. Make sure the output UI accounts for all of those possible “what if” situations.
Also known as ‘Perfect state’ or ‘Happy path’
This is the Dribbble-ready screen. Let’s assume you already know about this one. Quick win. Moving on.
In the context of CRUD, deleting is a typical action performed on storage systems. If we mirror that to broader user interactions, we can look at the same type of destructive or permanent actions. This stands for things like sending, exporting, publishing, submitting, moving, etc.
The goal of states here is to provide feedback as to how the system is responding to the intended user action or request.
Use Case: System error
Things like invalid requests or resources not found can seem intelligible for most technical people, but displaying a system error to a user is a unique opportunity to bring a human touch to a machine communication.
CTA: Communicate clearly to the user what the error is and what caused it
Here, it’s important to avoid as much jargon as possible. Unless your users are highly technical and it’s relevant to the task they’re working on.
You want to be clear about exactly what went wrong and why. In some cases, this is a good place to educate users if something they did participated in said error. This way, they’ll be more aware in the future and will potentially avoid triggering the same error again.
Use Case: User error
Sometimes, the error is entirely on the user’s side. Maybe their Wi-Fi connection just abruptly dropped, or they’re trying to upload a file with an unsupported format.
In general good error handling is structured like this:
- Operation was not successful → The reason it was not successful → What to do next
CTA: Tell users what they can do to recover from the error
Here, you want to inform them of (precisely) what went wrong and quickly nudge them towards what they can do to fix it.
They should never have to feel stuck in front of a passive error message.
In the example of an unsupported file upload, many things might be at play. Maybe the file is too big, maybe it’s in the wrong format, maybe another file with the same name exists.
Precision is key here. You really want to provide details so users can come out of the error with new knowledge so they don’t reproduce it.
CTA: Present all errors and don’t persist error state
Along the lines of the Presumption of Innocence legal principle, your assumption should be that your user’s input is going to be good. Only state it as an error once you’re sure the user has come through with their intention of submitting the content.
An example you’ve probably already encountered as a user yourself is an email field giving you an error as soon as you’ve typed in the first character because the system is not detecting an “@” and a “.com”. Frustrating, right? In that case, the user is still in a partial state and no errors should be triggered until the field is out of focus.
In a form submission, showcase all the errors at once instead of one at a time and then wait for them to submit again and prompt the next error in line. Make sure your system returns all errors from the server (if the error is coming from the server) or if errors are a result of validation on the front-end, like in between input fields.
Finally, make sure the error state isn’t persistent. If you’ve shown what the error is and they’ve input something new, don’t assume the error is still there.
CTA: Investigate how to prevent errors from happening again
In any case, you want to make sure you document these errors and their frequency and see how you can reduce their occurrence or fix them permanently. Getting some statistical visibility will help you identify what tends to trigger system errors.
In the case of user input errors, try to see where you can improve the instructions, maybe by distinctly styling the field type (date format, dropdown selection, text area, etc), or tweaking your placeholders, etc.
Also known as ‘Confirmation state’
Use Case: User successfully completed a flow
A user-side success state happens when they complete a task or action with no errors.
CTA: Clear confirmation message or indicator
Here, you want to bring this win to the surface! Make sure you inform the user of this success with a clear confirmation message.
The key here is to ensure your confirmation message is reflective of the scale of the action.
You might use a full page confirmation message, maybe with a joyful illustration, if it was a critical action for which such a break in the user flow is appropriate.
For smaller scale task completions, you might prefer opting for a more subtle banner or toast.
Your confirmation message can even happen inline directly where the user performed the action.
💡 Also worth noting
If you use top-banners to showcase these success messages, make sure they’re distinct from one another as users might trigger multiple tasks at once. If your message is too generic and is simply repeated 3 times, it might become confusing and worrisome for the user.
Use Case: Machine successfully performed a task
A system-side success state is the same but for a task completed by the machine. These tend to be more “in the background” actions such as exporting a dataset, running a machine learning flow or converting a selection of files.
CTA: Determine how much space that confirmation deserve
Again, make sure the importance of that task is reflected in the amount of real estate you give its confirmation message. Is it worth interrupting the user flow for? Is it something minimal that can just “be there” as reference if they really need to know?
Let’s look at some options in order of mission-critical-ness:
Very important machine tasks may deserve a full page moment.
Slightly less critical tasks make sense as a dismissable modal. Note that this too disrupts the user flow pretty abruptly so again, make sure you’re interrupting users justifiably.
5 second toast (dismissable)
Smaller scale background tasks work well as dismissible toasts that are displayed for 5 seconds.
GMail uses a timed toast notification to confirm message sending
Tiny inline indicator (2-3s)
The least disruptive way to communicate a successful machine task is with a subtle inline indicator.
A good example of this is the “Saving” and “Saved to Drive” indicator while working on Google Suite documents. It’s great to constantly show the user what’s up without taking too much of their attention. It’s a small reference point users become used to seeing there if they need it.
Google Drive uses a subtle indicator to constantly communicate whether a document is syncing or synced
CTA: Guide the user to their next action
In both success state cases, you want to guide users towards their next goal so they don’t hang out in front of a big green checkmark forever. What can they do now? Has this unlocked some other action for them? Can you guide them towards another object that needs the same task done to it? Is their next step to communicate this change to another user? Bring these suggestions right there, front and center, in the success state.
Thinking in states increases the overall quality of your product because it forces you to really consider all the places users might find themselves in and then respond accordingly by providing the feedback they need to get going. It might feel like a lot of detail to manage, but constantly communicating relevant actions and next steps differentiates ok products from market-leading products.
It also gives you more control over your product, knowing you’ve mapped out all uncharted territories. Working towards your product being able to handle users in various scenarios improves how maintainable and scalable your platform is. Tightening up details like this could mean the difference in countless new users adopting your product, support tickets being created and the overall success of your product.
How does this fit in with the development workflow?
If you’ve been struggling to implement complex features, (like interaction states) that your users find intuitive to use, we have put together a course (UX Skills for Developers) that will help improve your implementation and overall usability.
🧠 Enjoying this type of deep dive into UX Patterns?
We’ve also gathered all there is to know about data tables interaction.
Sign up to stay in the loop
Receive an email when we publish a new article on design.
States are the foundation of human-computer interaction!Ceara CrawshawFounder @ Pencil & Paper