Skip to content

About

No description, website, or topics provided.

Resources

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Latest commit

 

History

43 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Capacitor Native UI

Render supported Web controls with UIKit and SwiftUI on iOS while keeping your application state, routing, and handlers in the WebView. Use existing Ionic markup or explicit HTML buttons; custom SwiftUI or Android Compose content can be added through experimental app-local extensions.

Native UI is opt-in and officially supported starting with 2.0.0. Standard controls on Android, browsers, older iOS, and unsupported controls retain Web presentation. Android supports experimental app-local Compose extensions. Keep your application's styles and layout; the iOS theme is optional.

How projection works

A projection is another presentation of a Web control. Your application keeps the original DOM element, state and event handlers. The plugin reads supported labels, icons, selected/disabled state and geometry, then associates the rendered control with that source element. It does not convert the page into a native screen or run a second copy of your application.

Web source Projected presentation
Supported Ionic buttons, tabs and segments Native UIKit/SwiftUI controls on supported iOS
Explicitly marked HTML buttons and back buttons Native buttons on supported iOS
Eligible controls placed in Vertical Bars SwiftUI controls in the available system rail; Web rail presentation otherwise
An app-local extension's DOM element The SwiftUI or Compose content registered by your app, using props read from that element

The original element stays in the DOM while projected. Its bindings and handlers remain attached. Once native presentation is accepted, the runtime hides the source visually, prevents direct pointer interaction and hides it from Web accessibility so there are not two interactive copies. Element projection retains the source's layout box; rail spacing is supplied by your app or theme. A Web rail similarly uses its adapter's Web presentation: Ionic layout/clones or HTML proxy buttons in your webHost.

For example, tapping a projected Save button activates the original Web button. Its existing handler performs the save and updates application state; supported DOM changes then update the projection. Tabs and other controls likewise route actions through their source adapters. Continue updating the source, rather than managing separate native state. For reader values that change without a DOM mutation, use markForCheck().

Projection changes presentation; it does not translate arbitrary HTML or CSS into native views. Native controls use supported data and styling; a custom Web appearance is not guaranteed to be reproduced. Removing or disabling a source prevents further valid actions. If projection is unavailable or stopped, the runtime releases its hiding state and the source returns to Web presentation, or to an available Web rail. Your application's own hidden/disabled state still applies.

Native ownership follows the source and its projection context, independently of animation and image availability. Modal presentation, dragging, resizing, and image restoration do not transfer an accepted source to Web while its projection remains eligible. An explicit Web handover, unsupported or rejected projection, runtime failure, or the end of the source/context releases it. A disabled button remains the same Native-owned control with disabled input.

Blocking Web overlays can keep prepared images of native buttons, segments, FABs and tab bars beneath their backdrop. Images retain the captured native appearance and are prepared after native updates and coalesced visual changes. Platform capture can differ from live compositing, including Liquid Glass edge highlights; pixel-identical material reproduction is not guaranteed. Preparation pauses during touch; presentation uses the last completed image without waiting for capture or a bridge acknowledgement. A missing image does not release an accepted Native source to Web. Native popovers that retain their controls keep the native route. See the handoff rules for movement, restoration and exclusions. Rebuild the native app to enable this capability; older native binaries keep their existing route.

Choose what to build

Goal Start here
Render existing Ionic controls natively Ionic setup
Use native buttons without Ionic HTML setup
Add a side rail to Ionic while keeping your theme Vertical Bars step by step
Add a side rail without Ionic HTML rail setup
Add native controls to an existing iOS 27 theme app Add Native UI to your theme
Read Android Material You colors Android dynamic colors
Render custom SwiftUI or Android Compose content App-local extensions (experimental)

Install from npm

We use Capacitor Foldable, an excellent community plugin by Erkam Yaman, as our standard device-layout integration. It exposes fold state, hinge geometry, bar placement and change events to your application, giving you control over how the layout responds. Foldable handles device reporting; the theme provides Web layout helpers and Native UI projects controls. Install it alongside the package you use, then connect its events in your app.

Install Native UI and Foldable together in your Capacitor application:

npm install @rdlabo/capacitor-native-ui @erkamyaman/capacitor-foldable

If you also use the iOS 27 theme, install or update both packages together:

npm install @rdlabo/capacitor-native-ui @rdlabo/ionic-theme-ios27 @erkamyaman/capacitor-foldable

Native UI, the theme, Engine, and Foldable are versioned independently. When using the theme, choose a Native UI version compatible with the theme’s declared peer dependency. Native UI includes the Engine npm dependency and links its native libraries through SwiftPM and Maven. CocoaPods consumers also need the local Engine pod setup. Declare Native UI as a direct dependency so Capacitor can discover the plugin. The setup commands install Foldable as a separate application dependency. Installing these packages does not activate native controls; application startup chooses projection and connects device layout.

Continue with Ionic setup, HTML setup, or extensions. After configuring startup, build your application's Web assets, run npx cap sync and rebuild the native app. No repository checkout or archive download is needed.

Upgrading from the native APIs bundled in theme 1.2.0 or 1.2.1? Follow the migration guide for import, configuration and native project changes.

Requirements

