Error patterns
Error patterns is a collection of error guidelines to support a consistent, intuitive and accessible error handling experience.
- Failure to load
- Invalid or incorrect input
- Failure to perform an action
- Content unavailable
- Expired session
- Known SEV error
- 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

- 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 dismissalTitle: 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


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 (<FormikinitialValues={{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 (<FormLayoutformErrorsAlert={<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><SelectFieldrequiredname="title"label="Title"helperText="Which title do you go by?"><Option value="">Select…</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><TextFieldrequiredname="fullName"label="Name"helperText="Enter your full name"placeholder="Gloria Steinem"/><DateFieldname="startDate"legend="Start date"helperText="Start date must be today or after and within the next year (mm/dd/yyyy)"requiredmin={min}max={max}/><RadioGroupname="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 carriercharges.</Radio><Radio value="email" label="Email">We will email [email protected] and go directly to your spamfolder.</Radio></RadioGroup><CheckboxGroupname="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 isnecessary!</Checkbox><Checkbox value="alerts" label="Alerts">Important things could happen at any time, really, andit's super critical that you hear about it ASAP. Wewould go so far as to call it a P0. P(-1) if we could, butlegally we cannot.</Checkbox></CheckboxGroup><TextAreaFieldrequiredid="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={<CharacterCountlength={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.


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

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.

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.

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.

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 implementation.
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.