Estimated reading time: 6 min read

Error Handling in the UI, Beyond “Something Went Wrong”

#error-handling#user-experience#error-boundaries#frontend-architecture

Good user interfaces don’t just look reliable, they feel reliable. Error handling plays a huge role in that trust. When something breaks, users shouldn’t feel lost or confused. They should know what happened, why it happened, and what to do next. This post covers practical ways to design error-resilient UIs using a simple and universal rendering flow:

html
<template v-if="loading"></template>
<template v-else-if="error"></template>
<template v-else>{{ data }}</template>

Even though this is pseudocode, the idea applies everywhere, from web apps to native interfaces. Let’s break it down.

1. The Three States of UI Rendering

A robust interface usually has three predictable states: 1. Loading state — when data is being fetched. 2. Error state — when the process fails. 3. Success state — when data is ready to display. Each state should be visually distinct and informative.

Loading

Use skeleton screens, shimmer effects, or simple placeholders. Avoid spinner-only designs; users should sense *structure* even before content loads.

Error

Display a human-friendly message:

  • Be specific: “We couldn’t load your profile” is better than “Something went wrong.”
  • Offer recovery: Provide a retry button or refresh action.
  • Use subtle cues: color, tone, or microanimation (e.g., a soft shake).

Success

When everything loads correctly, content should replace placeholders gracefully, not abruptly. Use fade-in or gentle transitions to make the UI feel stable and polished.

2. Designing Error States

Inline Validation

Catch issues before the user submits data. Validate inputs on blur or in real time. Highlight the exact field in error and guide the user to fix it, don’t just show a red border. Example pattern:

typescript
if (input.isInvalid) {
  showError("Password must be at least 8 characters");
}

Why it works: it prevents frustration before submission and builds confidence.

Fallback UI

When data fetching fails, show useful fallbacks, cached content, placeholders, or retry actions. Never leave users staring at a blank screen. Good example:

typescript
if (error) {
  showMessage("Can’t reach the server. Try again?");
  showButton("Retry");
}

Why it works: it keeps the interaction alive and communicates that the app is still responsive.

3. UI Error Boundaries, The Concept

Even without a specific framework, think of an error boundary as a safe wrapper around your rendering logic. Its goal: Catch unexpected rendering errors and show a stable fallback instead of breaking the entire interface. Conceptually:

typescript
try {
  renderComponent()
} catch (error) {
  renderFallback("Something went wrong. Please try again.")
  logError(error)
}

Why it’s a boundary: You don’t let one failing part crash the whole UI. The purpose of an error boundary is simple: you don’t let one failing part crash the entire interface. It isolates the failure, preserves the rest of the UI, and maintains user trust. A good implementation captures unexpected errors, logs them quietly to a monitoring service like Sentry or Datadog, and replaces the broken view with a stable fallback that communicates clearly without exposing technical details. The user should never see a stack trace or an internal error code. At the same time, errors shouldn’t be hidden silently; they need to be recorded for developers to fix later. Error boundaries work best as one layer of protection, but they don’t replace proper handling of asynchronous or event-driven failures, which must be managed separately to keep the interface consistent and predictable.

4. Handling Asynchronous Errors

Error boundaries don’t catch everything. Network issues, timeouts, or rejected promises need explicit handling. Pattern:

typescript
try {
  data = await fetchData()
} catch (error) {
  showError("Could not load data.")
}

Always pair async operations with:

  • A loading state to inform users.
  • An error state with recovery options.
  • A success state for normal display.

This keeps UI transitions predictable and testable.

5. Writing Human-Friendly Error Messages

Writing human-friendly error messages is about empathy and clarity. A good message should sound like guidance, not an alert. Use plain, direct language that helps the user understand what went wrong and what they can do next, phrases like “Check your internet connection” or “Tap to retry” feel natural and actionable. Keep the tone polite and neutral, avoiding any technical jargon or blame. Users don’t need to see “Error: 500” or “NullReferenceException,” and they shouldn’t feel responsible for system failures. Overly dramatic colors or intrusive alerts only increase anxiety. A calm, conversational tone paired with simple visual cues builds confidence and helps users recover faster.

6. Logging and Monitoring

Every visible error should have an invisible log. Use a client-side error reporter that sends useful metadata:

  • error type and message
  • user actions before the error
  • environment (browser, device)

This data helps developers fix issues faster and prioritize real-world bugs.

7. Summary, Graceful Degradation, Not Panic

Your UI should degrade gracefully, not catastrophically. When errors happen (and they always will), the experience should stay stable, helpful, and calm. Checklist for resilient UIs:

  • Handle loading, error, and success states explicitly
  • Validate inputs early
  • Show friendly fallback views
  • Log all unexpected errors
  • Keep users informed, not overwhelmed

Final Thought

Error handling isn’t just about fixing bugs, it’s part of your design language. A UI that communicates failure clearly also communicates trust. When users see calm, informative responses under pressure, they believe the rest of your system is just as solid.