PABCD Initiative Documentation Hub pabcd_initiative codexclaw cli-jaw

ux-states

Source: dev-uiux-design/references/ux-states.md

1. Onboarding & First-Run Patterns

Design first-run experiences that deliver value immediately.

Rules:

  • Enable action before profile completion. Let users DO something valuable first.
  • Deliver core value within 60 seconds of first interaction.
  • Collect only essential data on Day 1. Gather intent early (goal/use case) to personalize.
  • Make every tutorial, walkthrough, and setup step skippable.
  • Provide starter content, templates, or sample data so the first screen is never empty.

Welcome Screen Anatomy

┌─────────────────────────────────┐
│  [Brand mark / illustration]    │
│                                 │
│  Headline (value proposition)   │
│  Subtext (1 line, what to do)   │
│                                 │
│  [Primary CTA: Start Action]    │
│  Skip / Later (text link)       │
└─────────────────────────────────┘

Feature Discovery Patterns

PatternWhen to UseWhen to Avoid
Contextual tooltipFirst time a feature appearsAlready-discovered features
Coach mark (spotlight)Complex multi-step workflowsSimple, self-explanatory UI
Feature tour (multi-step)Major new feature releaseFirst-time onboarding (too heavy)
Empty-state-as-onboardingDashboard/list views with no dataViews that always have data
Progressive profileCollecting user preferences over timeOne-time setup flows

Permission Request Timing

Ask for permissions at the moment of need, not during onboarding. Explain why before asking: "To send you updates, we need notification access."

---

2. Empty State Design

Every major view MUST have a designed empty state. Never leave a screen blank or show a generic "No data" message.

Three Types

TypeWhenTone
InformationalContent absent, user hasn't acted yetNeutral, guiding: "Add your first project to get started"
Action-orientedFeature requires user input to populateEncouraging, CTA-forward: "Create your first workspace"
CelebratoryAll items completed/resolvedPositive reinforcement: "All caught up!"

Composition Template

┌─────────────────────────────────┐
│      [Illustration / Icon]      │
│     (contextual, not generic)   │
│                                 │
│   Headline (short, specific)    │
│   Subtext (1 line, optional)    │
│                                 │
│   [Primary CTA button]          │
│   Secondary link (optional)     │
└─────────────────────────────────┘

Rules:

  • Tailor copy to the specific scenario: first-use vs empty-search vs completed-tasks each get different messages.
  • Use direct language ("Your cart is empty") not vague ("Nothing here yet").
  • Single prominent CTA. Never show multiple competing actions.
  • Match visuals to design system (colors, fonts, spacing, illustration style).
  • For search results: suggest alternatives ("Try adjusting filters or checking spelling").
  • For first-use: provide starter templates or sample data to reduce uncertainty.
  • Use ARIA live regions to notify assistive technology when content state changes.

---

3. Error State Taxonomy

Every error message follows: (1) what happened, (2) why, (3) what to do next.

PatternWhenRecoverySeverity
Inline validationStructured input (email, password)Real-time feedback as user typesPreventative
Toast notificationLow-severity temporary errorsBrief 3-5s non-modal, optional retryAmbient
BannerSystem-wide warnings (maintenance, degraded)Persistent, dismissible, with details linkAmbient
Full-page errorCritical system failure, content cannot loadCentered: Try Again + escape hatchInterrupting
Empty-state errorNo search results, empty filtered listsSuggest alternatives or creation pathsInformational
Network failureLost connectionEnable offline mode where possible, auto-retry on reconnectInterrupting
TimeoutServer response > 10–15sOffer Cancel + Keep WaitingInterrupting
Permission deniedUser lacks role accessOffer Request Access or role upgrade infoInformational
404 Not FoundURL does not existBranded page + search bar + top nav links. Never a dead endInformational
Rate limitingAPI limits exceededBanner with wait time + link to docsAmbient

Rules:

  • Never use generic messages ("Invalid input"). Be specific ("Password must contain at least 8 characters").
  • Never blame the user. "Enter a valid email address" not "You entered an invalid email."
  • Every error needs one primary action (retry, fix, request access) and one optional escape hatch (home, support).
  • For multi-step forms: surface errors at each step, not at final submission.
  • Never rely on color alone for error indication. Pair red with icons AND text.
  • Match error tone to product surface: playful for casual apps, professional for finance/health.
  • Severity hierarchy: Preventative (inline) → Ambient (toast) → Interrupting (full-page). Use full-page ONLY for showstoppers.

---

4. Loading State Patterns

Skeleton Loaders

  • Match the skeleton to the real layout (same heights, widths, spacing).
  • Shimmer direction: left-to-right, single wave, 1.5–2s duration.
  • Animate with background-position on a gradient — not opacity flicker.
  • Show skeleton for a minimum of 300ms to avoid flash.

Decision Table

ScenarioPatternDetails
Page initial loadSkeletonMatch layout, show immediately
Data refreshStale-while-revalidateShow old data + subtle loading indicator
Action resultOptimistic updateApply change immediately, revert on error
Long operation (> 3s)Progress bar / percentageShow estimated time if available
Background processToast / status badgeNon-blocking notification
Infinite scrollSkeleton rows at bottom3–5 placeholder rows

Skeleton → Content Transition

Fade skeleton out, fade content in. Duration: 200ms. Never pop — the transition must feel continuous.

---

5. Progressive Disclosure

Show ONLY essential information first. Defer advanced/optional content behind explicit user action.

Three Categories

CategoryMechanismExample
Step-by-stepSequential multi-stageCheckout: shipping → method → payment → confirm
ConditionalHidden until explicitly requested"Advanced Settings" toggle
ContextualContent surfaced based on prior inputShow company fields only after selecting "Business"

UI Components for Disclosure

ComponentUse For
AccordionStructured information (FAQ, settings groups)
TabsCategorized content at the same level
Conditional form fieldsShow/hide based on prior input
Tooltip / popoverContext help on hover/click
"Show more" linkOptional fields in forms
Lazy loadingSupplementary content on scroll

Rules:

  • Discoverability test: hidden features MUST be findable when needed. If users cannot discover advanced options, the pattern has failed.
  • Do NOT use progressive disclosure when users need to compare information simultaneously.
  • Do NOT hide critical safety information or destructive-action warnings.
  • Save progress at each disclosure step. Users must be able to return without losing work.
  • Default to simple. "Advanced Settings" collapsed by default.
  • For forms: show only required fields initially. Optional fields appear via "Show more options."
  • For dashboards: show summary KPIs first. Drill-down detail on click.
  • For settings: group into Basic (visible) and Advanced (collapsed).

---

6. Error Page Taxonomy

PageContentTone
404Branded page + search bar + top nav + "popular pages" linksFriendly, helpful
500Branded page + "We're working on it" + status page link + retry buttonReassuring, transparent
MaintenanceBranded page + estimated return time + status page linkProfessional, time-bound
OfflineService worker cached page + "You're offline" + cached content listInformative, functional

Rules:

  • Every error page maintains site branding (logo, colors)
  • Every error page has a path back (nav, home link, search)
  • Never show stack traces or technical errors to end users
  • 500 page: auto-retry with exponential backoff (2s, 4s, 8s) + manual retry button
  • Offline page: cache in service worker during first successful visit
  • Maintenance page: deploy as static HTML (doesn't depend on the broken server)

Service Worker Offline Shell

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('offline-v1').then(cache => cache.add('/offline.html'))
  );
});

self.addEventListener('fetch', (event) => {
  if (event.request.mode === 'navigate') {
    event.respondWith(
      fetch(event.request).catch(() => caches.match('/offline.html'))
    );
  }
});