Skip to content
rdlabo.devdocs

Configuration

This is the settings reference. Start with how projection works, then Ionic setup or HTML setup for installation.

Call enableNativeUI() once after your application's DOM is mounted. Ionic and explicitly marked HTML controls share the standard Swift renderers. The theme is optional; Web layout, safe areas and spacing belong to your app or theme.

import { enableNativeUI } from '@rdlabo/capacitor-native-ui';

const shell = await enableNativeUI();

Without Ionic, import the same API from @rdlabo/capacitor-native-ui/core. That entry does not load Ionic's optional popover integration. See plain HTML.

Platform behavior

Feature Supported iOS Android Browser / older iOS
Automatic Ionic controls (ionic.auto) Native Original Web controls Original Web controls
Explicit element buttons Native Original Web controls Original Web controls
Element extensions SwiftUI Kotlin / Compose Original Web content
Vertical Bars Native when available; otherwise Web rail Web rail Web rail
tab-accessory extensions Supported iOS tab host only Original Web content Original Web content

Web rails require the Ionic rail layout or an application-owned HTML webHost. Settings never create a device capability.

For HTML-derived light/dark behavior, see Light and dark appearance. Appearance is configured in Web CSS, not through a startup option.

All settings

controls chooses selection and grouping defaults. verticalBars configures the rail. ionic configures Ionic discovery and appearance. Omitted fields default to true in controls, including partial objects.

const shell = await enableNativeUI({
  root: document.body,
  controls: {
    buttonGroup: true,
    button: true,
    backButton: true,
    tabs: true,
    segment: true,
    fab: true,
  },
  verticalBars: {
    enabled: true,
    // Optional: placement: { edge: 'trailing', webHost: appOwnedHost },
  },
  extensions: [],
  ionic: {
    auto: true,
    verticalBars: {
      buttonProjection: 'system',
      buttonDefaultFill: 'glass',
      popover: false,
    },
  },
  debug: false,
});

Selection defaults

Every control accepts ControlProjection. controls itself accepts the same values as a shorthand for all six fields.

Value Ordinary placement Vertical rail placement
true (default) Selected Selected
false Not selected Not selected
'horizontalOnly' Selected Not selected
'verticalOnly' Not selected Selected

Horizontal and vertical describe control placement, not screen orientation or device model. These defaults do not move a control, provide a missing device capability, or make unsupported markup eligible.

Control Responsibility
buttonGroup Group already selected actions when the group qualifies. Never selects children.
button Select ordinary buttons and Ionic menu buttons.
backButton Select back navigation, independently of its parent group.
tabs Select tabs and their registered search presentation.
segment Select supported segments.
fab Select supported floating action buttons.

Local data-horizontal-bars="true|false" and data-vertical-bars="true|false" override the matching selection default. On ion-buttons they control grouping only. Explicit data-native-ui sources are independent of these defaults. Structural exclusions and rail availability still apply. See toolbar selection and grouping.

Other settings

Setting Default Meaning
root document.body Connected source subtree. Controls outside it are not projected.
verticalBars.enabled true Allow rail projection when available.
verticalBars.placement omitted Optional { edge: 'leading' | 'trailing', webHost?: HTMLElement }. Edge is a preference, not a capability declaration.
extensions [] Experimental. App-specific SwiftUI / Compose extensions. Independent of controls and ionic.auto; the app decides when its reader returns content.
ionic.auto true Enable automatic Ionic discovery. false disables it, including local layout opt-ins. Explicit data-native-ui sources and extensions remain independent.
ionic.verticalBars.buttonProjection 'system' 'system' uses native styling; 'source' projects supported Ionic styling. Local appearance overrides take precedence.
ionic.verticalBars.buttonDefaultFill 'glass' 'glass' or 'solid' for source-style standalone Ionic buttons without explicit fill. Explicit fills and the clear default inside groups take precedence.
ionic.verticalBars.popover false Opt into Ionic popovers anchored to native rail buttons. Requires the Ionic entry and its optional peers.
debug false Log source elements and fallback reasons to the developer console.

HTML fallback rail host

webHost is an optional, application-styled HTML fallback rail. Its requirements are:

  • Connected to the same document as root.
  • Outside root; neither element may contain the other.
  • Styled and positioned by the application, including any reserved content spacing.

Use a limited root, such as your <main>, with a sibling host; the default document.body cannot have such a host. Without a host, unavailable HTML rails leave original controls visible. Ionic's existing rail layout uses its own fallback adapter.

HTML currently supports buttons and back buttons. Tab, segment and FAB readers remain Ionic adapters; enabling those settings does not infer equivalent structures from arbitrary HTML.

Common configurations

// Ionic or HTML: all supported controls, rail support enabled by default.
const shell = await enableNativeUI();

// Alternative: automatic selection only in the rail.
const railOnly = await enableNativeUI({ controls: 'verticalOnly' });

