Skip to main content

Feedback patterns

Feedback patterns are UI states that communicate system status to users: loading indicators, success/error toasts, empty states, and progress indicators. Consistent feedback helps users understand what's happening and what to do next.

Pattern categories

CategoryPurposeExamples
LoadingShow work in progressSkeleton screens, spinners, progress bars
ToastCommunicate outcomesSuccess, error, warning, info notifications
EmptyGuide when no contentEmpty lists, no results, first-time states
ErrorExplain problemsConnection errors, validation failures
SuccessConfirm completionSaved, submitted, completed states
ProgressShow advancementStep indicators, upload progress

Loading patterns

When to use skeleton vs spinner

ScenarioPatternWhy
Layout is known, > 500ms expectedSkeletonReduces perceived wait time
Quick operation, < 500ms expectedSpinnerSimpler, less visual noise
Unknown layout or complex contentSpinnerCan't fake what you don't know
Full page loadingPage spinner or Skeleton pageDepends on layout predictability
Button action in progressInline spinnerShows action is processing

Skeleton patterns

// Card skeleton
// Table row skeleton
// Text skeleton

Spinner patterns

// Inline spinner (in button)


// Page spinner (centered)
// Overlay spinner (blocking)

Toast patterns

Toast types

TypeIconColorDurationUse case
SuccessCheckGreen5s auto-dismissOperation completed
ErrorXRedPersistent until dismissedOperation failed
WarningAlertTriangleAmber8s or dismissCaution needed
InfoInfoBlue5s auto-dismissNeutral notification

Toast guidelines

// Success — brief, no action needed
toast.success("Changes saved")

// Error — explain what happened, offer action
toast.error("Failed to save changes", {
  description: "Check your connection and try again",
  action: { label: "Retry", onClick: handleRetry }
})

// Warning — alert to potential issue
toast.warning("Unsaved changes", {
  description: "Your changes will be lost if you leave"
})

// Info — neutral information
toast.info("New version available")

Toast timing

TypeDurationReason
Success5 secondsBrief confirmation
Info5 secondsNon-critical information
Warning8 secondsUser needs time to read and decide
ErrorPersistentUser must acknowledge and potentially act

Empty state patterns

Types

TypeContextIncludes action?
First-timeNo content created yetYes: "Create your first X"
No resultsSearch/filter yielded nothingYes: "Clear filters" or "Try different search"
ClearedUser deleted all itemsOptional: "Undo" or guidance
ErrorFailed to load contentYes: "Retry" or troubleshooting

Templates

// First-time empty state

No projects yet

Get started by creating your first project.

// No search results

No results found

Try adjusting your search or filters.

// Error state

Failed to load

Something went wrong. Please try again.

Progress patterns

Progress bar

// Determinate
Uploading... 75%
// Indeterminate

Step progress

{steps.map((step, i) => (
currentStep && "bg-muted text-muted-foreground" )}> {i < currentStep ? : i + 1}
{i < steps.length - 1 && (
)} ))}

MCP tool usage

Suggest feedback pattern

// Loading a list of items
suggest_feedback_pattern({
  context: 'data-loading',
  contentType: 'list'
})
// Returns: skeleton-card pattern

// Form submission
suggest_feedback_pattern({
  context: 'form-submit',
  dataState: 'success'
})
// Returns: toast-success pattern

// Search with no results
suggest_feedback_pattern({
  context: 'data-loading',
  contentType: 'list',
  dataState: 'empty'
})
// Returns: empty-search pattern

Get feedback template

get_feedback_template({
  pattern: 'skeleton-card',
  includeCode: true
})

get_feedback_template({
  pattern: 'toast-success',
  includeCode: true
})

Accessibility

Loading states

RequirementImplementation
Announce loadingaria-live="polite" on loading container
Announce completionUpdate live region when loaded
Visible focusLoading must not trap focus

Toasts

RequirementImplementation
Announce to screen readersrole="alert" or aria-live="assertive" for errors
Keyboard dismissibleEscape key closes toast
Auto-dismiss considerationErrors should not auto-dismiss

Empty states

RequirementImplementation
Clear headingUse semantic heading for empty state title
ActionableProvide clear next step
Focus managementFocus the CTA when appropriate

Decision flowchart

Is data loading?
├── Yes → Is layout known?
│   ├── Yes → Is it expected to take > 500ms?
│   │   ├── Yes → Use skeleton
│   │   └── No → Use spinner (or nothing for < 200ms)
│   └── No → Use page spinner
└── No → Is there data?
    ├── Yes → Show content
    └── No → What's the reason?
        ├── First time → Empty state with CTA
        ├── No results → Empty state with "clear filters"
        └── Error → Error state with retry

Common mistakes

Using spinners when skeletons are better

// Spinner for known layout
{isLoading ? : }
// Skeleton that matches the layout
{isLoading ? : }

Generic empty states

// Unhelpful empty state
No data
// Contextual empty state with action } title="No projects yet" description="Create your first project to get started." action={} />

Auto-dismissing errors

// Error disappears before user can read it
toast.error("Failed to save", { duration: 3000 })

// Error persists until acknowledged
toast.error("Failed to save", {
  duration: Infinity,
  action: { label: "Retry", onClick: handleRetry }
})

Was this page helpful?

Last reviewed by @jschuyler
All systems operationalDocsDevelopersPlatformPricing
PrivacyTerms© 2026 fndd, LLC