Native startup requires Capacitor 8 and an iOS or Android project. Android extensions require API 24+ and Kotlin/Compose. Ordinary native rendering requires iOS 26+ and Xcode 26+. The Ionic adapter declares Ionic >=8.8.1 <10, with automated E2E validation on Ionic 9; the /core HTML adapter requires no Ionic or theme package. Vertical Bars and extension placement have additional device requirements.

For standard controls, follow the setup guide for your markup; the Ionic and HTML adapters use different imports. For custom native content, start directly with the extensions guide.

Reference

Development

npm ci
npm run build
npm test
npm run test:packages

Shared DOM and renderer implementation is maintained in the local packages/native-ui-core workspace. This repository owns Capacitor registration and the host adapters. android/src/main/java/shared-core is generated from the local Core sources by npm run sync:core; edit Core, not this copy. SwiftPM and CocoaPods compile the shared Swift sources directly from packages/native-ui-core/ios/Sources. Git-based SwiftPM consumers do not need Node.js. Existing native module and Kotlin symbol names are preserved for app-local extensions.

The repository root remains the npm plugin: src, ios, android, and package.json live together. Its TypeScript unit tests live in tests; Swift and Android tests live with the plugin. examples/native-ui-html demonstrates use without the Ionic theme.

The build follows the Capacitor plugin layout: src/index.ts is compiled by TypeScript to dist/esm, and Rollup generates the browser bundle (dist/plugin.js) and CommonJS bundle (dist/plugin.cjs). The .cjs extension is required because this package uses type: module. Public ESM subpaths share the same modules in dist/esm. Run npm run watch to rebuild TypeScript changes or npm run verify:web for the web checks.

Test with the Ionic theme

The full Ionic demo, screenshot tests, theme transition tests, and simulator integration fixtures remain in ionic-theme-ios27. They install this plugin as an npm dependency. Do not duplicate the demo here.

To test unpublished changes, build and pack this repository:

npm run build
npm pack --ignore-scripts --pack-destination /tmp

From a migrated theme checkout, run npm run test:native-candidate -- /tmp/rdlabo-capacitor-native-ui-VERSION.tgz --e2e. The command creates an isolated copy, installs the archive for both the theme and demo, then runs package checks, integration unit tests, the production build, and browser tests. Add --simulator=UDID to run the existing simulator suites. The original checkout and its lockfiles are preserved; the temporary copy remains available for diagnostics.

Releases

The plugin has an independent version from the theme. Run npm run release from an up-to-date main when authorized to publish. The vVERSION tag workflow publishes only this plugin (next for prereleases, latest for stable versions).

Pin Engine to the same exact version in both npm manifests, Package.swift, the podspec and Android Gradle dependency, and update the lockfile together. A floating npm range can resolve a newer Engine than the native dependencies and break installation.

Before the first release, configure the npm trusted publisher for webview-native-ui/capacitor-plugin, workflow release.yml, environment npm. Configure that GitHub environment's reviewer policy as appropriate. Already-published versions cannot be republished; choose a new version for the first release from this repository.

This repository was extracted from webview-native-ui/ionic-theme-ios27 commit 1831bde5. Earlier history remains in that repository.

Developing shared Core

Core lives in packages/native-ui-core in this repository. Run npm ci, npm run build, and npm test from the repository root; no separate Core publication or checkout is required. The build embeds Core JavaScript and declarations into the plugin archive. Core is a private workspace, not a separately released npm dependency.

Edit shared TypeScript, Swift and Kotlin in packages/native-ui-core. npm run sync:core regenerates the Android copy; npm run check:core detects drift. Shared renderer/DOM tests run alongside Capacitor adapter tests through the root test command. Native Core tests remain under its ios and android directories.

Capacitor migrations run from this repository root. package.json, Package.swift, the podspec and android/ retain their standard locations. Validate native builds after SDK migrations; retaining the layout does not guarantee compatibility with future migration tools.

Swift Package consumers

Add https://github.com/webview-native-ui/capacitor-plugin.git in Xcode and select the NativeUICore product to use import NativeUICore without linking the Capacitor adapter. Before this change is released, select the refactor/extract-shared-core branch. Both products share the repository release tags; SwiftPM can still fetch Capacitor while resolving the package dependencies.

Capacitor apps continue to use the RdlaboCapacitorNativeUi product and import NativeUIPlugin. That module re-exports Core, so existing NativeUI.register calls keep using the same registration state as the adapter. The root manifest owns both targets; there is no separate nested Swift package.

CocoaPods continues to compile the adapter and shared sources into the existing plugin pod. This change does not publish a standalone Core pod or implement a Cordova adapter.

Android dynamic colors

Read Android 12+ Material You colors with getDynamicColorScheme() and observe resume/configuration notifications with addDynamicColorListener(). Import both from @rdlabo/capacitor-native-ui. Dynamic colors are a supported, opt-in API, independent of Native UI projection and experimental app-local extensions.

The plugin returns light and dark schemes without changing the Activity theme or requesting wallpaper permissions. Other platforms report available: false. The plugin does not apply CSS; Ionic applications can use enableDynamicColors from @rdlabo/ionic-theme-md3/dynamic-colors.

See Android dynamic colors for installation, API contracts, change notifications, cleanup, and Ionic integration.

About

No description, website, or topics provided.

Resources

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages