UI State visualization

State handling is a crucial part of building a user interface. It is important to provide a good user experience when the application is in different states such as loading, error, or empty. This document provides best practices for handling different states in the user interface.

A. Error State

The error state should be the first thing to handle when building a user interface. It is important to provide a clear and helpful error message to the user when something goes wrong. There are three levels of error handling in the user interface: page-level error, component-level error, and action error.

Page-level error

We should show clear and centralized error (e.g. error.tsx) and it is the default error bounding for display. We should have enough message for the error details and also the way to contact support. It should align to the default 404 page error.
notion image
 

Component-level error

Use the correct UI element for the type of error
notion image
For using existing, let’s reference wemakeapp-ui project with the ErrorBox component:
notion image

Action error

In most cases, we should use Component-level error instead of action error. If you are planning to adopt one, try to align with the overall design.
notion image
 

B. Loading State

Page-level loading

For better user experience, we make use of Server-side component to avoid page level loading. However, if there is a case to display page-level loading, please checkout wemakeapp-ui project’s LoadingState component with the centralized spinner example.

Component-level loading

Skeleton’s like loading is the best practice in modern UI development. With the combination of the Skeleton component with shadcn/ui and also the Pulse animation of Tailwind, it would come with a better UI expectation while the user is waiting.
notion image
Example: Tab-based skeleton loading
{ loading ? ( <div className="flex gap-2"> <Skeleton className="h-8 w-12"></Skeleton> <Skeleton className="h-8 w-12"></Skeleton> <Skeleton className="h-8 w-12"></Skeleton> </div> ) : ( <Tabs value={activeTab} onValueChange={(value) => { if (isValidVisibilityToggle(value)) { toggleVisibility(value) } }} className="print:hidden" > <TabsList> <TabsTrigger value="all">All</TabsTrigger> <TabsTrigger value="en">English</TabsTrigger> <TabsTrigger value="fr">French</TabsTrigger> </TabsList> </Tabs> ) }

Link-level loading

If you want to detect loading from <Link... , you can use useLinkStatus from Next.js, or you can use nextjs-toploader to apply globally.

C. Empty State

Page-level empty

It would be basically like the default 404 view, checkout Next.js’s notFound route example.

Component-level

Below are the expected elements and example when there is no data for such view.
notion image