Opsi UI untuk Capacitor: Ionic Framework 8/9 dengan komponen mobile-grade, atau Tailwind/shadcn-style di React/Vue biasa. Serta pola mobile UX seperti navigation stack, tabs, modals, dan pull-to-refresh.

Setelah di episode 14 kita menguasai SPM dependency management, pada episode ini kita membahas framework UI — bagaimana membuat tampilan aplikasi mobile yang terasa native. Capacitor tidak memaksa framework tertentu, tetapi pilihan UI framework sangat mempengaruhi UX dan kecepatan development.
Mengapa framework UI penting? Karena native-like UI bukan hanya tentang fungsionalitas — tetapi tentang nuansa: animasi transisi yang halus, gesture yang natural, dan komponen yang terasa familiar di tangan user mobile.
Ionic adalah framework UI yang dirancang khusus untuk hybrid mobile. Ionic 8 (2024) dan Ionic 9 (Agustus 2026) menyediakan komponen yang terasa native di iOS dan Android.
npm install @ionic/react @ionic/react-router ioniconsimport { IonApp, IonPage, IonHeader, IonToolbar, IonTitle, IonContent, IonButton } from '@ionic/react';
function HomePage() {
return (
<IonApp>
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>My App</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
<IonButton expand="block">Tap Me</IonButton>
</IonContent>
</IonPage>
</IonApp>
);
}Keunggulan Ionic:
IonButton, IonCard, IonModal, IonTabs.Jika kalian sudah nyaman dengan Tailwind, tidak perlu beralih ke Ionic. Tailwind + komponen shadcn-style bisa menghasilkan UI yang bersih dan profesional.
npm install -D tailwindcss @tailwindcss/vite@import "tailwindcss";
:root {
--safe-top: env(safe-area-inset-top);
--safe-bottom: env(safe-area-inset-bottom);
}
body {
padding-top: var(--safe-top);
padding-bottom: var(--safe-bottom);
}Kelebihan: lebih ringan, lebih fleksibel, komunitas Tailwind yang besar. Kekurangan: harus membangun komponen mobile (tabs, modals) dari nol atau pakai library tambahan.
Untuk project sangat ringan, web components standar sudah cukup:
<template id="my-button">
<style>
:host { display: block; }
button {
width: 100%;
padding: 14px;
border-radius: 10px;
background: var(--ion-color-primary);
color: white;
font-size: 16px;
}
</style>
<button><slot></slot></button>
</template>Mobile app menggunakan navigasi bertingkat (stack), bukan routing datar seperti web:
Dengan Ionic:
import { IonRouterOutlet } from '@ionic/react';
import { Route, Redirect } from 'react-router';
function App() {
return (
<IonRouterOutlet animated>
<Route exact path="/home" component={HomePage} />
<Route exact path="/home/:id" component={DetailPage} />
<Route exact path="/home/:id/edit" component={EditPage} />
<Redirect from="/" to="/home" />
</IonRouterOutlet>
);
}Bottom navigation (tabs) adalah pattern paling umum di mobile:
import { IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel, IonRouterOutlet } from '@ionic/react';
import { home, search, person } from 'ionicons/icons';
function TabLayout() {
return (
<IonTabs>
<IonRouterOutlet>
<Route exact path="/tab/home" component={HomePage} />
<Route exact path="/tab/search" component={SearchPage} />
<Route exact path="/tab/profile" component={ProfilePage} />
</IonRouterOutlet>
<IonTabBar slot="bottom">
<IonTabButton tab="home" href="/tab/home">
<IonIcon icon={home} />
<IonLabel>Home</IonLabel>
</IonTabButton>
<IonTabButton tab="search" href="/tab/search">
<IonIcon icon={search} />
<IonLabel>Search</IonLabel>
</IonTabButton>
<IonTabButton tab="profile" href="/tab/profile">
<IonIcon icon={person} />
<IonLabel>Profile</IonLabel>
</IonTabButton>
</IonTabBar>
</IonTabs>
);
}Modal di mobile berbeda dari web — ia slide up dari bawah:
import { IonModal, IonButton, IonContent } from '@ionic/react';
function ModalExample() {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<IonButton onClick={() => setIsOpen(true)}>Open Modal</IonButton>
<IonModal isOpen={isOpen} onDidDismiss={() => setIsOpen(false)}>
<IonContent>
<p>Modal content di sini</p>
<IonButton onClick={() => setIsOpen(false)}>Close</IonButton>
</IonContent>
</IonModal>
</>
);
}import { IonRefresher, IonRefresherContent, IonContent } from '@ionic/react';
function RefresherExample() {
async function handleRefresh(event: any) {
await fetchData();
event.detail.complete();
}
return (
<IonContent>
<IonRefresher slot="fixed" onIonRefresh={handleRefresh}>
<IonRefresherContent />
</IonRefresher>
{/* content */}
</IonContent>
);
}Tip
Untuk app yang sudah ada di React/Vue, mulai dari Tailwind + pola navigasi native. Baru pertimbangkan Ionic jika butuh komponen mobile-grade lengkap. Jangan pakai dua framework UI sekaligus.
Pada episode 15 ini, kalian telah memahami:
Di episode 16 selanjutnya, kita akan membahas performance optimization — startup time, ukuran bundle, animasi 60fps di WebView, profiling dengan Chrome DevTools remote & Safari Web Inspector, serta praktik mengurangi cold start dan jank. Sampai jumpa!