Skip to content

Error patterns

Error patterns is a collection of error guidelines to support a consistent, intuitive and accessible error handling experience.



  • When content has failed to load, use Crash to bound the affected area.
  • The user should be able to interact with the rest of the app.
  • Crashes can be placed at the component, section, or page level
  • When there are two or more Crashes, apply the Crash to its parent container. As much as possible, users should only be asked to resolve one Crash at a time.

Title: We can’t load {affected area}
CTA: Try again

Title: We can’t load this task
CTA: Try again

Title: We can’t load your payroll history
CTA: Try again

Crash being used when a tab’s content has failed to load.
Crash being used when a tab’s content has failed to load.

  • When the user inputs incomplete or incorrect data, show an inline validation error after the user tabs out of the field. Use the component’s invalid state as indicated in Workbench.
  • When the user submits a form without resolving two or less validation error(s), display an inline validation below the appropriate field with no error list Alert. For submissions with three or more errors, display an inline validation below the appropriate field and use an error list Alert.

  • List of checkboxes: Choose at least one.
  • Dropdown, radio: Choose an answer.
  • DatePicker: Choose a date.
  • Checkbox: Select the checkbox to continue.
  • File uploader: Upload a file.
  • Text field: Enter a [label name] or Enter a valid [label name].
    • Enter a city.
    • Enter a valid email.

  • Invalid text: [Things] have [validation criteria].
    • Names have to include letters.
    • Social Security numbers have 9 digits.
  • Invalid number: The [number] must be [validation criteria].
    • The percentage must be a number between 1–100 without a decimal.
  • Invalid date: Choose a date [validation criteria].
    • Choose a date that's after [employee first name]'s hire date, [date].
    • Choose a date before [date].
    • Choose a date between Mar 13–Mar 17.
    • Choose a date before your first payroll, [date].
  • Invalid upload format: Upload a file [validation criteria]
    • Upload a file smaller than 10MB.
    • Upload a file with one of these formats: .jpg, .png, .gif, .doc, .docx

  • Less than 2 invalid fields: No error Alert, inline validation only. Focus and scroll to the first error.

  • 2 or more invalid fields: Error Alert and inline validation. Focus and scroll to the error Alert.

    Title: Fix the following info to continue
    Description: Your changes were saved, but you need to fix some info before going to the next step:
    Link: Employee’s last day
    Link: Reason for dismissal

    Title: We couldn’t send your form
    Description: Your changes were saved, but we couldn’t send your form. Fix the following info and try again:
    Link: Employee’s last day
    Link: Reason for dismissal


Form validation utilizing inline validation for adding a contractor.
Form validation utilizing inline validation for adding a contractor.
Form validation utilizing  inline validation and an error list Alert for adding an employee.
Form validation utilizing inline validation and an error list Alert for adding an employee.

Tell us more about yourself

Which title do you go by?

Enter your full name

Start date

Start date must be today or after and within the next year (mm/dd/yyyy)

What is the best way to contact you?

We will contact you regularly with alerts or for our newsletter.

We will text updates to (555) 867-5309. Subject to carrier charges.
We will email [email protected] and go directly to your spam folder.
How often would you like to hear from us?
Furthermore I would like to say that this content is necessary!
Important things could happen at any time, really, and it's super critical that you hear about it ASAP. We would go so far as to call it a P0. P(-1) if we could, but legally we cannot.

What have you done in your life?

500 characters allowed