// Alternative: automatically select only tabs.
const tabsOnly = await enableNativeUI({
  controls: { buttonGroup: false, button: false, backButton: false, tabs: true, segment: false, fab: false },
});

These are alternatives: start only one shell. Repeating equivalent startup settings returns its handle; different settings require destroying the previous shell.

controls: false disables all default selection and grouping, while local attributes can still opt in. ionic: { auto: false } instead disables automatic Ionic discovery altogether. Neither disables explicit HTML sources or extensions.

verticalBars.enabled: false leaves rail controls on the Web regardless of selection. It does not affect ordinary controls. data-native-ui-placement="element" preserves an explicit control's original position, even with controls: 'verticalOnly'. For Ionic groups, put placement on the whole ion-buttons group so its shared native surface has one placement.

Select controls explicitly in a customized Ionic app

Disable automatic Ionic projection while retaining the native rail and your custom extensions:

const shell = await enableNativeUI({
  root: document.querySelector<HTMLElement>('#content')!,
  ionic: { auto: false },
  verticalBars: {
    enabled: true,
    placement: {
      edge: 'trailing',
      webHost: document.querySelector<HTMLElement>('#rail')!,
    },
  },
  extensions: [myExtension],
});
<main id="content">
  <ion-button>Keep this on the Web</ion-button>
  <ion-button data-native-ui="button" data-native-ui-placement="rail">Project this action</ion-button>
  <button data-native-ui="button">Project at its original position</button>
</main>
<aside id="rail" aria-label="Actions"></aside>

Keep your original handlers on each source. Explicit ion-button uses the standard button reader, so supported content and styling rules still apply; the attribute does not make arbitrary custom markup supported. data-native-ui-placement="rail" requests the system rail when available. Style the optional webHost and reserve space in your app; without it, unavailable explicit rails keep their source controls visible. Unmarked Ionic controls remain Web-rendered. An automatically projected group containing an explicitly marked source stays on the Web to avoid covering that source twice.

Explicit built-in sources support individual button, ion-button, and [role="button"] elements with data-native-ui="button" or "back-button". This does not opt existing ion-back-button, ion-menu-button, button groups, tabs, segments, or FABs into their Ionic-specific readers. Keep automatic projection enabled for the corresponding kind, use a standard explicit button with your own click handler, or register an extension for custom UI.

verticalBars.enabled still governs rail availability. To leave a specific source on the Web, remove its explicit marker or use data-native-ui="disabled".

Change rail use after startup

await shell.disableVerticalControlArea();
await shell.enableVerticalControlArea();

These methods change verticalBars.enabled on this shell and preserve appearance, controls and placement. They retire existing projection before transferring ownership, without creating another shell. Calls are serialized. Calling either method after destroy() rejects.

const lease = await shell.suspend(); // temporarily restore all Web controls
await lease.resume();
await shell.destroy(); // stop the shell and restore sources

Disabling rail projection does not remove application CSS or change application-owned spacing. Update your own layout alongside this setting if needed.

Request a check after an unobserved change

shell.markForCheck();

Use this after changing a property or reader state that does not cause a DOM mutation. It schedules a check, batches repeated requests, and sends only changed native snapshots. It returns void; it does not wait for native completion or render your framework's DOM. Calls after destruction are ignored. See source checks and observation.

Understand why a source stays on the Web

Temporarily add debug: true to your existing startup options, then rebuild the Web app. Do not start a second shell to enable diagnostics:

const shell = await enableNativeUI({ debug: true, extensions: [myExtension] });
console.info(shell.getStatus());

Open the WebView developer console and filter for [Native UI]. Diagnostics include the DOM source when available. Each reason is logged once per source during that activation, so scrolling or repeated markForCheck() calls do not flood the console. Props, action payloads and application exception messages are not logged.

Diagnostic What to check
web-fallback Platform support, native plugin registration, sync and native rebuild.
unregistered-extension Native registration name matches the DOM definition and runs before startup. Newly inserted sources are checked too.
reader-error / invalid-props Reader implementation, JSON-only values and the 64K size limit.
unsupported-placement / missing-tab-owner Platform and owning native tab host for a tab accessory.
source-hidden / source-clipped / source-focused Visibility, clipping/transformed ancestors, or a focused Web descendant.
reader-opt-out Your reader returned null.
unsupported-control Supported markup, accessible name, fixed placement and styling.
native-rejected Native props decoding, placement or device support. The bridge reports rejection, not the native exception.

getStatus() summarizes the shell; it is not a per-element diagnostic report. Normal navigation, suspension and unsupported content can intentionally retain Web presentation. Turn debug off after investigating; it does not change projection eligibility.

Existing applications

The standalone enableVerticalControlArea() defaults selection to 'verticalOnly'. Its setPlacement() helper is a deprecated Web CSS adapter; use the theme's setVerticalBarsLayout({ edge, inset }) for Web layout. Native availability comes from the plugin's device metrics.

See upgrading from 1.2.1 for package separation, removed options and changed partial-control defaults.