Why state handling matters
User attention is short. Your UI must be clear at every step. A missing state or a confusing spinner breaks trust. That leads to drop off. Small improvements raise satisfaction and retention.
Design rules you can apply now
- Show immediate feedback. If a tap leads to work, respond within 100 to 200 milliseconds with a visual change. That confirms the action.
- Delay heavy indicators. Only show a full screen spinner after 300 milliseconds. For shorter loads use a subtle micro animation or a skeleton.
- Prefer skeletons for lists. Skeleton rows set expectations about layout and reduce perceived wait time.
- Keep operations cancellable. If an operation takes longer than 2 seconds let the user cancel or swipe back.
- Use meaningful placeholders for empty states. Explain what empty means and offer one clear action to proceed.
- Provide a clear retry path for errors. Show the exact problem and a focused action to retry or recover.
Timing thresholds
Set thresholds to match human perception. Use them as defaults and tune by telemetry.
- 0 to 200 ms: Instant. No loading indicator.
- 200 to 800 ms: Use micro feedback like a button state change or a small progress bar.
- 800 ms to 3 s: Show a skeleton or an inline spinner.
- 3 s and above: Show a full screen loading view and a cancel option.
Loading states
Loading states must communicate progress and avoid blocking users unnecessarily.
- Differentiate types of loading. Use inline loaders for small updates. Use full screen loaders for initial data fetches.
- Use skeletons for lists and repeated items. They set the layout and reduce jarring shifts when content loads.
- Show estimations when possible. A progress bar with percent is better than an indeterminate spinner for long uploads or downloads.
- Make background refresh quiet. Do not interrupt the user for silent sync. Show a subtle badge or a pull-to-refresh indicator only when the user requests refresh.
Empty states
Empty states are an opportunity. Use them to teach and to drive action.
- Explain the cause. State what is missing. Use plain language.
- Offer one clear next step. A primary action is better than multiple options.
- Include helpful microcopy. Show one short tip, a link to documentation, or a quick tutorial link.
- Persist a small help button. Let users access guidance from any empty screen.
Error states
Errors should be honest and useful. Avoid generic messages like "Something went wrong."
- Surface the cause. Distinguish network, authorization, and server errors.
- Use friendly language. Write short sentences. Offer the next action.
- Provide recovery actions. Retry, switch network, or open account settings.
- Throttle retries. Use exponential backoff for automatic retries. Show a retry button for manual attempts.
Edge-case handling
Think about partial data, intermittent network, duplicates, and background state changes.
- Partial data. Render what you have. Mark missing pieces clearly with placeholders and an inline loader for the missing fields.
- Offline mode. Offer read-only access to cached content. Show an offline banner with a clear sync button.
- Duplicate actions. Debounce taps for 300 to 500 ms. Disable primary controls while a request is inflight.
- Background and foreground. Cancel long tasks when the app goes to background. Resume or restart when the app returns to foreground.
Accessibility baked into states
Make every state accessible to VoiceOver users and to users who change system settings.
- VoiceOver labels. Describe loaders, empty screens, and errors. Use isAccessibilityElement and accessibilityLabel. For a loading spinner say "Loading messages" not "Loading".
- Dynamic Type. Support large text sizes for all state screens. Test at the largest accessibility size.
- Color contrast. Ensure your empty and error screens meet WCAG contrast ratios. Use clear visual cues in addition to color.
- Reduce motion. Respect the system reduce motion setting. Provide a static placeholder when it is enabled.
- Focus management. When an error appears after a network failure move VoiceOver focus to the error banner. When a modal completes move focus to the updated content.
Telemetry and metrics
Measure to validate changes and to find regressions.
- Track state durations. Log how long loads stay in each state. Watch for growth over time.
- Record user recovery actions. Count retries, cancellations, and fallback flows.
- Measure empty state conversions. Track how often users follow the suggested action.
- Monitor crash-free rates. Correlate crashes with state transitions.
Example checks before merge
- Does the initial load show an appropriate view for 0.3 s, 1 s, and 5 s?
- Is every empty screen actionable?
- Do error messages name the failure and suggest a fix?
- Are loaders and error banners accessible to VoiceOver?
- Does the app behave well on slow and offline networks?
Small changes with big wins
Start with these quick wins. Add a skeleton for your main list. Replace full screen spinners with inline feedback. Add a clear retry button for the most common network call. Test with VoiceOver and large text sizes. Use telemetry to confirm impact. These steps will make your feature feel faster and more reliable to your users.