Skip to Content
PatternsMobile-First

Mobile-First

Most Mukoko users access the ecosystem on mobile devices, often on mid-range Android phones with variable network quality. Mobile is not an afterthought — it is the primary platform.

Design principles

  1. Start with mobile — design the smallest screen first, then enhance for larger screens
  2. Optimize for touch — 48px minimum targets, thumb-friendly action placement
  3. Minimize data usage — lazy load images, compress assets, avoid unnecessary requests
  4. Work offline — core features should function without a network connection
  5. Fast first paint — show skeleton content immediately, load data progressively

Bottom navigation

Use mukoko-bottom-nav for app-level navigation on mobile. This places primary navigation in the thumb zone:

{/* Mobile: bottom nav. Desktop: sidebar */} <div className="hidden md:block"> <MukokoSidebar /> </div> <div className="fixed inset-x-0 bottom-0 md:hidden"> <MukokoBottomNav items={[ { label: "Home", href: "/", icon: Home }, { label: "Search", href: "/search", icon: Search }, { label: "Events", href: "/events", icon: Calendar }, { label: "Profile", href: "/profile", icon: User }, ]} /> </div>

Limit bottom nav to 4-5 items maximum. More items create touch targets that are too small.

Sheet over dialog

On mobile, use Sheet (slides from the bottom) instead of Dialog (centered modal). Sheets are easier to reach and dismiss:

{/* Responsive: Sheet on mobile, Dialog on desktop */} <div className="hidden sm:block"> <Dialog> <DialogTrigger asChild><Button>Open</Button></DialogTrigger> <DialogContent>{/* Content */}</DialogContent> </Dialog> </div> <div className="sm:hidden"> <Sheet> <SheetTrigger asChild><Button>Open</Button></SheetTrigger> <SheetContent side="bottom">{/* Same content */}</SheetContent> </Sheet> </div>

Touch targets

All interactive elements must be at least 48px in the touch dimension:

{/* Correct — sufficient touch target */} <button className="flex h-12 items-center gap-3 px-4"> <Icon className="size-5" /> <span>Menu item</span> </button> {/* Incorrect — too small for touch */} <button className="h-6 px-2 text-xs">Tap</button>

For lists of tappable items, use the Item component which provides the correct touch target:

<ItemGroup> <Item> <ItemContent> <ItemTitle>Event name</ItemTitle> <ItemDescription>15 June 2026</ItemDescription> </ItemContent> </Item> </ItemGroup>

Progressive loading

Sequential mount

Use the lazy-section pattern to load page sections one at a time, reducing the initial bundle size:

<LazySection id="hero" priority={0}> <HeroSection /> </LazySection> <LazySection id="stats" priority={1}> <StatsSection /> </LazySection> <LazySection id="chart" priority={2}> <ChartSection /> </LazySection>

Image loading

  • Use loading="lazy" on images below the fold
  • Provide explicit width and height to prevent layout shift
  • Use thumbnail/placeholder images for progressive loading

Memory pressure

On low-memory devices, the useMemoryPressure hook detects pressure and can unload offscreen sections:

const { isUnderPressure } = useMemoryPressure() {isUnderPressure ? ( <Skeleton className="h-64" /> ) : ( <ExpensiveChart data={data} /> )}

Network awareness

Design for intermittent connectivity:

{/* Offline indicator */} {!navigator.onLine && ( <Alert variant="warning" className="mx-4 mt-2"> <AlertTitle>You are offline</AlertTitle> <AlertDescription> Some features may be limited. Changes will sync when you reconnect. </AlertDescription> </Alert> )}

Text input on mobile

  • Use appropriate type attributes for on-screen keyboard optimization
  • type="email" shows the @ key
  • type="tel" shows the number pad
  • inputMode="numeric" for numeric input without the tel format
  • Use autoComplete to reduce typing
<Input type="email" autoComplete="email" inputMode="email" /> <Input type="tel" autoComplete="tel" /> <Input inputMode="numeric" pattern="[0-9]*" />

Responsive patterns summary

PatternMobileDesktop
NavigationBottom navSidebar
ModalsBottom sheetCentered dialog
Data tablesCard listFull table
SidebarHidden/overlayPersistent
Stats grid2 columns4 columns
Filter barCollapsibleAlways visible
ImagesFull widthConstrained
Last updated on