ComposeMultiplatformWebview
0.1.5indexedNative WebView integration leveraging system web rendering via JNA, offering true native performance, no bundled browser engines, bidirectional JS interop, navigation controls, and state management.
Native WebView integration leveraging system web rendering via JNA, offering true native performance, no bundled browser engines, bidirectional JS interop, navigation controls, and state management.
A powerful native WebView integration for Compose Multiplatform that provides seamless web content rendering across Android, iOS, Desktop (Windows/macOS), and JVM platforms. Built with native platform APIs for superior performance, authentic user experience, and zero external dependencies.

True Native Integration – Leverages each platform's native WebView components: Android WebView, iOS WKWebView, Windows WebView2 (Chromium), and macOS WKWebView for authentic platform behavior.
Universal Cross-Platform Support – Single API that works seamlessly across Android, iOS, Windows, macOS, and Linux (community-supported).
Zero Bundled Dependencies – No embedded browsers or heavy dependencies. Uses the web rendering technology already present on each platform.
Production-Ready Performance – Battle-tested in real-world applications with native-level performance and memory efficiency.
Compose-First Design – Idiomatic Kotlin Multiplatform API built specifically for Compose developers with reactive state management.
Enterprise-Grade Security – Inherits security features and automatic updates from each platform's native WebView implementation.
Add the dependency to your build.gradle.kts:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("com.saralapps:composemultiplatformwebview:0.1.4")
}
}
}
Android:
iOS:
Windows (x64):
macOS:
Linux:
import com.saralapps.composemultiplatformwebview.PlatformWebView
@Composable
fun App() {
PlatformWebView(
url = "https://kotlinlang.org",
modifier = Modifier.fillMaxSize()
)
}
@Composable
fun WebViewWithState() {
val webViewState = rememberPlatformWebViewState(
url = "https://github.com",
javaScriptEnabled = true,
allowsFileAccess = true
)
PlatformWebView(
state = webViewState,
modifier = Modifier.fillMaxSize(),
onUrlChanged = { newUrl ->
println("Navigated to: $newUrl")
}
)
}
@Composable
fun WebViewWithFallback() {
PlatformWebView(
url = ,
modifier = Modifier.fillMaxSize(),
onUnavailable = { availability ->
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
(availability) {
WebViewAvailability.NotInstalled -> {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text()
Button(onClick = { }) {
Text()
}
}
}
WebViewAvailability.Error -> {
Text()
}
-> {
Text()
}
}
}
}
)
}
Manages the internal state and configuration of the WebView:
val webViewState = rememberPlatformWebViewState(
url = "https://example.com",
javaScriptEnabled = true,
allowsFileAccess = false,
onNavigating = { url ->
// Return true to allow, false to block navigation
url.startsWith("https://")
}
)
Parameters:
url: String? - Initial URL to load (optional)javaScriptEnabled: Boolean - Enable/disable JavaScript execution (default: true)allowsFileAccess: Boolean - Allow/deny local file access (default: true)onNavigating: ((String) -> Boolean)? - Navigation interception callbackTwo variants available for different use cases:
Best for complex scenarios requiring state management:
@Composable
fun PlatformWebView(
state: PlatformWebViewState,
modifier: Modifier = Modifier,
placeholderColor: Color = Color.White,
onUrlChanged: ((String) -> Unit)? = null,
onCreated: (() -> Unit)? = null,
onDisposed: (() -> )? = ,
onUnavailable: @ (() -> )? =
)
Perfect for straightforward WebView integration:
@Composable
sealed class WebViewAvailability {
object Available : WebViewAvailability()
object NotInstalled : WebViewAvailability()
data class Error(val message: String) : WebViewAvailability()
}
@Composable
fun SecureWebView(url: String) {
val webViewState = rememberPlatformWebViewState(
url = url,
javaScriptEnabled = false, // Disable for untrusted content
allowsFileAccess = false,
onNavigating = { navigationUrl ->
allowedDomains = listOf(, )
uri = URI(navigationUrl)
allowedDomains.any { uri.host?.endsWith(it) == }
}
)
PlatformWebView(
state = webViewState,
modifier = Modifier.fillMaxSize()
)
}
@Composable
fun WebViewWithLifecycle() {
var webViewActive by remember { mutableStateOf(false) }
pageTitle remember { mutableStateOf() }
PlatformWebView(
url = ,
modifier = Modifier.fillMaxSize(),
onCreated = {
webViewActive =
println()
},
onUrlChanged = { url ->
pageTitle = url.substringAfter().substringBefore()
},
onDisposed = {
webViewActive =
println()
}
)
}
Control JavaScript execution based on content trust level:
// Untrusted external content
PlatformWebView(
url = "https://untrusted-site.com",
javaScriptEnabled = false, // Disable JavaScript
allowsFileAccess = false,
modifier = Modifier.fillMaxSize()
)
// Trusted application content
PlatformWebView(
url = "https://your-app.com",
javaScriptEnabled = true,
modifier = Modifier.fillMaxSize()
)
// Web content (recommended: deny file access)
val webState = rememberPlatformWebViewState(
url = "https://example.com",
allowsFileAccess = false
)
// Local HTML content (required: allow file access)
val localState = rememberPlatformWebViewState(
url = "file:///android_asset/index.html",
allowsFileAccess = true
)
val secureWebViewState = rememberPlatformWebViewState(
url = "https://myapp.com",
onNavigating = { url ->
when {
// Block non-HTTPS
!url.startsWith("https://") -> false
// Block tracking and ads
url.contains("analytics") || url.contains() ->
!url.contains() && !url.contains() ->
->
}
}
)
// Android-specific WebView settings can be configured
// through the native platform implementation
// iOS WKWebView provides automatic dark mode support
// and native Safari features
// WebView2 provides full Chromium engine compatibility
// with automatic updates through Windows Update
// macOS WKWebView integrates seamlessly with system
// appearance and accessibility features
Comprehensive support for modern web technologies across all platforms:
*Requires appropriate platform permissions
Native WebView vs. Embedded Browser Solutions:
WebView not updating:
// Users may need to update Android System WebView from Play Store
// Your app should handle this gracefully
Clear WebView cache:
// Platform-specific cache clearing can be implemented
// through the native implementation
Content not loading:
Info.plistProgrammatic check:
fun isWebView2Available(): Boolean {
// Check if WebView2 runtime is installed
return true // Implementation depends on platform detection
}
Minimum version check:
fun checkMacOSVersion(): Boolean {
val osVersion = System.getProperty("os.version")
// macOS 10.15+ required
return true
}
JNA Loading Errors:
dependencies {
implementation("net.java.dev.jna:jna:5.13.0")
implementation("net.java.dev.jna:jna-platform:5.13.0")
}
Memory Leaks:
onDisposed callback for cleanup@Test
fun testWebViewStateInitialization() = runComposeUiTest {
var state: PlatformWebViewState? = null
setContent {
state = rememberPlatformWebViewState(
url = "https://example.com",
javaScriptEnabled = true
)
}
assertNotNull(state)
}
@Test
fun testNavigationBlocking() = runComposeUiTest {
var navigationBlocked = false
setContent {
val state = rememberPlatformWebViewState(
url = "https://example.com",
onNavigating = { url ->
(url.contains()) {
navigationBlocked =
} {
}
}
)
PlatformWebView(state = state)
}
assertTrue(navigationBlocked || !navigationBlocked)
}
@Test
fun testWebViewLifecycle() = runComposeUiTest {
var created = false
var disposed = false
setContent {
PlatformWebView(
url = "https://example.com",
onCreated = { created = true },
onDisposed = { disposed = true }
)
}
waitUntil(timeoutMillis = 5000) { created }
setContent { /* Remove WebView */ }
waitUntil(timeoutMillis = ) { disposed }
}
Compose Multiplatform Native WebView is developed and maintained by Saral Apps Pvt. Ltd., a Nepal-based technology company specializing in innovative software solutions and custom digital experiences.
Based in Kathmandu, Nepal, Saral Apps focuses on creating scalable, interactive solutions for businesses and educational institutions across various industries.
Core Services:
Technology Stack:
Our solutions power educational and business platforms across Nepal:
We build production-ready, open-source libraries that solve real problems for the developer community. Our tools are battle-tested in commercial applications and continuously improved based on real-world usage.
We welcome contributions from the community! Whether it's bug reports, feature requests, documentation improvements, or code contributions, your input helps make this library better for everyone.
We're actively seeking contributors to implement Linux support!
The library currently supports Android, iOS, Windows, and macOS. We'd love to extend support to Linux using native WebView solutions.
Potential Linux WebView Approaches:
What We're Looking For:
Found a bug or have a feature request? Please open an issue on GitHub:
For Linux support specifically, reach out to info@saralapps.com or start a discussion on GitHub. We're happy to provide guidance and technical support throughout development.
Get help and connect with other developers:
Copyright 2025 Saral Apps Pvt. Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance the License.
You may obtain a copy of the License at
http:
Unless applicable law agreed to writing, software
distributed under the License distributed an BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express implied.
See the License the specific language governing permissions
limitations under the License.
If you find Compose Multiplatform Native WebView useful, please:
Built with ❤️ by Saral Apps Pvt. Ltd. in Kathmandu, Nepal
Empowering developers worldwide with production-ready, native-quality tools for Kotlin Multiplatform
kotlin multiplatform, compose multiplatform, webview, android webview, ios wkwebview, windows webview2, macos wkwebview, cross-platform webview, native webview, compose desktop, kotlin native, multiplatform library, web integration, javascript bridge, kotlin compose, mobile development, desktop development, web view component, kmp library, compose ui
fun InteractiveBrowser() {
var currentUrl by remember { mutableStateOf("https://example.com") }
var isLoading by remember { mutableStateOf(false) }
Column(modifier = Modifier.fillMaxSize()) {
// Navigation bar
Row(
modifier = Modifier.fillMaxWidth().padding(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
TextField(
value = currentUrl,
onValueChange = { currentUrl = it },
modifier = Modifier.weight(1f),
placeholder = { Text("Enter URL") },
singleLine = true
)
Button(
onClick = { /* Trigger navigation */ },
enabled = !isLoading
) {
Text("Go")
}
}
// Loading indicator
if (isLoading) {
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth()
)
}
// WebView
PlatformWebView(
url = currentUrl,
modifier = Modifier.weight(1f),
javaScriptEnabled = true,
onUrlChanged = { newUrl ->
currentUrl = newUrl
isLoading = false
},
onNavigating = { url ->
isLoading = true
true // Allow navigation
}
)
}
}
| Parameter | Type | Description |
|---|
modifier | Modifier | Compose modifier for layout and styling |
placeholderColor | Color | Background color during WebView initialization |
onUrlChanged | ((String) -> Unit)? | Callback triggered when URL changes |
onNavigating | ((String) -> Boolean)? | Pre-navigation callback; return false to block |
onCreated | (() -> Unit)? | Callback when WebView is successfully created |
onDisposed | (() -> Unit)? | Callback when WebView is disposed |
onUnavailable | @Composable ((WebViewAvailability) -> Unit)? | Composable shown when WebView unavailable |
fun DynamicContentViewer() {
var selectedContent by remember {
mutableStateOf("https://kotlinlang.org")
}
val contentOptions = mapOf(
"Kotlin" to "https://kotlinlang.org",
"Compose" to "https://www.jetbrains.com/lp/compose-multiplatform/",
"GitHub" to "https://github.com"
)
Column(modifier = Modifier.fillMaxSize()) {
// Content selector
Row(
modifier = Modifier.fillMaxWidth().padding(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
contentOptions.forEach { (label, url) ->
Button(
onClick = { selectedContent = url },
colors = ButtonDefaults.buttonColors(
containerColor = if (selectedContent == url)
MaterialTheme.colorScheme.primary
else
MaterialTheme.colorScheme.secondary
)
) {
Text(label)
}
}
}
// WebView with dynamic content
PlatformWebView(
url = selectedContent,
modifier = Modifier.weight(1f),
javaScriptEnabled = true
)
}
}
fun TabbedBrowser() {
var tabs by remember {
mutableStateOf(listOf(
"https://kotlinlang.org",
"https://github.com"
))
}
var selectedTab by remember { mutableStateOf(0) }
Column(modifier = Modifier.fillMaxSize()) {
// Tab row
ScrollableTabRow(selectedTabIndex = selectedTab) {
tabs.forEachIndexed { index, url ->
Tab(
selected = selectedTab == index,
onClick = { selectedTab = index },
text = {
Text(
url.substringAfter("://")
.substringBefore("/")
.take(20)
)
}
)
}
}
// Active tab content
PlatformWebView(
url = tabs[selectedTab],
modifier = Modifier.weight(1f),
javaScriptEnabled = true,
onUrlChanged = { newUrl ->
tabs = tabs.toMutableList().apply {
set(selectedTab, newUrl)
}
}
)
}
}
| Feature | Android | iOS | Windows | macOS |
|---|
| HTML5 | ✅ | ✅ | ✅ | ✅ |
| CSS3 | ✅ | ✅ | ✅ | ✅ |
| ES6+ JavaScript | ✅ | ✅ | ✅ | ✅ |
| WebGL | ✅ | ✅ | ✅ | ✅ |
| WebAssembly | ✅ | ✅ | ✅ | ✅ |
| WebSockets | ✅ | ✅ | ✅ | ✅ |
| Service Workers | ✅ | ✅ | ✅ | ✅ |
| Local Storage | ✅ | ✅ | ✅ | ✅ |
| IndexedDB | ✅ | ✅ | ✅ | ✅ |
| WebRTC | ✅ | ✅ | ✅ | ✅ |
| Canvas API | ✅ | ✅ | ✅ | ✅ |
| Web Audio API | ✅ | ✅ | ✅ | ✅ |
| Geolocation* | ✅ | ✅ | ✅ | ✅ |
| Media Capture | ✅ | ✅ | ✅ | ✅ |
| Metric | Native WebView | Embedded Chromium |
|---|
| App Size Increase | ~2MB | ~100-150MB |
| Memory Footprint | Low (Shared) | High (Isolated) |
| Startup Time | Fast | Slow |
| System Integration | Native | Sandboxed |
| Security Updates | Automatic (OS) | Manual (Developer) |
| Platform Consistency | Native UX | Consistent but foreign |
| Battery Impact | Optimized | Higher |
Fork the repository
git clone https://github.com/saral-apps/composemultiplatformwebview
cd composemultiplatformwebview
Create a feature branch
git checkout -b feature/your-feature-name
Make your changes
Test thoroughly
Submit a Pull Request
Surfaced from shared tags and platforms — no rankings paid for.