← Back to principles
UI
Graceful Degradation & Resilient Error Handling
Statement
Error boundaries, meaningful error states, offline support, and fallback UI are first-class concerns, not edge cases. Users should never see a white screen or cryptic error.
What this means in practice
The UI is designed to fail gracefully at every level. When something goes wrong — a network request fails, a service is down, data is missing, or an unexpected exception is thrown — the user sees a helpful, contextual response rather than a broken screen.
Error boundaries are placed strategically throughout the component tree so that a failure in one area does not take down the entire application.
Every data-fetching path has explicit loading, error, and empty states designed and implemented as part of the feature, not added as an afterthought.
Error messages are meaningful and actionable: they tell the user what happened and what they can do about it, not expose stack traces or generic "Something went wrong" text.
Offline and degraded-network scenarios are considered from the start — the UI communicates connectivity status and, where possible, allows users to continue working.
Fallback UI is designed intentionally: skeleton screens, cached data, or read-only modes are preferred over blank pages or spinners that never resolve.
Errors are logged and reported centrally so the team can detect and respond to issues before users escalate them.
Why this matters
Users judge quality by what happens when things go wrong, not just when they go right.
A white screen or unhandled exception destroys user trust instantly, even if the rest of the product works perfectly.
Cryptic errors generate support tickets and frustrate users who cannot self-serve.
Without error boundaries, a single failing component can crash the entire page — disproportionate impact from a localised problem.
Offline and poor-network scenarios are common in the real world; ignoring them means the product breaks for a significant portion of users.
Meaningful error states reduce support load and improve perceived reliability.
Teams that design for failure from the start ship more resilient products and spend less time firefighting in production.
Practices that meet this principle
Place React error boundaries (or equivalent) around major UI sections so failures are contained and recoverable.
Design loading, error, and empty states for every data-driven component as part of the initial feature work, not as follow-up tickets.
Write user-facing error messages that are specific, non-technical, and suggest a next action (retry, contact support, check connectivity).
Implement retry logic with backoff for transient network failures rather than immediately showing an error.
Use service workers or caching strategies to serve previously loaded content when the network is unavailable.
Display a clear connectivity indicator when the app detects offline or degraded-network conditions.
Log client-side errors to a centralised monitoring service (Sentry, Application Insights, or equivalent) with enough context to reproduce the issue.
Test error paths explicitly: simulate network failures, API errors, and missing data in development and automated tests.
Avoid swallowing errors silently — every caught exception should either be handled meaningfully or reported.
Validation
A project meets this principle when:
No user journey results in a white screen or unhandled exception under any failure condition.
Error boundaries are in place and tested, preventing localised failures from cascading.
Every data-fetching component has designed loading, error, and empty states.
Error messages shown to users are specific, helpful, and free of technical jargon or stack traces.
The application behaves sensibly offline or on poor connections — at minimum communicating the situation clearly.
Client-side errors are logged centrally and reviewed regularly by the team.
Error scenarios are covered in automated tests, not just happy paths.
Potential blockers
Third-party components that throw unhandled exceptions outside the team's control.
Lack of centralised error monitoring tooling.
Design specs that only cover the happy path, leaving error states undefined.
Time pressure leading to error handling being deferred as "we'll add it later".