Error states
Patterns for handling failure gracefully. Every screen — 404, 500, network drop, inline field error — has a template you can drop in.
Overview
Error states are an inevitable part of any application. The goal isn't to eliminate errors entirely, but to handle them in a way that keeps users informed, maintains their confidence, and provides clear paths to resolution.
Inform
Tell users what happened in plain language
Guide
Provide actionable steps to resolve the issue
Recover
Make it easy to retry or take alternative actions
Inline Errors
Display validation errors directly next to the relevant input field. This pattern provides immediate, contextual feedback that helps users fix issues without searching for what went wrong.
<div>
<label>Email address</label>
<input className={hasError ? 'border-danger-500/60' : 'border-gray-300'} />
{hasError && (
<p className="mt-1.5 text-xs text-danger-400 flex items-center gap-1">
<Cancel01Icon size={12} />
Please enter a valid email address
</p>
)}
</div>Page-Level Errors
When an entire page fails to load, replace the content area with a centered error state. Include a clear icon, descriptive message, and recovery actions like retry or navigation.
Something went wrong
We couldn't load this page. Please try again or contact support if the problem persists.
<div className="text-center py-12">
<div className="w-16 h-16 rounded-full bg-danger-500/10
border border-danger-500/20 flex items-center justify-center mx-auto">
<Alert01Icon size={28} className="text-danger-400" />
</div>
<h3>Something went wrong</h3>
<p>We couldn't load this page. Please try again.</p>
<button onClick={retry}>
<RefreshIcon size={14} /> Try Again
</button>
</div>Toast Errors
Use toast notifications for non-blocking errors that don't require immediate action. Toasts appear temporarily and auto-dismiss, keeping the user's workflow uninterrupted while still surfacing the issue.
Click a button above to see toast errors
function showErrorToast(message: string) {
toast.error(message, {
duration: 4000,
icon: <Cancel01Icon size={14} />,
action: {
label: 'Retry',
onClick: () => retryLastAction(),
},
});
}Empty Error States
When an API call fails and there's no cached data to show, display an illustrative empty state with a helpful message and clear recovery options. This is better than showing a blank page.
Failed to load data
The API returned an unexpected error. This might be temporary — please try again in a moment.
Network Errors
Handle offline and connectivity issues with a dedicated network error state. Include visual cues (wifi-off icon), a concise message, and an automatic or manual retry mechanism.
No connection
Check your internet connection and try again.
function useNetworkStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return isOnline;
}Form Validation
Combine inline field errors with a summary banner for comprehensive form validation. The banner gives an at-a-glance view of all issues, while inline messages guide users to each specific field.
function validateForm(values: FormValues) {
const errors: Record<string, string> = {};
if (!values.name.trim())
errors.name = 'Name is required';
if (!values.email.trim())
errors.email = 'Email is required';
else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(values.email))
errors.email = 'Invalid email format';
return errors;
}
// Display summary banner when errors exist
{Object.keys(errors).length > 0 && (
<div className="bg-danger-500/8 border border-danger-500/20 rounded-lg p-4">
<p className="font-medium text-danger-400">Please fix the following:</p>
<ul>{Object.values(errors).map(e => <li>{e}</li>)}</ul>
</div>
)}Error Boundaries
React Error Boundaries catch JavaScript errors in the component tree and display a fallback UI instead of crashing the entire application. Use them to isolate failures to specific sections.
'use client';
import { Component, type ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
error: Error | null;
}
export class ErrorBoundary extends Component<Props, State> {
state: State = { hasError: false, error: null };
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
console.error('ErrorBoundary caught:', error, info);
// Send to your error tracking service
// reportError(error, info);
}
render() {
if (this.state.hasError) {
return this.props.fallback ?? (
<div className="text-center py-12 px-4">
<div className="w-12 h-12 rounded-full bg-danger-500/10
flex items-center justify-center mx-auto mb-4">
<Alert01Icon size={20} className="text-danger-400" />
</div>
<h3 className="font-semibold mb-1">Something went wrong</h3>
<p className="text-sm text-muted mb-4">
{this.state.error?.message}
</p>
<button onClick={() => this.setState({ hasError: false, error: null })}>
Try Again
</button>
</div>
);
}
return this.props.children;
}
}<ErrorBoundary fallback={<CustomFallback />}>
<RiskyComponent />
</ErrorBoundary>
// Or wrap sections independently
<ErrorBoundary>
<Header />
</ErrorBoundary>
<ErrorBoundary>
<MainContent />
</ErrorBoundary>
<ErrorBoundary>
<Sidebar />
</ErrorBoundary>Best Practices
Follow these guidelines to create consistent, helpful error experiences across your application.
Do
- ✓Use plain, human-readable language
- ✓Provide specific recovery actions (retry, go back)
- ✓Show errors close to where they occurred
- ✓Preserve user input when displaying errors
- ✓Log errors for debugging (console, Sentry)
- ✓Use appropriate severity (error vs warning)
- ✓Add animations for smooth error transitions
- ✓Test error states during development
Don't
- ×Show raw error codes or stack traces to users
- ×Use generic "Something went wrong" without context
- ×Clear form data when showing validation errors
- ×Leave users with no way to recover
- ×Show multiple error modals stacked on top of each other
- ×Blame the user ("You did something wrong")
- ×Ignore errors silently without feedback
- ×Use red for non-error warnings or info states
Related
Components and patterns that work well with error states.