Best Mobile Frameworks in 2026: React Native vs Flutter vs Capacitor
·PkgPulse Team
TL;DR
React Native (Expo) for web developers; Flutter for native-feel performance; Capacitor for web-to-mobile. React Native + Expo (~3M weekly downloads) gives web developers the fastest path to mobile with a massive npm ecosystem. Flutter (~8M downloads) from Google uses Dart and its own rendering engine — pixel-perfect UI across all platforms. Capacitor (~900K downloads) wraps a web app in a native shell — best for Progressive Web Apps going native.
Key Takeaways
- Expo (React Native): ~3M weekly downloads — managed workflow, OTA updates, EAS build service
- Flutter: ~8M downloads — Dart language, custom renderer, 60/120fps on all platforms
- Capacitor: ~900K downloads — web app → native, same codebase as your PWA
- React Native New Architecture — JSI + Fabric renderer, near-native performance in 2026
- Expo Router — file-based routing for web + mobile unified
React Native + Expo (Web Devs)
// Expo — project setup (managed workflow)
// npx create-expo-app MyApp --template
// or: npx expo start
// app/(tabs)/index.tsx — Expo Router (file-based)
import { View, Text, StyleSheet, Pressable, FlatList } from 'react-native';
import { useRouter } from 'expo-router';
import { useQuery } from '@tanstack/react-query';
interface Post {
id: number;
title: string;
}
export default function HomeScreen() {
const router = useRouter();
const { data: posts, isLoading } = useQuery<Post[]>({
queryKey: ['posts'],
queryFn: () => fetch('https://api.example.com/posts').then(r => r.json()),
});
if (isLoading) return <ActivityIndicator />;
return (
<View style={styles.container}>
<Text style={styles.title}>Latest Posts</Text>
<FlatList
data={posts}
keyExtractor={(item) => String(item.id)}
renderItem={({ item }) => (
<Pressable
style={styles.card}
onPress={() => router.push(`/post/${item.id}`)}
>
<Text>{item.title}</Text>
</Pressable>
)}
/>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 16 },
title: { fontSize: 24, fontWeight: 'bold', marginBottom: 16 },
card: {
padding: 16,
marginBottom: 8,
backgroundColor: '#f5f5f5',
borderRadius: 8,
},
});
// React Native — native modules (bridging)
// Using Expo's pre-built native modules:
import * as Camera from 'expo-camera';
import * as Location from 'expo-location';
import * as Haptics from 'expo-haptics';
import * as SecureStore from 'expo-secure-store';
import * as Notifications from 'expo-notifications';
// Camera
async function takePhoto() {
const { status } = await Camera.requestCameraPermissionsAsync();
if (status !== 'granted') return;
// ... use Camera component
}
// Location
async function getLocation() {
const { status } = await Location.requestForegroundPermissionsAsync();
const location = await Location.getCurrentPositionAsync({});
return location.coords;
}
// Haptic feedback
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
// Secure storage (Keychain/Keystore)
await SecureStore.setItemAsync('auth_token', token);
const token = await SecureStore.getItemAsync('auth_token');
// Expo EAS — build and submit to App Store/Play Store
// eas.json
{
"build": {
"preview": {
"distribution": "internal",
"android": { "buildType": "apk" }
},
"production": {
"android": { "buildType": "app-bundle" },
"ios": { "resourceClass": "m-medium" }
}
},
"submit": {
"production": {
"ios": { "appleId": "dev@example.com" },
"android": { "serviceAccountKeyPath": "./play-store-key.json" }
}
}
}
// Commands:
// eas build --platform all --profile production
// eas submit --platform all --profile production
// eas update --branch production (OTA update, no app store review)
Flutter (Native Performance)
// Flutter — Dart, custom renderer (no native views)
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
),
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Posts')),
body: FutureBuilder<List<Post>>(
future: fetchPosts(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
final post = snapshot.data![index];
return ListTile(
title: Text(post.title),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => PostScreen(post: post),
),
),
);
},
);
},
),
);
}
}
Capacitor (Web App → Native)
// Capacitor — add native to existing web app
// npm install @capacitor/core @capacitor/cli
// npx cap init MyApp com.myapp.app
// npx cap add ios
// npx cap add android
// Use Capacitor plugins alongside web APIs
import { Camera, CameraResultType } from '@capacitor/camera';
import { Geolocation } from '@capacitor/geolocation';
import { PushNotifications } from '@capacitor/push-notifications';
// Camera (falls back to input[type=file] on web)
async function takePicture() {
const image = await Camera.getPhoto({
quality: 90,
allowEditing: true,
resultType: CameraResultType.Uri,
});
return image.webPath;
}
// Geolocation
const coordinates = await Geolocation.getCurrentPosition();
console.log(coordinates.coords.latitude, coordinates.coords.longitude);
// Push notifications
await PushNotifications.register();
PushNotifications.addListener('registration', (token) => {
console.log('Push token:', token.value);
});
// Capacitor — sync and run
// After building your web app (npm run build):
// npx cap sync — copy web assets to native projects
// npx cap open ios — open in Xcode
// npx cap open android — open in Android Studio
// npx cap run ios — run on simulator
Comparison Table
| Framework | Language | Performance | Web Dev Friendly | Bundle Size |
|---|---|---|---|---|
| React Native + Expo | TypeScript | High (New Arch) | ✅ | ~25MB |
| Flutter | Dart | Highest | ⚠️ (Dart) | ~10MB |
| Capacitor | TypeScript | Medium | ✅ | ~5MB + Web |
| Ionic | TypeScript | Medium | ✅ | ~8MB |
When to Choose
| Scenario | Pick |
|---|---|
| Web developer, want mobile fast | React Native + Expo |
| Pixel-perfect UI across all platforms | Flutter |
| Already have a web app, add native | Capacitor |
| Maximum performance (gaming, intensive apps) | Flutter |
| Shared codebase with Next.js web app | React Native (Expo Router) |
| Desktop + mobile + web (single codebase) | Flutter |
| PWA that needs app store presence | Capacitor |
Compare mobile framework package health on PkgPulse.
See the live comparison
View react native vs. flutter on PkgPulse →