import React from 'react'
import { addYears, formatISO } from 'date-fns'
import { Formik } from 'formik'
import * as Yup from 'yup'
import { Actions, Button, CharacterCount, Heading } from '@gusto/workbench'
import {
Checkbox,
CheckboxGroup,
DateField,
FormErrorsAlert,
FormLayout,
Option,
Radio,
RadioGroup,
SelectField,
SubmitButton,
TextAreaField,
TextField,
} from '@gusto/workbench-formik'
export const FormWithFormErrorsAlert = () => {
const today = new Date()
// representation: 'date' omits the time component of the date leaving
// just '1970-01-01' and not '1970-01-01T00:00:00.000Z'
const min = formatISO(today, {
representation: 'date',
})
const max = formatISO(addYears(today, 1), {
representation: 'date',
})
return (
<Formik
initialValues={{
bio: '',
contact: 'email',
contactConsent: [],
fullName: '',
startDate: '',
title: '',
}}
onSubmit={values => {
alert(JSON.stringify(values, null, 2))
}}
validationSchema={Yup.object({
contact: Yup.string()
.oneOf(
['email'],
'Our text systems are down so please just choose email, ok?',
)
.required('Contact method required'),
contactConsent: Yup.array().required(
'Contact consent is required as part of this offering',
),
})}
>
{({ values }) => {
return (
<FormLayout
formErrorsAlert={
<FormErrorsAlert label="We couldn't add your personal information">
Fix the following fields and try again:
</FormErrorsAlert>
}
fields={
<>
<Heading level={2}>Tell us more about yourself</Heading>
<SelectField
required
name="title"
label="Title"
helperText="Which title do you go by?"
>
<Option value="">Select&hellip;</Option>
<Option value="ms">Ms.</Option>
<Option value="mr">Mr.</Option>
<Option value="other">Other</Option>
<Option value="prefer-not-to-say">Prefer not to say</Option>
</SelectField>
<TextField
required
name="fullName"
label="Name"
helperText="Enter your full name"
placeholder="Gloria Steinem"
/>
<DateField
name="startDate"
legend="Start date"
helperText="Start date must be today or after and within the next year (mm/dd/yyyy)"
required
min={min}
max={max}
/>
<RadioGroup
name="contact"
legend="What is the best way to contact you?"
helperText="We will contact you regularly with alerts or for our newsletter."
>
<Radio value="text" label="Text">
We will text updates to (555) 867-5309. Subject to carrier
charges.
</Radio>
<Radio value="email" label="Email">
We will email [email protected] and go directly to your spam
folder.
</Radio>
</RadioGroup>
<CheckboxGroup
name="contactConsent"
legend="How often would you like to hear from us?"
>
<Checkbox value="newsletter" label="Newsletter">
Furthermore I would like to say that this content is
necessary!
</Checkbox>
<Checkbox value="alerts" label="Alerts">
Important things could happen at any time, really, and
it&apos;s super critical that you hear about it ASAP. We
would go so far as to call it a P0. P(-1) if we could, but
legally we cannot.
</Checkbox>
</CheckboxGroup>
<TextAreaField
required
id="bio"
name="bio"
label="Personal bio"
helperText="What have you done in your life?"
placeholder="In West Philadelphia, born and raised..."
rows={5}
maxLength={500}
counter={
<CharacterCount
length={values.bio.length}
maxLength={500}
/>
}
/>
</>
}
actions={
<Actions justifyContent="center">
<SubmitButton>Submit</SubmitButton>
<Button>Back</Button>
<Button variant="tertiary">Skip</Button>
</Actions>
}
/>
)
}}
</Formik>
)
}

  • When we failed to perform an action (eg. submit, dismiss, delete) show an error alert
  • As much as possible, changes to a form should be retained so that the user can retry submitting without having to re-enter everything
  • Alert should be placed below the Breadcrumb, Stepper or Tab group above the page, section, form or component title. For Drawers and Dialogs, Alert should be placed at the top of the body.

Alert should be placed below the form title.


Title: We couldn’t {do X}
Description: {Cause}. {Next step}.

Title: We couldn’t save your changes
Description: Internet connection lost. Try again.

Title: {Event}. Please try again.

Title: We ran into a problem. Please try again.
Title: We couldn’t connect your Google account. Please try again.

