ComposeNativeWebView is a Compose Multiplatform WebView whose API design and mobile implementations (Android & iOS) are intentionally derived almost verbatim from KevinnZou/compose-webview-multiplatform.
This project exists first and foremost to bring that same API to Desktop, backed by native OS webviews instead of a bundled Chromium runtime.
io.github.kdroidfilter.webview.*
๐ข Reused on purpose
- API surface (
WebViewState,WebViewNavigator, settings, callbacks, mental model) - Android implementation (
android.webkit.WebView) - iOS implementation (
WKWebView) - Overall behavior and semantics
๐ If you already know compose-webview-multiplatform, you already know how to use this.
๐ What ComposeNativeWebView adds
- Desktop support with native engines
- A Rust + UniFFI (Wry) backend instead of KCEF / embedded Chromium
- A tiny desktop footprint with system-provided webviews
โ
Android: android.webkit.WebView
โ
iOS: WKWebView
โ
Desktop: Wry (Rust) via UniFFI
Desktop engines:
- Windows: WebView2
- macOS: WKWebView
- Linux: WebKitGTK
@Composable
fun App() {
val state = rememberWebViewState("https://example.com")
WebView(state, Modifier.fillMaxSize())
}Thatโs it.
dependencies {
implementation("io.github.kdroidfilter:composewebview:<version>")
}Same artifact for Android, iOS, Desktop.
Wry uses native access via JNA.
compose.desktop {
application {
jvmArgs += "--enable-native-access=ALL-UNNAMED"
}
}Run the feature showcase first:
- Desktop:
./gradlew :demo:run - Android:
./gradlew :demo-android:installDebug - iOS: open
iosApp/iosApp.xcodeprojin Xcode and Run
Responsive UI:
- large screens โ side Tools panel
- phones โ bottom sheet
loadUrl(url, headers)loadHtml(html)loadHtmlFile(fileName, readType)
navigateBack(),navigateForward()reload(),stopLoading()canGoBack,canGoForward
isLoadingloadingStatelastLoadedUrlpageTitle
Unified cookie API:
state.cookieManager.setCookie(...)
state.cookieManager.getCookies(url)
state.cookieManager.removeCookies(url)
state.cookieManager.removeAllCookies()navigator.evaluateJavaScript("document.title = 'Hello'")- injected automatically after page load
- callback-based
- works on all platforms
window.kmpJsBridge.callNative("echo", {...}, callback)Intercept navigator-initiated navigations only:
override fun onInterceptUrlRequest(
request: WebRequest,
navigator: WebViewNavigator
): WebRequestInterceptResultUseful for:
- blocking URLs
- app-driven routing
- security rules
val state = rememberWebViewState(
url = "https://example.com"
) {
customUserAgentString = "MyApp/1.0"
}Supports:
- URL
- inline HTML
- resource files
val navigator = rememberWebViewNavigator()
WebView(state, navigator)Commands:
loadUrlloadHtmlloadHtmlFileevaluateJavaScript
state.webSettings.customUserAgentString = "MyApp/1.2.3"Desktop note:
- applied at creation time
- changing it recreates the WebView (debounced)
- JS context/history may be lost
๐ Set it early.
state.webSettings.logSeverity = KLogSeverity.DebugWebView(
state,
navigator,
onCreated = { native ->
println(native.getCurrentUrl())
}
)Useful for debugging or platform-specific hooks.
wrywebview/โ Rust core + UniFFI bindingswrywebview-compose/โ Compose APIdemo-shared/โ shared demo UIdemo/,demo-android/,iosApp/โ platform launchers
- RequestInterceptor does not intercept sub-resources
- Desktop UA change recreates the WebView
- API inspiration: KevinnZou/compose-webview-multiplatform
- Wry (Tauri ecosystem)
- UniFFI (Mozilla)