Skip to main content

UI/UX Best Practices

Building a Mini App that feels "native" to Watchee improves user trust and engagement. This guide covers principles and best practices. For specific measurements and spacing values, see the Design Guidelines.

Design Principles

1. Mobile First

Mini Apps are primarily consumed on mobile devices. Design for touch targets (minimum 44x44px) and small screens.

2. Clean & Simple

Avoid clutter. Users are often in a "browsing" mode. Focus each screen on a single primary task.

3. Fast Loading

Optimize your assets. The WebView should load almost instantly. Target under 2 seconds for initial load.

4. Respect System Preferences

Honor the user's system theme (Dark/Light mode) and accessibility settings.

Flat Architecture

  • Avoid Deep Hierarchies: Keep your app navigation flat (max 2-3 levels deep)
  • Clear Back Navigation: Users should always know how to go back
  • Persistent State: Remember scroll position and form state when navigating

Back Button Handling

Watchee handles the native back button. Ensure your app's internal routing handles history correctly so the native back button doesn't unexpectedly close your app.

// Handle back navigation properly
useEffect(() => {
const handleBackButton = () => {
if (canGoBack) {
router.back();
return true; // Prevent default
}
return false; // Let Watchee handle it
};

// Register your handler
}, [canGoBack]);

Theming

Always respect the user's system theme preference:

/* Light mode (default) */
:root {
--bg-primary: #FFFFFF;
--text-primary: #222222;
}

/* Dark mode */
@media (prefers-color-scheme: dark) {
:root {
--bg-primary: #121212;
--text-primary: #FFFFFF;
}
}

Theme Best Practices

  • Never force a specific theme
  • Test both light and dark modes thoroughly
  • Ensure sufficient contrast ratios (WCAG AA minimum)
  • Use semantic color tokens, not hardcoded values

Permissions

Ask at the Right Time

  • Request permissions only when needed, not immediately on launch
  • Provide context before requesting (explain why you need it)
  • Handle denial gracefully with alternative flows

Permission Request Pattern

// Good: Contextual permission request
const handleConnectWallet = async () => {
// Show explanation first
setShowPermissionExplanation(true);
};

const onProceed = async () => {
setShowPermissionExplanation(false);
const result = await MiniKit.requestPermission('wallet');
// Handle result...
};

Safe Areas

Modern devices have notches, rounded corners, and home indicators. Always account for safe areas:

:root {
--safe-area-top: env(safe-area-inset-top, 0px);
--safe-area-bottom: env(safe-area-inset-bottom, 0px);
--safe-area-left: env(safe-area-inset-left, 0px);
--safe-area-right: env(safe-area-inset-right, 0px);
}

/* Apply to fixed elements */
.header {
padding-top: var(--safe-area-top);
}

.bottom-bar {
padding-bottom: calc(24px + var(--safe-area-bottom));
}

Loading States

Always provide feedback for async operations:

Skeleton Loading

// Use skeletons for content that's loading
{isLoading ? (
<div className="animate-pulse">
<div className="h-4 bg-gray-200 rounded w-3/4 mb-2" />
<div className="h-4 bg-gray-200 rounded w-1/2" />
</div>
) : (
<Content data={data} />
)}

Button Loading States

<Button 
disabled={isSubmitting}
className={isSubmitting ? 'opacity-70' : ''}
>
{isSubmitting ? (
<span className="flex items-center gap-2">
<Spinner className="w-4 h-4" />
Processing...
</span>
) : (
'Submit'
)}
</Button>

Error Handling

User-Friendly Errors

  • Never show raw error messages or stack traces
  • Provide actionable next steps
  • Offer retry options when appropriate
// Good error state
<div className="text-center py-8">
<AlertIcon className="w-12 h-12 text-red-500 mx-auto mb-4" />
<h3 className="font-semibold mb-2">Something went wrong</h3>
<p className="text-secondary mb-4">
We couldn't load your data. Please try again.
</p>
<Button onClick={retry}>Try Again</Button>
</div>

Accessibility

Touch Targets

Ensure all interactive elements have a minimum touch target of 44x44px:

.button, .link, .touchable {
min-height: 44px;
min-width: 44px;
}

Focus Management

  • Provide visible focus indicators
  • Manage focus when modals open/close
  • Support keyboard navigation where applicable

Screen Readers

  • Use semantic HTML elements
  • Provide meaningful labels for icons and images
  • Test with VoiceOver (iOS) and TalkBack (Android)

Performance Tips

  1. Lazy Load Images: Use loading="lazy" or intersection observer
  2. Minimize Bundle Size: Code-split routes and defer non-critical JS
  3. Optimize Images: Use WebP, appropriate sizes, and CDN
  4. Cache Aggressively: Leverage browser caching for static assets
  5. Avoid Layout Shifts: Reserve space for async content
// Reserve space to prevent layout shift
<div style={{ aspectRatio: '16/9' }}>
<Image
src={thumbnailUrl}
loading="lazy"
className="w-full h-full object-cover"
/>
</div>

Forms

Input Best Practices

  • Use appropriate input types (email, tel, number)
  • Show validation errors inline, not in alerts
  • Support autofill where appropriate
  • Position submit buttons 24px above the keyboard
<input
type="email"
autoComplete="email"
inputMode="email"
className="w-full p-4 rounded-lg border"
/>
{error && (
<p className="text-red-500 text-sm mt-1">{error}</p>
)}

Gestures

Support common mobile gestures:

  • Pull to refresh: For list/feed content
  • Swipe to dismiss: For modals and sheets
  • Long press: For context menus (with haptic feedback)
// Request haptic feedback for important interactions
MiniKit.sendHapticFeedback('impact', 'medium');

For specific measurements, spacing values, and component specifications, see the Design Guidelines.