Error Alert being used on a page with a form.
Error Alert being used on a page with a form.
Error Alert being used on a page without a form.
Error Alert being used on a page without a form.

When a page is unavailable because it was archived or deleted, show a painter’s page.

Painters page being used when page is unavailable.
Painters page being used when page is unavailable.

When an object is unavailable because it was archived, deleted, or dismissed, show an informational Alert above its section title. The alert will disappear when the user navigates away or refreshes the page.

Informational Alert on homepage.
Informational Alert on homepage.

When the user’s session has expired, they will be automatically redirected to the sign-in screen where they need to reauthentacaate. After authentication, the user should be redirected where they last left off.

Informational Alert on sign-in page.
Informational Alert on sign-in page.

When there’s a known outage, CX will report the problem to the status page and a top warning banner is logically shown in-app.

We know {status page error}, and we’re working on it. Check our status page for updates.

We know some parts of Gusto are not working properly, and we’re working on it. Check our status page for more information and updates.

SEV banner being used on the homepage.
SEV banner being used on the homepage.

Aim to proactively prevent customers from triggering error states in the first place. Errors also shouldn’t cause more errors to occur. For additional implementation considerations, check out designing for implementationExternal link.

The blast radius of an error should be constrained. As much as possible, errors should not block users from interacting with other parts of Gusto.

The error message should provide helpful context (when available) and clear instructions on how to resolve the problem.

Place errors close to the source of the problem.

Errors should be tracked and prioritized accordingly to prevent further customer pain.


When communicating errors, Gusto takes ownership of the problem. We only apologize or use “please” if we’re certain Gusto caused the error. Avoid assigning blame to the customer.

  • Gusto-caused: We ran into a problem. Please try again.
  • User-caused: You’ve been signed out due to inactivity. Sign back in to continue.


When communicating success, give credit to the customer. When communicating failure, we take responsibility.

  • Success: You’ve connected your Google account.
  • Failure: We couldn’t connect your Google account. Please try again.

Use simple, human language the customer can understand, regardless of how technical the error is.

  • Do: We ran into a problem. Please try again.
  • Don’t: There was an error communicating with the server. Please try again in a few minutes.


Inform, but don’t alarm. Avoid exclamation points, and be mindful of what level of info the customer needs to know to move forward.

Don’t differentiate between errors caused by Gusto or third-parties, unless knowing who’s at fault is valuable to the customer, and we’re asking them to take action.

  • Do: We couldn’t authenticate with Google. Sign back in with your Google account and try again.

  • Do: We ran into a problem. Please try again.
  • Don’t: Google ran into a problem. Please try again.

When an alert is shown as a result of failure to perform action:

  • User must be returned to the previous view where they took action.
  • User focus should be on the alert.
  • The error should be announced by the screenreader.

When a Crash is shown as a result of failure to load:

  • Use the right heading level based on the Crash’s placement on the page. If it appears on the page, use h1. If it’s appears after an h1 title, use h2.
  • User focus should remain where the user is.


Failure
The observed difference from the expected function.
Error
The mistake which caused the failure to occur.
Brownout

A time period when incoming requests (API calls, page views, form saves) slow due to a heavy, rapid influx of requests or service responding slowly.

Validation failure

Errors where the action of a customer does not meet expectations. This may be the result of miscommunication, mistyping, lack of clarity, or in the rarest case intentional abuse.

Runtime failure

Failures that occur while the application is being used not due to Gustomer’s use. These errors are commonly from untested code, unknown edge cases, or a customer’s expired session needing them to log in.

System failure

Internal, backend errors in a system that Gusto owns. This can be represented by either a portion or the entire system is unavailable or unresponsive.

Service failure

These failures arise when an external service or 3rd party dependency (i.e. Google, AWS, Salesforce) acts in an unexpected manner. This could be in both an outage, temporary disruption, or brownout.

Network failure

Failures that are caused by a slow or unstable internet connection or lack of server availability.