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.
Navigation
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
- Lazy Load Images: Use
loading="lazy"or intersection observer - Minimize Bundle Size: Code-split routes and defer non-critical JS
- Optimize Images: Use WebP, appropriate sizes, and CDN
- Cache Aggressively: Leverage browser caching for static assets
- 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.