diff --git a/demo/src/app/docs/docs-content.generated.ts b/demo/src/app/docs/docs-content.generated.ts
index 59a6003c..40800cd1 100644
--- a/demo/src/app/docs/docs-content.generated.ts
+++ b/demo/src/app/docs/docs-content.generated.ts
@@ -1,3 +1,3 @@
// Generated from packages/theme-ios27/docs/special-markup.md. Do not edit directly.
export const docsContentHtml =
- '
Special markup and classes \nMost Ionic markup works without changes. The combinations below are explicit opt-ins provided by the theme.
\nPrimary submit buttons \nSolid submit buttons use the Ionic color's contrast value for their foreground. Their directional edge treatment follows the iOS 27 prominent-button appearance and does not require an additional brightness color.
\n< ion-button type = "submit" color = "primary" >Submit</ ion-button > \n< ion-button class = "button-submit" fill = "solid" color = "primary" >Continue</ ion-button > \n\n\nPreview \nSubmit \nContinue \n \n\nUse .button-submit when the button needs the same treatment but cannot use type="submit".
\nPreferred overlay actions \nFor iOS alerts and action sheets, set role: 'preferred' on a button to give it a filled --ion-color-primary background and --ion-color-primary-contrast text and icons. While pressed, the background uses --ion-color-primary-shade. This is a theme convention using Ionic's custom button roles; it does not automatically select or invoke the action. Dismissal reports the role as preferred.
\nbuttons: [ \n { text: \'Cancel\', role: \'cancel\' }, \n { text: \'Continue\', role: \'preferred\' }, \n]; \n\nButtons with no role or default keep the normal text color. cancel retains Ionic's cancellation behavior, selected remains a selection state, and destructive uses --ios-theme-destructive-color. An existing confirm role is not treated as preferred. Use preferred for the recommended action, not simply any action that confirms a choice.
\nFloating iPad sheets \nSet expandToScroll: false on a sheet modal to use floating lower corners and a 20px bottom gap on iPad. Ionic then sizes the visible page at each breakpoint, so the theme can style it with CSS alone. Content scrolls within the current breakpoint; dragging the handle still resizes the sheet. With the default expandToScroll: true, the sheet keeps Ionic's bottom-attached layout and scroll-to-expand behavior.
\nTab bar position \nAdd one of tab-bar-position-start, tab-bar-position-center, or tab-bar-position-end to an iOS ion-tab-bar to position the whole bar within its safe area. These classes work with both slot="top" and slot="bottom" and preserve the bar's width and press animation. Start and end follow the text direction (reversed in RTL). Without a class, the existing placement is unchanged.
\n< ion-tab-bar slot = "bottom" class = "tab-bar-position-center" > \n < ion-tab-button tab = "home" >Home</ ion-tab-button > \n < ion-tab-button tab = "settings" >Settings</ ion-tab-button > \n</ ion-tab-bar > \n\nThese classes do not reposition a separate ion-fab; leave room for it when choosing the bar's position.
\nSupport iPhone Duo (preview) \niPhone Duo support, including standalone vertical bars, is available as a preview alongside Native UI. Its APIs and supported behavior may change.
\niPhone Duo support — the vertical system rail, hinge posture, and the posture-driven split-pane layout — is independent of the iOS 27 theme and the full Native UI. See iPhone Duo support for the complete setup, including device-layout monitoring without a projection runtime.
\nSee Vertical Bars for rail layout classes, control eligibility and native button appearance.
\nFor standalone setup while keeping your existing theme, see iPhone Duo with your existing theme .
\nTwo-line inset list items \nPlace an unslotted ion-label immediately alongside an unslotted ion-note to render a two-line item. When using the iOS-style inset-list background, wrap the items in ion-item-group; keep ion-list-header outside the group.
\n< ion-list inset = "true" > \n < ion-list-header > \n < ion-label >Connections</ ion-label > \n </ ion-list-header > \n < ion-item-group > \n < ion-item > \n < ion-label >Network & internet</ ion-label > \n < ion-note >Mobile, Wi-Fi, hotspot</ ion-note > \n </ ion-item > \n </ ion-item-group > \n</ ion-list > \n\n\nPreview \n\n \n Connections \n \n \n \n Network & internet \n Mobile, Wi-Fi, hotspot \n \n \n \n \n\nUse slot="end" on ion-note when you want the standard trailing-note layout instead.
\nInset-list section headers \nAdd .item-group-header to an ion-item-group to create the centered icon, title, and description used at the top of the component demo pages.
\nThis is an introductory group. Place regular list items in a separate ion-item-group that follows it.
\n< ion-list inset = "true" > \n < ion-item-group class = "item-group-header" > \n < ion-item > \n < ion-label > \n < ion-icon name = "list" style = "background: var(--ion-color-primary)" ></ ion-icon > \n < h2 >Lists</ h2 > \n < ion-text >Inset-list examples</ ion-text > \n </ ion-label > \n </ ion-item > \n </ ion-item-group > \n < ion-item-group > \n < ion-item >< ion-label >First item</ ion-label ></ ion-item > \n </ ion-item-group > \n</ ion-list > \n\n\nPreview \n\n \n \n First item \n \n \n \n\nFull-width segments \nAdd .segment-style-glass to give a segment the same glass surface and selected indicator treatment as the tab bar. The class preserves the segment's existing dimensions and text colors, supports scrollable segments, and respects Ionic's public --background property.
\n< ion-segment class = "segment-style-glass" value = "available" > \n < ion-segment-button value = "available" >Available</ ion-segment-button > \n < ion-segment-button value = "away" >Away</ ion-segment-button > \n</ ion-segment > \n\nFor colored segments, use Ionic's color property (for example, color="primary" or color="secondary"). Ionic uses the palette's base color for the softly tinted track while keeping the selected surface and labels neutral. A surrounding colored toolbar only supplies colors when the segment has no color of its own. The optional moving glass inherits the selected surface color, and custom Ionic palettes work without additional registration.
\nAdd .segment-expand when segment buttons should divide the available width evenly. The class also changes the Liquid Glass effect sizing when registerSegmentEffect is used.
\nSegments use a 32px minimum height in content and a 48px minimum height inside ion-toolbar. .segment-expand keeps the compact 32px layout in a toolbar. Compact segments retain Ionic's flat background and indicator colors; only the regular toolbar variant has a glass container and scales its outer container while pressed. Content and expanded segments keep their outer bounds. The optional moving glass lens is independent of the container's background.
\n< ion-segment class = "segment-expand" value = "new" > \n < ion-segment-button value = "new" >< ion-label >New</ ion-label ></ ion-segment-button > \n < ion-segment-button value = "replied" >< ion-label >Replied</ ion-label ></ ion-segment-button > \n</ ion-segment > \n\n\nPreview \n\n New \n Replied \n \n \n\nClassic search bar in a condense header \nThe theme gives iOS search bars the iOS 27 appearance by default. Add .searchbar-classic to the search field shown beneath a large title in an ion-header with collapse="condense". It uses the conventional filled iOS appearance and collapses with the large title instead of remaining in the fixed header.
\nPlace it in a toolbar with a color, such as color="light"; the classic background is derived from that color's contrast value.
\nThe example uses Ionic's standard collapsible large-title structure. Scroll the preview to collapse the large title and reveal the fixed header.
\n< div class = "ion-page" > \n < ion-header translucent = "true" > \n < ion-toolbar color = "light" > \n < ion-title >Search</ ion-title > \n </ ion-toolbar > \n </ ion-header > \n < ion-content color = "light" fullscreen = "true" > \n < ion-header collapse = "condense" > \n < ion-toolbar color = "light" > \n < ion-title size = "large" >Search</ ion-title > \n </ ion-toolbar > \n < ion-toolbar color = "light" > \n < ion-searchbar class = "searchbar-classic" placeholder = "Filter results" ></ ion-searchbar > \n </ ion-toolbar > \n </ ion-header > \n < ion-list inset = "true" > \n < ion-item-group > \n < ion-item >< ion-label >Recent item 1</ ion-label ></ ion-item > \n < ion-item >< ion-label >Recent item 2</ ion-label ></ ion-item > \n < ion-item >< ion-label >Recent item 3</ ion-label ></ ion-item > \n < ion-item >< ion-label >Recent item 4</ ion-label ></ ion-item > \n < ion-item >< ion-label >Recent item 5</ ion-label ></ ion-item > \n < ion-item >< ion-label >Recent item 6</ ion-label ></ ion-item > \n < ion-item >< ion-label >Recent item 7</ ion-label ></ ion-item > \n < ion-item >< ion-label >Recent item 8</ ion-label ></ ion-item > \n < ion-item >< ion-label >Recent item 9</ ion-label ></ ion-item > \n < ion-item >< ion-label >Recent item 10</ ion-label ></ ion-item > \n </ ion-item-group > \n </ ion-list > \n </ ion-content > \n</ div > \n\n\nPreview \n\n \n \n Search \n \n \n \n \n \n Search \n \n \n \n \n \n \n \n Recent item 1 \n Recent item 2 \n Recent item 3 \n Recent item 4 \n Recent item 5 \n Recent item 6 \n Recent item 7 \n Recent item 8 \n Recent item 9 \n Recent item 10 \n \n \n \n
\n \n\nThe .ion-page wrapper makes this embedded preview behave like a complete routed page. An application using ion-router-outlet normally receives that page container automatically. The inset list and its items only provide enough content to demonstrate scrolling; they are not required by .searchbar-classic.
\nOpting out \nAdd .ios-theme-disabled to an individual Ionic component when it must retain Ionic's standard iOS styling.
\n.ios26-disabled is deprecated but remains supported as an alias with the same behavior. Use .ios-theme-disabled for new code.
\n< ion-button >iOS 27 theme</ ion-button > < ion-button class = "ios-theme-disabled" >Standard Ionic button</ ion-button > \n\n\nPreview \niOS 27 theme Standard Ionic button \n \n\nFor the background model behind inset lists, see Using ion-item-group .
\n';
+ 'Special markup and classes \nMost Ionic markup works without changes. The combinations below are explicit opt-ins provided by the theme.
\nPrimary submit buttons \nSolid submit buttons use the Ionic color's contrast value for their foreground. Their directional edge treatment follows the iOS 27 prominent-button appearance and does not require an additional brightness color.
\n< ion-button type = "submit" color = "primary" >Submit</ ion-button > \n< ion-button class = "button-submit" fill = "solid" color = "primary" >Continue</ ion-button > \n\n\nPreview \nSubmit \nContinue \n \n\nUse .button-submit when the button needs the same treatment but cannot use type="submit".
\nPreferred overlay actions \nFor iOS alerts and action sheets, set role: 'preferred' on a button to give it a filled --ion-color-primary background and --ion-color-primary-contrast text and icons. While pressed, the background uses --ion-color-primary-shade. This is a theme convention using Ionic's custom button roles; it does not automatically select or invoke the action. Dismissal reports the role as preferred.
\nbuttons: [ \n { text: \'Cancel\', role: \'cancel\' }, \n { text: \'Continue\', role: \'preferred\' }, \n]; \n\nChoose the role by action semantics:
\n\n\n\nRole \nAppearance and behavior \n \n \n\npreferred \nFilled primary action; dismissal reports preferred. Does not automatically select or invoke it. \n \n\nNo role or default \nNormal text color. \n \n\ncancel \nRetains Ionic's cancellation behavior. \n \n\nselected \nRetains selection state. \n \n\ndestructive \nUses --ios-theme-destructive-color. \n \n\nconfirm \nNot treated as preferred. Use preferred for a recommended action, not simply any confirmation. \n \n
\nFloating iPad sheets \nChoose the sheet's scrolling behavior with expandToScroll:
\n\n\n\nSetting \niPad appearance \nScrolling and resizing \n \n \n\nfalse \nFloating lower corners and a 20px bottom gap. \nContent scrolls within the current breakpoint; dragging the handle resizes the sheet. Ionic sizes the visible page at each breakpoint so CSS can style it. \n \n\ntrue (default) \nIonic's bottom-attached layout. \nScroll-to-expand behavior. \n \n
\nTab bar position \nChoose a class on an iOS ion-tab-bar to position the whole bar within its safe area:
\n\n\n\nClass \nPlacement \n \n \n\ntab-bar-position-start \nStart of the reading direction. \n \n\ntab-bar-position-center \nCenter. \n \n\ntab-bar-position-end \nEnd of the reading direction. \n \n\nNone \nExisting placement unchanged. \n \n
\nAll three classes support slot="top" and slot="bottom", preserve width and press animation, and resolve start/end in RTL.
\n< ion-tab-bar slot = "bottom" class = "tab-bar-position-center" > \n < ion-tab-button tab = "home" >Home</ ion-tab-button > \n < ion-tab-button tab = "settings" >Settings</ ion-tab-button > \n</ ion-tab-bar > \n\nThese classes do not reposition a separate ion-fab; leave room for it when choosing the bar's position.
\nSupport iPhone Duo \niPhone Duo support, including standalone Vertical Bars, is stable starting with version 2.0.0.
\niPhone Duo support — the vertical system rail, hinge posture, and the posture-driven split-pane layout — is independent of the iOS 27 theme and the full Native UI. See iPhone Duo support for the complete setup, including device-layout monitoring without a projection runtime.
\nSee Vertical Bars for rail layout classes, control eligibility and native button appearance.
\nFor standalone setup while keeping your existing theme, see iPhone Duo with your existing theme .
\nTwo-line inset list items \nPlace an unslotted ion-label immediately alongside an unslotted ion-note to render a two-line item. When using the iOS-style inset-list background, wrap the items in ion-item-group; keep ion-list-header outside the group.
\n< ion-list inset = "true" > \n < ion-list-header > \n < ion-label >Connections</ ion-label > \n </ ion-list-header > \n < ion-item-group > \n < ion-item > \n < ion-label >Network & internet</ ion-label > \n < ion-note >Mobile, Wi-Fi, hotspot</ ion-note > \n </ ion-item > \n </ ion-item-group > \n</ ion-list > \n\n\nPreview \n\n \n Connections \n \n \n \n Network & internet \n Mobile, Wi-Fi, hotspot \n \n \n \n \n\nUse slot="end" on ion-note when you want the standard trailing-note layout instead.
\nInset-list section headers \nAdd .item-group-header to an ion-item-group to create the centered icon, title, and description used at the top of the component demo pages.
\nThis is an introductory group. Place regular list items in a separate ion-item-group that follows it.
\n< ion-list inset = "true" > \n < ion-item-group class = "item-group-header" > \n < ion-item > \n < ion-label > \n < ion-icon name = "list" style = "background: var(--ion-color-primary)" ></ ion-icon > \n < h2 >Lists</ h2 > \n < ion-text >Inset-list examples</ ion-text > \n </ ion-label > \n </ ion-item > \n </ ion-item-group > \n < ion-item-group > \n < ion-item >< ion-label >First item</ ion-label ></ ion-item > \n </ ion-item-group > \n</ ion-list > \n\n\nPreview \n\n \n \n First item \n \n \n \n\nFull-width segments \nAdd .segment-style-glass to give a segment the same glass surface and selected indicator treatment as the tab bar. The class preserves the segment's existing dimensions and text colors, supports scrollable segments, and respects Ionic's public --background property.
\n< ion-segment class = "segment-style-glass" value = "available" > \n < ion-segment-button value = "available" >Available</ ion-segment-button > \n < ion-segment-button value = "away" >Away</ ion-segment-button > \n</ ion-segment > \n\nFor colored segments, use Ionic's color property (for example, color="primary" or color="secondary"). Ionic uses the palette's base color for the softly tinted track while keeping the selected surface and labels neutral. A surrounding colored toolbar only supplies colors when the segment has no color of its own. The optional moving glass inherits the selected surface color, and custom Ionic palettes work without additional registration.
\nAdd .segment-expand when segment buttons should divide the available width evenly. The class also changes the Liquid Glass effect sizing when registerSegmentEffect is used.
\nSegment size and press behavior \n\n\n\nPlacement \nMinimum height \nContainer behavior \n \n \n\nIn content \n32px \nCompact, flat background and indicator; outer bounds stay fixed. \n \n\nInside ion-toolbar \n48px \nGlass container; outer container scales while pressed. \n \n\n.segment-expand, including in a toolbar \n32px \nCompact, flat background and indicator; outer bounds stay fixed. \n \n
\nThe optional moving glass lens is independent of the container's background.
\n< ion-segment class = "segment-expand" value = "new" > \n < ion-segment-button value = "new" >< ion-label >New</ ion-label ></ ion-segment-button > \n < ion-segment-button value = "replied" >< ion-label >Replied</ ion-label ></ ion-segment-button > \n</ ion-segment > \n\n\nPreview \n\n New \n Replied \n \n \n\nClassic search bar in a condense header \nThe theme gives iOS search bars the iOS 27 appearance by default. Add .searchbar-classic to the search field shown beneath a large title in an ion-header with collapse="condense". It uses the conventional filled iOS appearance and collapses with the large title instead of remaining in the fixed header.
\nPlace it in a toolbar with a color, such as color="light"; the classic background is derived from that color's contrast value.
\nThe example uses Ionic's standard collapsible large-title structure. Scroll the preview to collapse the large title and reveal the fixed header.
\n< div class = "ion-page" > \n < ion-header translucent = "true" > \n < ion-toolbar color = "light" > \n < ion-title >Search</ ion-title > \n </ ion-toolbar > \n </ ion-header > \n < ion-content color = "light" fullscreen = "true" > \n < ion-header collapse = "condense" > \n < ion-toolbar color = "light" > \n < ion-title size = "large" >Search</ ion-title > \n </ ion-toolbar > \n < ion-toolbar color = "light" > \n < ion-searchbar class = "searchbar-classic" placeholder = "Filter results" ></ ion-searchbar > \n </ ion-toolbar > \n </ ion-header > \n < ion-list inset = "true" > \n < ion-item-group > \n < ion-item >< ion-label >Recent item 1</ ion-label ></ ion-item > \n < ion-item >< ion-label >Recent item 2</ ion-label ></ ion-item > \n < ion-item >< ion-label >Recent item 3</ ion-label ></ ion-item > \n < ion-item >< ion-label >Recent item 4</ ion-label ></ ion-item > \n < ion-item >< ion-label >Recent item 5</ ion-label ></ ion-item > \n < ion-item >< ion-label >Recent item 6</ ion-label ></ ion-item > \n < ion-item >< ion-label >Recent item 7</ ion-label ></ ion-item > \n < ion-item >< ion-label >Recent item 8</ ion-label ></ ion-item > \n < ion-item >< ion-label >Recent item 9</ ion-label ></ ion-item > \n < ion-item >< ion-label >Recent item 10</ ion-label ></ ion-item > \n </ ion-item-group > \n </ ion-list > \n </ ion-content > \n</ div > \n\n\nPreview \n\n \n \n Search \n \n \n \n \n \n Search \n \n \n \n \n \n \n \n Recent item 1 \n Recent item 2 \n Recent item 3 \n Recent item 4 \n Recent item 5 \n Recent item 6 \n Recent item 7 \n Recent item 8 \n Recent item 9 \n Recent item 10 \n \n \n \n
\n \n\nThe .ion-page wrapper makes this embedded preview behave like a complete routed page. An application using ion-router-outlet normally receives that page container automatically. The inset list and its items only provide enough content to demonstrate scrolling; they are not required by .searchbar-classic.
\nOpting out \nAdd .ios-theme-disabled to an individual Ionic component when it must retain Ionic's standard iOS styling.
\n.ios26-disabled is deprecated but remains supported as an alias with the same behavior. Use .ios-theme-disabled for new code.
\n< ion-button >iOS 27 theme</ ion-button > < ion-button class = "ios-theme-disabled" >Standard Ionic button</ ion-button > \n\n\nPreview \niOS 27 theme Standard Ionic button \n \n\nFor the background model behind inset lists, see Using ion-item-group .
\n';
diff --git a/packages/capacitor-native-ui/README.md b/packages/capacitor-native-ui/README.md
index e7d79447..8888a6f4 100644
--- a/packages/capacitor-native-ui/README.md
+++ b/packages/capacitor-native-ui/README.md
@@ -62,5 +62,6 @@ For standard controls, follow the setup guide for your markup; the Ionic and HTM
- [Supported Ionic controls](docs/native-ui.md#supported-markup) and fallback behavior.
- [Configuration](docs/configuration.md): defaults, selected controls, rail-only mode, and live changes.
+- [Light and dark appearance](docs/appearance.md): inherit the Web theme, configure custom HTML, and understand shared rail appearance.
- [Vertical Bars](docs/vertical-bars.md): eligibility, appearance, and runtime API.
- [Migration](docs/migration.md): package names, imports, and native project updates.
diff --git a/packages/capacitor-native-ui/docs/appearance.md b/packages/capacitor-native-ui/docs/appearance.md
new file mode 100644
index 00000000..cb5fbee9
--- /dev/null
+++ b/packages/capacitor-native-ui/docs/appearance.md
@@ -0,0 +1,45 @@
+# Light and dark appearance
+
+Native UI reads the computed style of each projected source element and sends its light/dark appearance to the native renderer. Configure appearance in your Web CSS; there is no separate native dark-mode startup option.
+
+## How appearance is resolved
+
+The first applicable row wins:
+
+| Priority | Computed source style | Result |
+| -------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
+| 1 | Theme-provided `--ios27-color-scheme` is non-empty | `dark` selects dark; other values select light. The iOS 27 theme supplies this marker through its light and dark stylesheets. |
+| 2 | `--ion-background-color-rgb` supplies three RGB channels | Infers dark or light from the background brightness. |
+| 3 | CSS `color-scheme` contains only `dark` or only `light` | Uses that scheme. `light dark` does not select a single appearance. |
+| 4 | None of the above | Uses the system's `prefers-color-scheme: dark` setting. |
+
+Theme markers and Ionic background values take precedence over `color-scheme`. Changing only an arbitrary CSS background or text color does not select native light/dark appearance.
+
+## Match your Web theme
+
+| Web setup | What to configure |
+| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| iOS 27 theme | Load the matching [System, Class or Always dark-mode pair](../../theme-ios27/docs/setup.md#choose-one-dark-mode-pair). The theme supplies the appearance marker. |
+| Ionic with an existing theme | Keep `--ion-background-color-rgb` consistent with the active palette; native appearance uses it when no theme marker is present. |
+| HTML without those theme variables | Set `color-scheme: light` or `color-scheme: dark` to match the active Web theme. |
+| System-managed HTML appearance | Use system-responsive CSS; when no single scheme is selected, native appearance falls back to the system setting. |
+
+For example, a plain HTML app with a manual dark-mode class can declare:
+
+```css
+:root {
+ color-scheme: light;
+}
+
+:root.app-dark {
+ color-scheme: dark;
+}
+```
+
+Use the same class for your application's dark palette. This example assumes neither of the higher-priority theme variables is supplied. Class/style mutations and system appearance changes trigger re-evaluation while the runtime is active. For style changes without an observed DOM mutation, call `shell.markForCheck()` after applying them.
+
+## Shared Vertical Bars appearance
+
+Ordinary controls resolve appearance from their own source styles. Vertical Bars shares one native rail appearance: **if any projected control is dark, the whole rail uses dark appearance**. It does not render separate light and dark rail regions. Keep sources projected into the same rail on a consistent Web palette.
+
+Native light/dark appearance selects the system material and control appearance; it does not reproduce arbitrary application CSS. Vertical button fill and tint remain governed by the separate [button appearance settings](vertical-bars.md#choose-button-appearance).
diff --git a/packages/capacitor-native-ui/docs/configuration.md b/packages/capacitor-native-ui/docs/configuration.md
index 5a6741d4..9218de48 100644
--- a/packages/capacitor-native-ui/docs/configuration.md
+++ b/packages/capacitor-native-ui/docs/configuration.md
@@ -24,6 +24,8 @@ Without Ionic, import the same API from `@rdlabo/capacitor-native-ui/core`. That
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.md). 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.
@@ -94,7 +96,15 @@ Local `data-horizontal-bars="true|false"` and `data-vertical-bars="true|false"`
| `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. |
-`webHost` is an optional, application-styled HTML fallback rail. It must be connected in the same document and outside `root`, with neither element containing the other. Use a limited `root`, such as your ``, when supplying a sibling host; the default `document.body` cannot have such a host. Without a host, unavailable HTML rails leave their original controls visible. Ionic's existing rail layout uses its own fallback adapter.
+### 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 ``, 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.
diff --git a/packages/capacitor-native-ui/docs/framework-independent.md b/packages/capacitor-native-ui/docs/framework-independent.md
index adbb27cf..393ae8f9 100644
--- a/packages/capacitor-native-ui/docs/framework-independent.md
+++ b/packages/capacitor-native-ui/docs/framework-independent.md
@@ -30,12 +30,25 @@ The `/core` entry requires `@capacitor/core` but no Ionic dependencies. If this
```
-- `data-native-ui="button"` opts into a standard action button.
-- `data-native-ui="back-button"` opts into a standard back button. Its original click handler owns navigation; the plugin never calls `history.back()`.
-- `data-native-ui-placement="rail"` requests Vertical Bars. Without it, the source rectangle is used. `element` explicitly preserves that rectangle.
-- Sources must be ``, explicitly marked `ion-button`, or custom elements exposing `role="button"` and click behavior. Unmarked HTML is never inferred to be a control.
-
-Keep the accessible name, supported static SVG, disabled state and application handlers on the source. Names resolve from `aria-labelledby`, `aria-label`, then text. Native `disabled`, a disabled fieldset and `aria-disabled="true"` are respected. Hidden, removed, excluded or out-of-scope sources are retired and stale actions are rejected. Use `data-native-ui="disabled"` to exclude a subtree.
+| Attribute | Value | Effect |
+| -------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------ |
+| `data-native-ui` | `button` | Project a standard action button. |
+| `data-native-ui` | `back-button` | Project a standard back button. The source click handler owns navigation; the plugin never calls `history.back()`. |
+| `data-native-ui` | `disabled` | Exclude the element and its subtree from projection. |
+| `data-native-ui-placement` | `element` (default) | Use the source element's rectangle. |
+| `data-native-ui-placement` | `rail` | Request Vertical Bars; use the optional Web rail host when native rail presentation is unavailable. |
+
+Apply `button` and `back-button` to ``, explicitly marked `ion-button`, or elements exposing `role="button"` and click behavior. Unmarked HTML is never inferred to be a control. `data-native-ui-placement` chooses placement for a declared source; it does not opt an unmarked HTML element into projection.
+
+Keep the control's data and behavior on its source:
+
+| Source property | Projection behavior |
+| -------------------- | ------------------------------------------------------------------------------------------ |
+| Accessible name | Resolves from `aria-labelledby`, then `aria-label`, then text. |
+| Artwork | Uses supported static SVG. |
+| Disabled state | Respects native `disabled`, a disabled fieldset, and `aria-disabled="true"`. |
+| Visibility and scope | Hidden, removed, excluded or out-of-scope sources are retired; stale actions are rejected. |
+| Application handlers | Remain on the original element. |
Native and Web-proxy actions click the original element. An ordinary `` therefore keeps its original form behavior. Use `type="button"` for actions that must not submit. The native bridge does not preserve browser user-activation privileges for APIs such as popup opening.
@@ -51,6 +64,10 @@ const shell = await enableNativeUI({
Call once after the source root exists. Keep the handle and call `await shell.destroy()` when that root is disposed. The plugin does not add page padding or safe-area rules; retain your application's CSS. For Angular, React, or Vue, use the [app-root lifecycle examples](setup.md#place-startup-in-your-frameworks-app-root) with the `/core` import. For animated navigation, use [suspension through completion and cancellation](setup.md#navigation-without-the-theme).
+### Match the HTML theme
+
+For a custom HTML theme, declare `color-scheme: light` or `color-scheme: dark` in the same CSS that selects your Web palette. Background colors alone do not select native appearance. See [Light and dark appearance](appearance.md) for a manual-toggle example, precedence when Ionic/theme variables are present, and shared rail behavior.
+
## 4. Build and run on iOS
Build your Web app, run `npx cap sync ios`, and launch the iOS project from Xcode. With the step 3 configuration, Add renders natively and invokes its original click handler. With the rail configuration below, Add retains native element placement and Back uses the native rail when available. In a browser, the same button remains an ordinary Web button. Unmarked HTML remains unchanged.
@@ -83,6 +100,8 @@ Explicit markup chooses placement: Back goes to the rail and Add keeps native el
Currently the HTML adapter supports buttons and back buttons. Arbitrary HTML tabs, segments, searchbars and FAB structures are not translated. Ionic adapters continue to support their existing controls. Custom element extensions work with the same scoped root; the existing `tab-accessory` extension placement still requires an Ionic tab-bar owner.
+For device-driven spacing and placement, see [Use with Capacitor Foldable](vertical-bars.md#use-with-capacitor-foldable). Read its initial placement and subscribe to changes, applying the edge and inset to your own CSS; the theme package is not required.
+
## Example
The [plain HTML example](https://github.com/rdlabo-dev/ionic-theme-ios27/tree/main/examples/native-ui-html) demonstrates Add, routerless Home/Details/Back navigation, optional Web rail projection and cleanup. It uses no Ionic or theme CSS.
diff --git a/packages/capacitor-native-ui/docs/native-ui-extensions.md b/packages/capacitor-native-ui/docs/native-ui-extensions.md
index 39bc94b5..f9c02916 100644
--- a/packages/capacitor-native-ui/docs/native-ui-extensions.md
+++ b/packages/capacitor-native-ui/docs/native-ui-extensions.md
@@ -66,7 +66,15 @@ source.querySelector('button')!.addEventListener('click', () => {
The shell observes DOM changes and sends the new props to the native content. State rendered into DOM attributes or text is observed automatically. For properties or external state read by the reader that do not mutate the DOM, call `shell.markForCheck()` after changing them.
-The reader is synchronous and read-only. Return `null` when the source should stay on the Web. Props are a JSON object (finite numbers, strings, booleans, null, arrays and objects), limited to 64K characters. Action names map to the source itself or its descendants; missing, hidden and disabled targets cannot be activated. Up to 30 actions are supported per source.
+### Reader contract
+
+| Part | Requirement |
+| -------------- | -------------------------------------------------------------------------------------------------------------- |
+| Execution | Synchronous and read-only. |
+| Web fallback | Return `null` when the source should stay on the Web. |
+| `props` | JSON object containing finite numbers, strings, booleans, null, arrays and objects; limited to 64K characters. |
+| Action targets | The source itself or its descendants. Missing, hidden and disabled targets cannot be activated. |
+| Action count | Up to 30 per source. |
## 2a. Register a SwiftUI View on iOS
@@ -227,10 +235,19 @@ For source-specific fallback reasons, temporarily enable [`debug: true`](configu
| Omitted or `'element'` | Hosts native content in the source element's Web frame on iOS 26+ or Android API 24+. |
| `'tab-accessory'` | Attaches the View to a projected tab bar's system SwiftUI tab accessory on iOS 26.1+. |
-For `tab-accessory`, place the source inside its owning `ion-tabs`. The shell resolves that tabs element's `ion-tab-bar`; applications do not pass native controller references. Currently only the **Vertical Bars SwiftUI tab host** supports this placement. Ordinary UIKit tab bars, missing native tabs, iOS 26.0, or multiple competing accessories keep the source on the Web. The shell does not approximate a system accessory with a floating overlay. Keep an equivalent Web layout (for example, a fixed footer); the source retains its layout space while hidden.
-
Extensions are independent of `controls` and `ionic.auto`, including with `enableVerticalControlArea()`. The application controls their presentation: return `null` from the reader when a source should stay on the Web, then call `markForCheck()` when that condition changes. Accessories still require their supported native tab host.
+### Tab accessory requirements
+
+| Requirement | Supported configuration |
+| ------------------- | ------------------------------------------------------------------------------------------------- |
+| Source location | Inside its owning `ion-tabs`; the shell resolves that element's `ion-tab-bar`. |
+| Native host | Vertical Bars SwiftUI tab host on iOS 26.1+. |
+| Accessory count | A single accessory for the host. |
+| Web fallback layout | Keep an equivalent Web layout, such as a fixed footer; the source retains its space while hidden. |
+
+Ordinary UIKit tab bars, missing native tabs, iOS 26.0 or competing accessories keep the source on the Web. Applications do not pass native controller references, and the shell does not approximate a system accessory with a floating overlay.
+
## Read source geometry and presentation state
The shell measures the source DOM element automatically. With the default `element` placement, it positions and sizes the SwiftUI host to match that rectangle. You do not need to send coordinates in `props` or position the native view yourself.
@@ -359,7 +376,16 @@ The shell owns discovery, geometry, state synchronization, native controller con
Element placement requires the source to fit fully inside the viewport and its ancestors' overflow clipping bounds, including Ionic's shadow scroll containers. Partially clipped sources stay on the Web and become eligible again when scrolled fully into view. Tab accessories use the system host's placement instead.
-`try await actions.activate(name)` completes after the original Web element receives its click. It does **not** wait for application work launched by the click handler (network requests, navigation or modal presentation). Disabled, removed or stale actions reject; teardown, native task cancellation and bridge timeouts settle pending calls. The bridge acknowledgement is a Capacitor Promise method.
+### Action completion
+
+| Event | Result of `try await actions.activate(name)` |
+| ------------------------------------------------------------------ | -------------------------------------------- |
+| Original Web element receives its click | Completes. |
+| Handler starts a network request, navigation or modal presentation | Does not wait for that application work. |
+| Action is disabled, removed or stale | Rejects. |
+| Teardown, native task cancellation or bridge timeout | Settles pending calls. |
+
+The bridge acknowledgement is a Capacitor Promise method.
The [demo reader](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/demo/src/native-ui/counter.ts) and [demo SwiftUI View](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/demo/ios/App/App/NativeUICounter.swift) show both placements using app-local files. Open the Native UI demo page to compare native projection with its Web fallback.
diff --git a/packages/capacitor-native-ui/docs/native-ui.md b/packages/capacitor-native-ui/docs/native-ui.md
index 04b6c6eb..e9b0c73e 100644
--- a/packages/capacitor-native-ui/docs/native-ui.md
+++ b/packages/capacitor-native-ui/docs/native-ui.md
@@ -37,11 +37,23 @@ import { configureNativeTransition } from '@rdlabo/capacitor-native-ui';
await configureNativeTransition();
```
-If using the theme’s `iosTransitionAnimation`, keep that setting; it already coordinates native transitions. For Ionic default or custom animations, configure the [transition adapter](migration.md#native-ui-transition-adapter). No per-page registration, component list, native callback, or Swift view controller is required. Run `npx cap sync ios` after installing or updating the package. The package supplies both Swift Package Manager (SPM) and CocoaPods integration. Keep your existing dependency manager when syncing. If you choose to migrate an existing CocoaPods app to SPM, run `npx cap spm-migration-assistant` and link the generated `CapApp-SPM` package to the app target in Xcode. Build with Xcode 26 or later and Capacitor 8; native glass requires iOS 26 or later. Web, Android, SSR and older iOS keep the Web implementation.
+### Navigation and native builds
+
+| Setup | Action |
+| ----------------------------------- | --------------------------------------------------------------------------------------------------------------- |
+| Theme's `iosTransitionAnimation` | Keep it; native transitions are already coordinated. |
+| Ionic default or custom animation | Configure the [transition adapter](migration.md#native-ui-transition-adapter). |
+| Plugin installed or updated | Run `npx cap sync ios`, then rebuild the native app. |
+| SPM or CocoaPods | Keep the existing dependency manager; both are supported. |
+| Optional CocoaPods-to-SPM migration | Run `npx cap spm-migration-assistant`, then link the generated `CapApp-SPM` package to the app target in Xcode. |
+| Ordinary native glass | Requires Xcode 26+, Capacitor 8 and iOS 26+. |
+| Web, Android, SSR or older iOS | Retain the Web implementation. |
+
+No per-page registration, component list, native callback or Swift view controller is required.
This is an opt-in feature. The ordinary `@rdlabo/ionic-theme-ios27` theme entry point does not import Capacitor. Native runtime entry points require `@capacitor/core`, including in browser builds. Native sources are still detected and built by Capacitor's sync when `@rdlabo/capacitor-native-ui` is a direct dependency of a Capacitor project, even if the application does not call `enableNativeUI()`.
-Native appearance follows the applied class, system or always-dark theme CSS. System theme changes are synchronized while the runtime is active.
+Native light/dark appearance is resolved from each source element’s computed style. See [Light and dark appearance](appearance.md) for theme inheritance, priority, live updates and the shared Vertical Bars behavior.
## Toolbar selection and grouping
@@ -65,11 +77,12 @@ Here, ordinary toolbar actions project individually and eligible rail actions ca
Restart the runtime to change these startup options.
-Use `data-horizontal-bars="true|false"` for local horizontal overrides, and
-`data-vertical-bars="true|false"` for vertical overrides. Each attribute applies
-only in its corresponding layout. On a button/back button it controls selection;
-on `ion-buttons` it controls grouping only. Neither attribute is inherited.
-Invalid or empty values fall back to the matching startup option.
+| Attribute | Applies in | `true` | `false` |
+| ---------------------- | ----------------------- | ------------------------------------------------------ | --------------------------------------------------------- |
+| `data-horizontal-bars` | Ordinary placement | Select the action, or allow grouping on `ion-buttons`. | Exclude the action, or disable grouping on `ion-buttons`. |
+| `data-vertical-bars` | Vertical rail placement | Select the action, or allow grouping on `ion-buttons`. | Exclude the action, or disable grouping on `ion-buttons`. |
+
+On a button/back button these attributes control selection; on `ion-buttons` they control grouping only. Neither is inherited. Missing, invalid or empty values use the matching `controls` default. Vertical action opt-ins and their eligibility rules are detailed in [Vertical Bars](vertical-bars.md#explicit-action-selection-and-local-grouping).
```html
@@ -110,6 +123,8 @@ The table below describes the ordinary Native UI. Vertical Bars uses the separat
For the ordinary Native UI, only supported iOS-mode components are eligible. Theme variables are not required; calling `enableNativeUI()` opts into native projection. Explicitly enabled Vertical Bars is mode-independent as described below. `ios-theme-disabled` and the legacy `ios26-disabled` on an element or ancestor always exclude it. A disabled theme on one tab/segment item keeps its whole group on the Web.
+### Opt out of native projection
+
Use `data-native-ui="disabled"` (or the equivalent `native-ui-disabled` class) to disable only the iOS Native UI while keeping the Web theme. It excludes the element and all its descendants. Adding or removing the attribute or class at runtime automatically restores Web rendering or re-evaluates native eligibility.
Only the exact value `disabled` opts out; an empty or unknown value does not opt out. Remove the attribute to restore automatic Ionic projection. For explicitly declared HTML controls, restore the original `data-native-ui="button"` or `data-native-ui="back-button"` value instead. Alternatively, set `data-native-ui="disabled"` on a parent to disable a subtree while preserving its children's declarations. If the `native-ui-disabled` class is also present, remove it as well. This does not disable clicks or change the Web theme. The runtime-owned `data-native-ui-projected` marker is separate; applications should not set it. For the previous opt-out attribute, see [migration](migration.md#native-ui-naming-changes).
@@ -122,13 +137,58 @@ Only the exact value `disabled` opts out; an empty or unknown value does not opt
In ordinary placement, an explicit `data-native-ui="disabled"` or `.native-ui-disabled` opt-out inside a shared surface keeps that surface and its children on the Web, including button groups, tab bars, segments and FAB lists. This is different from disabling grouping with `buttonGroup`. Opting out of the search FAB or any part of the search footer disables native search integration; the tab bar can still render natively if it remains eligible.
-Placement is required even when the appearance is glass. In the ordinary Native UI, buttons, back buttons, menu-button groups and segments need a toolbar directly inside `ion-header` or `ion-footer`, with no `ion-content` ancestor around the control. Buttons directly inside a header/footer, standalone toolbars, and toolbars or headers nested in scrolling content stay on the Web. FABs without `slot="fixed"` also stay on the Web. Moving a projected control to an excluded location restores its Web rendering; moving it back re-evaluates eligibility. When `.ios-theme-vertical-bars` is enabled, a standard `ion-back-button` can instead be projected to the Vertical Control Area from outside a fixed toolbar, including routed content or a persistent app shell. The application chooses where to enable this mode and which Ionic component mode to use; Vertical Bars projection does not require `ios` mode classes. Collapsed headers, opted-out controls and departed pages are excluded. Full-width foreground modals can participate in Vertical Bars as described below; other overlay surfaces keep their own layout.
+### Placement requirements
+
+Glass appearance alone is not enough. Ordinary projection requires fixed controls:
+
+| Control or location | Eligibility |
+| ------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
+| Buttons, back buttons, menu-button groups and segments | Toolbar directly inside `ion-header` or `ion-footer`, with no `ion-content` ancestor around the control. |
+| Buttons directly inside a header/footer, standalone toolbars, or toolbars/headers in scrolling content | Stay on the Web. |
+| FAB | Must use the `ion-content` fixed slot. |
+| Back button with `.ios-theme-vertical-bars` enabled | Can project outside a fixed toolbar, including routed content or a persistent app shell. Vertical Bars does not require `ios` mode. |
+| Collapsed headers, opted-out controls and departed pages | Excluded. |
+| Full-width foreground modal | Can participate in Vertical Bars; other overlay surfaces keep their own layout. |
+
+Moving a projected control to an excluded location restores its Web rendering; moving it back re-evaluates eligibility. The application chooses where to enable Vertical Bars and which Ionic component mode to use.
+
+### Tab layout
+
+| Source layout | Result |
+| -------------------------------------------------------------------- | --------------------------------------------------------------- |
+| Equal-width items with the default `layout="icon-top"` | Native stacked icon/label layout, including iPad and landscape. |
+| `icon-start`, `icon-end`, `icon-bottom`, `icon-hide` or `label-hide` | Entire tab bar stays on the Web. |
+| Unequal item widths | Entire tab bar stays on the Web. |
+| Start, center or end placement | Follows the source bar, including RTL. |
+| Directional `ion-icon` artwork | Preserves its rendered RTL flip. |
+
+The native bar uses local compact horizontal and regular vertical size classes without changing the app's size class. Label size and weight follow the Web snapshot.
-Native tabs accept equal-width items with Ionic's default `layout="icon-top"`. The native bar uses a local compact horizontal and regular vertical size class to preserve the Web's stacked icon/label arrangement on iPad and in landscape. This does not change the app's size class. Label size and weight follow the Web snapshot. Other explicit Ionic layouts (`icon-start`, `icon-end`, `icon-bottom`, `icon-hide`, `label-hide`) and unequal item widths keep the entire tab bar on the Web. Start, center and end placement follow the original `ion-tab-bar`, including RTL. Directional `ion-icon` artwork preserves its rendered RTL flip.
+### Ordinary button grouping
-Outside Vertical Bars, clear buttons can project individually inside `ion-buttons`; standalone clear, solid and outline buttons remain on the Web. With grouping enabled, two or more eligible clear buttons share one native surface while retaining separate actions. Menu buttons can also join that group; a single menu button uses its parent group as the surface. If grouping is disabled or the group contains mixed fills or unsupported children, eligible actions can still project individually. Back buttons never join the group.
+These rules apply outside Vertical Bars:
-Custom button colors, custom back icons/colors, collapsing headers, toolbars inside scrolling content, modal content, scrollable/expanded segments and segment-view integration remain on the Web. Complex slots and unsupported SVG features also fall back to Web. The plugin does not translate arbitrary application CSS into UIKit styles.
+| Source or setting | Result |
+| ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
+| Clear button inside `ion-buttons` | Can project individually. |
+| Standalone clear, solid or outline button | Stays on the Web. |
+| Grouping enabled with two or more eligible clear buttons | One native surface with separate actions. Menu buttons can join. |
+| Single menu button | Uses its parent group as the surface. |
+| Grouping disabled, mixed fills or unsupported group children | Eligible actions can still project individually. Explicit subtree opt-outs follow the shared-surface rules above. |
+| Back button | Never joins an ordinary action group. |
+
+### Unsupported content
+
+The following remain on the Web; the plugin does not translate arbitrary application CSS into UIKit styles:
+
+| Area | Unsupported configuration |
+| ---------- | ------------------------------------------------------------------------------------------------------------------------------------- |
+| Buttons | Custom button colors or custom back icons/colors. |
+| Containers | Collapsing headers, toolbars inside scrolling content, and ordinary modal content. See the separate Vertical Bars modal policy below. |
+| Segments | Scrollable/expanded segments and segment-view integration. |
+| Content | Complex slots and unsupported SVG features. |
+
+### Backgrounds and fallback handoffs
Native glass samples the Web content actually drawn behind it. Existing toolbar backgrounds and header blur still affect that content. For content to scroll beneath a header, use Ionic's normal translucent-header/fullscreen-content layout; the plugin does not move page content or override an application-owned opaque toolbar background.
@@ -138,14 +198,53 @@ When an existing control becomes unsupported, its Web source is painted before i
## State and events
+### Menu actions
+
Menu buttons use Ionic's resolved default/configured icon or a supported slot icon/label. Native activation clicks the original `ion-menu-button`, preserving `menu` targeting. Non-default menu button types (`submit` / `reset`) remain on the Web. `disabled`, `autoHide`, menu availability and split-pane visibility follow the actual DOM. Opening a menu restores Web controls and retires their native covers; closing it reprojects eligible controls.
-The DOM owns labels, SVG content, placement, selected values and application behavior. The placement reference is `ion-tab-bar` itself. The native tab content uses that rectangle as its sizing proposal and placement anchor, accounting for UITabBar's larger outer frame: `tab-bar-position-start`, `tab-bar-position-center`, and `tab-bar-position-end` control the horizontal anchor (including RTL), while `slot="bottom"` retains the bottom edge and `slot="top"` retains the top edge. Class changes are reconciled automatically. UIKit owns internal margins and platter sizing, while the supported stacked layout and label typography follow the Web. UIKit can cap the content width even with fill positioning. On viewports at least 768px wide, the Web theme caps standard two-, three-, four- and five-item bars near the iPad stacked platter measurements (188, 274, 336 and 414pt). Smaller viewports retain the phone sizing. The standard bar is 62pt high, with a 54pt selection and a 4pt inset. Adjacent buttons overlap like UIKit’s controls; icon and label alignment is checked against simulator screenshots. These are default appearance targets, not a guarantee for custom fonts, icons or labels; UIKit still determines its intrinsic width. A different native width or height does not reject projection. Badge and title updates retain native item identities and remeasure the platter at the same placement anchor. The plugin does not stretch icons or modify UIKit's internal controls to force CSS item widths. The plugin measures the view subtree containing the native tab controls without private class names or fixed inset corrections; an unrecognized layout restores Web rendering. The runtime watches structural changes, affected shadow roots, sizing, page lifecycle events and overlays. Ancestor `display: none`, `hidden`, theme classes, component removal and disabled changes are reconciled automatically. Native touch events are checked against the current DOM and revision before clicking the original Ionic element.
+### Tab geometry and state updates
+
+The DOM owns labels, SVG content, placement, selected values and application behavior. `ion-tab-bar` itself is the placement reference; UIKit owns internal margins and platter sizing.
+
+| Source or viewport | Native behavior |
+| --------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
+| Source bar rectangle | Sizing proposal and placement anchor, accounting for UIKit's larger outer frame. |
+| `tab-bar-position-start`, `tab-bar-position-center`, `tab-bar-position-end` | Horizontal anchor, including RTL; class changes reconcile automatically. |
+| `slot="bottom"` / `slot="top"` | Retains the corresponding bottom/top edge. |
+| Supported stacked layout and label typography | Follow the Web snapshot. UIKit can cap content width even with fill positioning. |
+| Badge or title updates | Retain native item identities and remeasure at the same anchor. |
+| Different native width or height | Does not reject projection; icons are not stretched to force CSS item widths. |
+| Unrecognized native layout | Restores Web rendering. |
+
+The Web theme's standard appearance targets are:
+
+| Measurement | Target |
+| ------------------------------------------------ | ------------------------------------------------------------------------------------------------ |
+| Viewport at least 768px wide, 2 / 3 / 4 / 5 tabs | Platter widths near 188 / 274 / 336 / 414pt respectively. Smaller viewports retain phone sizing. |
+| Bar height | 62pt |
+| Selection height / inset | 54pt / 4pt |
+| Adjacent buttons | Overlap like UIKit controls. |
+
+These targets do not guarantee identical custom fonts, icons or labels; UIKit determines intrinsic width. Icon and label alignment is checked against simulator screenshots. The plugin measures the native tab subtree without private class names or fixed inset corrections.
+
+The runtime rechecks:
+
+- Structural changes, affected shadow roots and sizing.
+- Page lifecycle events and overlays.
+- Ancestor `display: none`, `hidden`, theme classes, component removal and disabled changes.
+
+Native touch events are checked against the current DOM and revision before clicking the original Ionic element.
+
+### Forms and segment values
For forms, retain `ion-button type="submit"` and the form's existing submit handler. An external form is still passed as `[form]="formRef"`. The plugin does not call `form.submit()`, add a second submit path or change Angular form ownership. Segment values retain their original type because the original `ion-segment-button` is clicked; programmatic value changes do not emit a synthetic `ionChange`.
+### Text and artwork
+
Labels use native text. Local static SVGs and resolved `ion-icon` SVGs (including `name` and changes to `name`) are rasterized at the display scale and cached. Source projection preserves their colors; system projection of vertical toolbar buttons uses template icons tinted by SwiftUI. Tab SVGs that follow text color use native template rendering, so icons and labels change selection color together without waiting for another bridge image. Multicolor tab artwork retains its original colors. External references, ``, animation, embedded HTML/images, SVG text and stylesheets are excluded. Web fonts and arbitrary slot layouts are not reproduced exactly.
+### Touch, badges and accessibility
+
The native host accepts input only within native controls. Tab interaction and accessibility are provided by the standard [UITabBar](https://developer.apple.com/documentation/uikit/uitabbar) control. Empty areas pass touches to the WebView. Ionic iOS hides empty badges by default. An empty visible `ion-badge` becomes a native notification dot; a non-empty badge displays its text. Badge background and text colors come from computed DOM styles, including Ionic `color` palettes. Hidden or removed badges clear the native badge. Tabs use UIKit’s standard tab control for selection, touch behavior, badges and accessibility, retaining item identity across selection updates. UIKit owns the item layout within the measured bar, so arbitrary CSS item placement is not reproduced. Native controls expose names, disabled/selected traits and badges to accessibility; the source is hidden from Web accessibility while projected. This is not a guarantee of identical VoiceOver traversal between Web and UIKit.
## Searchable tabs
@@ -162,7 +261,16 @@ Opening search preserves the selected Ionic tab and does not automatically show
Native edits pass through Ionic's input handlers, preserving `ionInput` debounce, `ionChange`, `ionFocus`, `ionBlur`, and `ionClear`. Programmatic `value` changes do not emit `ionInput`; synchronous application corrections and stale native input are distinguished. Native editing owns marked text and the caret. Returning through the footer's close action retains the value and does not emit `ionCancel` or `ionClear`.
-The first supported search configuration uses iOS-mode glass searchbars with the standard search keyboard, default clear control, no internal cancel button, and default autocorrection/capitalization settings. Custom input modes, return-key hints, min/max length, autocomplete, autocorrection, spellcheck, clear icons and classic searchbars remain on the Web. `disabled`, `placeholder`, `value` and `setFocus()` are synchronized for supported groups. General standalone searchbars are outside this feature.
+### Supported search configuration
+
+| Area | Native support | Remains on the Web |
+| -------------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
+| Search group | Registered searchable tabs with an iOS-mode glass searchbar. | General standalone searchbars and classic searchbars. |
+| Keyboard and text settings | Standard search keyboard and default autocorrection/capitalization. | Custom input modes, return-key hints, min/max length, autocomplete, autocorrection or spellcheck settings. |
+| Clear/cancel controls | Default clear control, no internal cancel button. | Custom clear icons or an internal cancel button. |
+| State and focus | Synchronizes `disabled`, `placeholder`, `value` and `setFocus()`. | Unsupported group configurations use Web rendering. |
+
+### Search lifetime and recovery
Page retirement, overlays, theme exclusion and lost native ownership close the native presentation and preserve the latest synchronized/application value. A later search starts from that closed state. The registration survives cached page transitions; reattaching on every return is not required. Bridge waits are finite. When an opening request loses the bridge, its pending Enter can complete through the existing Web animation. A disconnected bridge cannot recover native characters that were never delivered to JavaScript.
@@ -172,14 +280,27 @@ While enabled, Native UI suppresses the WebView’s top scroll-edge effect becau
The native search controller remains visible over its own keyboard. Other projected controls are hidden while a Web input opens the keyboard. UIKit's own accessibility and Reduce Motion behavior apply to the standard controls; full VoiceOver traversal is not a verified parity guarantee.
-## Transitions and recovery
+## FAB behavior
FABs retain Ionic's `activated`, per-child `show`, `close()` and original click handlers. Multiple lists, initial expansion, small buttons and `edge` use each button's measured layout. The native side reflects Ionic's staggered visibility without adding another timer or open/close controller. Main-button icon changes crossfade using the resolved `closeIcon`; Reduce Motion disables this crossfade. Native FAB button instances persist across open/close updates. The source FAB stays projected throughout ordinary opening and closing.
-FAB support covers the standard circular glass appearance, text and resolved static SVG/`ion-icon` content, including RTL icon mirroring. One unsupported child keeps the whole FAB in Web rendering, including when its list is closed. Colored solid buttons, submit/reset or href FABs, custom host backgrounds/shapes/motion, non-fixed-slot placement and unsupported artwork remain Web. For example, the demo's red-background `floating-action-button-fixed` page retains its existing Web appearance. A fixed-slot FAB must be a direct child of `ion-content`; a `slot="fixed"` attribute on a page sibling is not a content slot.
+### FAB eligibility
+
+One unsupported child keeps the whole FAB on the Web, even when its list is closed.
+
+| Area | Supported | Remains on the Web |
+| ---------- | ----------------------------------------------------------------- | --------------------------------------------------------------- |
+| Appearance | Standard circular glass. | Colored solid buttons or custom host backgrounds/shapes/motion. |
+| Content | Text and resolved static SVG/`ion-icon`, including RTL mirroring. | Unsupported artwork. |
+| Actions | Original click handlers. | Submit/reset or `href` FABs. |
+| Placement | `slot="fixed"` on a direct child of `ion-content`. | Non-fixed placement or `slot="fixed"` on a page sibling. |
+
+For example, the demo's red-background `floating-action-button-fixed` page retains its Web appearance.
Custom host animation or transition declarations on the FAB, list or button keep the group on the Web until removed. Button transforms support the standard identity and hidden-child scale(0), not custom scaling. For children inside a `display:none` list, browsers can report a computed transform of `none` even when a custom transform is declared. Such transforms are checked when layout becomes available; the whole FAB then returns to Web if needed. The plugin does not parse application stylesheets or temporarily open lists to predict hidden layout.
+## Transitions and recovery
+
The built-in `iosTransitionAnimation` waits for native retirement before starting the Web animation. Interactive progress and completion/cancellation are queued while that retirement is in progress. Stationary shared tabs are retained. First render and transitions without an animation builder are covered by the startup runtime and Ionic lifecycle events. Default Ionic and custom navigation builders can use the same integration through `withNativeUITransition()`, exported by `@rdlabo/ionic-theme-ios27`. See [Connect your navigation animation](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/theme-ios27/docs/iphone-duo-with-original-theme.md#3-connect-your-navigation-animation) for the setup.
Ordinary native/Web handoffs share a 180ms opacity crossfade, including Vertical Bars in both directions. Outgoing visuals cannot receive input or accessibility focus. Reduce Motion and modal presentation use immediate handoffs; search controllers keep their UIKit-managed transition.
@@ -247,7 +368,21 @@ Only one popover is relayed at a time. A nested popover or modal created in the
Native presentation owns the arrow, corners and animation; custom Web enter/leave animations do not replace them. The source enter builder runs once with zero duration and delay (including child animations), using the native anchor rectangle for the default `reference="trigger"` layout and visible state for Web fallback. Explicit `reference="event"` retains Ionic’s event-coordinate semantics. An explicit `enterAnimation` is honored; otherwise this package’s iOS popover builder is used. Set `enterAnimation` on the overlay to use a custom builder rather than relying on global `popoverEnter` configuration. Material Design (`md`) popovers keep Ionic’s Web presentation. Initial content dimensions come from Ionic.
-The relay copies document and popover shadow styles and preserves the content border spacing. Before native presentation it compares selected computed layout and appearance properties at the source dimensions. Detected differences, unsupported ancestor visual effects or size containers, and content exceeding 1,000 elements restore the live Web popover. Changes to mirrored ancestor attributes or source head styles also restore Web rendering. This is a conservative compatibility check, not a guarantee for arbitrary CSS (including pseudo-elements and dynamic CSSOM changes), document-bound queries, or full VoiceOver/hardware-keyboard parity. Failure to prepare or find the native anchor restores the Web popover. A popover opened from a Vertical Bars button also returns the rail to Web projection for the remainder of that presentation; dismissal restores native projection.
+#### Popover fallback conditions
+
+The relay copies document and popover shadow styles and preserves content border spacing. Before presentation, it compares selected computed layout and appearance properties at the source dimensions.
+
+| Condition | Result |
+| --------------------------------------------------------- | ------------------------------ |
+| Detected style/layout differences | Restores the live Web popover. |
+| Unsupported ancestor visual effects or size containers | Restores the live Web popover. |
+| Content exceeds 1,000 elements | Restores the live Web popover. |
+| Mirrored ancestor attributes or source head styles change | Restores Web rendering. |
+| Preparation fails or the native anchor is unavailable | Restores the Web popover. |
+
+A rail-button popover falling back to Web also returns the rail to Web projection until dismissal. This compatibility check does not guarantee arbitrary CSS (including pseudo-elements and dynamic CSSOM changes), document-bound queries, or full VoiceOver/hardware-keyboard parity.
+
+### Custom overlay suspension
For a custom modal or overlay that Native UI cannot detect, acquire a suspension before presenting it. The resolved suspension means projected controls have returned to Web rendering. Always release it after dismissal:
@@ -266,9 +401,9 @@ Suspensions are nestable and `resume()` is idempotent. Native projection resumes
The native material and control appearance follow the running iOS version; an iOS 26 device does not acquire iOS 27's appearance merely by installing this theme.
-## Support iPhone Duo (preview)
+## Support iPhone Duo
-iPhone Duo support, including standalone vertical bars without Native UI, is available as a **preview** alongside Native UI. Its APIs and supported behavior may change.
+iPhone Duo support, including standalone Vertical Bars, is stable starting with version 2.0.0.
The standalone Vertical Control Area entry point (`@rdlabo/capacitor-native-ui/vertical-bars`) works with application-supplied Web layout. The theme package offers an [optional rail stylesheet](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/theme-ios27/docs/vertical-bars-layout.md), `@rdlabo/ionic-theme-ios27/dist/css/vertical-bars.css`, which can be used without loading the full iOS 27 appearance. Call `enableNativeUI({ controls: 'verticalOnly' })` for this use case; its default selection targets controls placed in the vertical area. Apps already calling `enableNativeUI()` should keep that single runtime rather than starting both. See [iPhone Duo support](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/theme-ios27/docs/iphone-duo.md) for the complete setup, including hinge posture and the split-pane layout for apps that do not use this shell at all.
diff --git a/packages/capacitor-native-ui/docs/setup.md b/packages/capacitor-native-ui/docs/setup.md
index dd7b54f7..d7afc70d 100644
--- a/packages/capacitor-native-ui/docs/setup.md
+++ b/packages/capacitor-native-ui/docs/setup.md
@@ -8,7 +8,14 @@ Read [how projection works](../README.md#how-projection-works) first if you are
## Before you start
-Use an existing Ionic `>=8.8.1 <10` application with Capacitor 8 and an iOS project. Build with Xcode 26 or later; ordinary native controls require iOS 26 or later. Vertical Bars have separate [device requirements](vertical-bars.md). Keep your current SPM or CocoaPods integration.
+| Requirement | Ordinary native controls |
+| ------------------------- | -------------------------------------------------------------------------- |
+| Ionic | `>=8.8.1 <10` |
+| Native project | Capacitor 8 with an iOS project |
+| Build tools | Xcode 26 or later |
+| Device or simulator | iOS 26 or later |
+| Native dependency manager | Keep your existing SPM or CocoaPods integration. |
+| Vertical Bars | See the separate [device requirements](vertical-bars.md#native-rendering). |
If the app does not yet use Capacitor, create and configure its Capacitor iOS project before following the native steps below. Browser previews retain Web controls and do not demonstrate UIKit or SwiftUI rendering.
diff --git a/packages/capacitor-native-ui/docs/vertical-bars.md b/packages/capacitor-native-ui/docs/vertical-bars.md
index bc7324a0..2de27f2f 100644
--- a/packages/capacitor-native-ui/docs/vertical-bars.md
+++ b/packages/capacitor-native-ui/docs/vertical-bars.md
@@ -1,8 +1,8 @@
---
-title: Vertical Bars (preview)
+title: Vertical Bars
---
-# Vertical Bars (preview)
+# Vertical Bars
Vertical Bars moves eligible Ionic controls or explicitly marked HTML buttons into a side rail while keeping the original elements as the source of labels, icons and behavior. It works with the iOS theme or an existing Ionic theme, independently of hinge posture and the full Native UI.
@@ -12,9 +12,9 @@ Choose your setup before using this reference:
- **No Ionic:** [HTML setup](framework-independent.md) and its [Web rail configuration](framework-independent.md#optional-web-rail). Use `/core` and your own layout; the Ionic setup below does not apply.
- **Existing iOS 27 theme and Native UI:** [Connect device placement](../../theme-ios27/docs/iphone-duo.md#project-controls-into-the-rail) using your existing runtime.
-Return to this page for control eligibility, native button appearance and runtime options. Device events and Ionic split panes are covered in [iPhone Duo support](../../theme-ios27/docs/iphone-duo.md).
+This page covers device-plugin integration, control eligibility, native appearance and runtime options. For posture-driven Ionic split panes, see [iPhone Duo support](../../theme-ios27/docs/iphone-duo.md).
-Available as a **preview** feature. APIs and supported behavior may change.
+Vertical Bars and iPhone Duo support are stable starting with version 2.0.0.
See [shared configuration](configuration.md) for all defaults and live `rail.enableVerticalControlArea()` / `rail.disableVerticalControlArea()` methods. [Plain HTML controls](framework-independent.md) use the same native rail renderers.
@@ -36,7 +36,7 @@ import { enableNativeUI } from '@rdlabo/capacitor-native-ui';
const rail = await enableNativeUI({ controls: 'verticalOnly' });
```
-Configure your Web rail layout and [apply device placement](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/theme-ios27/docs/iphone-duo.md#project-controls-into-the-rail) for an iPhone Duo. The application owns placement and cleanup: call `await rail.destroy()` when its owner is disposed. If you already use `enableNativeUI()`, keep that runtime; it includes Vertical Bars. Do not start both.
+Configure your Web rail layout and [apply device placement](#use-with-capacitor-foldable) for an iPhone Duo. The application owns placement and cleanup: call `await rail.destroy()` when its owner is disposed. If you already use `enableNativeUI()`, keep that runtime; it includes Vertical Bars. Do not start both.
The `/vertical-bars` entry point requires `@capacitor/core`, including in browser builds. Using only the theme’s rail CSS needs no native runtime. For native setup and navigation transitions, follow the [existing-theme guide](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/theme-ios27/docs/iphone-duo-with-original-theme.md).
@@ -48,6 +48,53 @@ The application or theme owns Web spacing, rail positioning, and fallback styles
The [theme's optional rail stylesheet](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/theme-ios27/docs/vertical-bars-layout.md) supplies one ready-made layout. Its classes and CSS variables are not a requirement to install the theme package: applications may provide equivalent layout themselves. The runtime currently uses the `ios-theme-vertical-bars` class on `ion-app` to identify the rail layout, regardless of who supplies its styles.
+## Use with Capacitor Foldable
+
+Use [`@erkamyaman/capacitor-foldable`](https://github.com/erkamyaman/capacitor-foldable) when your Web layout needs to follow the device's bar placement or hinge posture. Native UI does not subscribe to Foldable automatically.
+
+| Owner | Responsibility |
+| -------------------- | ------------------------------------------------------------------------------------------- |
+| Foldable | Report initial device state and subsequent placement/posture changes. |
+| Application or theme | Reserve Web space and choose the layout using the reported logical edge and measured inset. |
+| Native UI | Check native rail availability independently and project eligible source controls. |
+
+```sh
+npm install @erkamyaman/capacitor-foldable
+npx cap sync ios
+```
+
+### With the theme's Ionic rail layout
+
+After loading [the rail stylesheet](../../theme-ios27/docs/vertical-bars-layout.md) and mounting `ion-app`, start the runtime once and connect device placement:
+
+```ts
+import { Foldable, type BarPlacement } from '@erkamyaman/capacitor-foldable';
+import { setVerticalBarsLayout } from '@rdlabo/ionic-theme-ios27';
+import { enableNativeUI } from '@rdlabo/capacitor-native-ui';
+
+const rail = await enableNativeUI({ controls: 'verticalOnly' });
+const applyPlacement = ({ verticalBarEdge, inset }: BarPlacement) => setVerticalBarsLayout({ edge: verticalBarEdge, inset });
+const listener = await Foldable.addListener('barPlacementChange', applyPlacement);
+applyPlacement(await Foldable.getBarPlacement());
+```
+
+If a runtime is already active, keep it and add only the placement subscription. `setVerticalBarsLayout` resolves logical edges for RTL; a null edge restores the ordinary Web layout. Use the measured inset instead of a fixed width. Keep the theme's rail stylesheet; Foldable's `ionic-tabs.css` is not needed for this integration.
+
+When the application owner is disposed, remove its subscription and destroy the runtime it owns:
+
+```ts
+await listener.remove();
+await rail.destroy();
+```
+
+### With application-owned CSS or without Ionic
+
+Use the same `getBarPlacement()` / `barPlacementChange` subscription, but apply the edge and inset to your own CSS instead of calling `setVerticalBarsLayout`. A null edge means your layout should restore its ordinary spacing. For Ionic, retain the rail layout classes described above; for plain HTML, use the `/core` entry and [source placement and an optional Web rail host](framework-independent.md#optional-web-rail). Neither Foldable nor Native UI supplies the application's CSS.
+
+Keep Web layout updates separate from native availability: do not pass Foldable's edge through the deprecated `nativeEdge` option. Native UI reads that capability itself. A browser layout preview does not require Foldable.
+
+For hinge-driven content layout, read `getFoldState()` and subscribe to `foldStateChange` separately. Hinge posture does not select controls or enable the rail; see the theme's [posture subscription and split-pane example](../../theme-ios27/docs/iphone-duo.md#read-the-device-layout).
+
## Native rendering
On supported iOS versions, adding `.ios-theme-vertical-bars` changes only controls that the system relocates into the physical side rail. Native UI presents eligible tabs, back navigation, menu buttons, and toolbar actions through a SwiftUI `TabView` and toolbar once the class is applied. When the OS reports a rail edge — on iPhone Duo linked against iOS 27.1 or later — it must agree with the applied placement; a disagreeing report keeps the rail on the Web. Without a device-reported edge, projection retains its Web fallback. A placement preference cannot enable an unsupported native rail. SwiftUI owns their adaptive placement and Liquid Glass appearance; Ionic remains the source of labels, icons, selected/disabled state, routing, form submission, and click handlers.
@@ -135,11 +182,14 @@ This does not disable tab projection or tab rail CSS.
Use `data-vertical-bars="true|false"` on the element whose behavior you want to
override. The attribute is **not inherited**.
-| Element | Meaning of its attribute | Fallback when absent or invalid |
-| -------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------ |
-| `ion-button` / `ion-menu-button` | Select or exclude this action. Explicit `true` bypasses the icon-only selection rule. | `button` and the automatic shape rules |
-| `ion-buttons` | Enable or disable grouping of already-selected actions. Never selects children. | `buttonGroup` and the existing grouping rules |
-| `ion-back-button` | Select or exclude this back button. | `backButton` and the supported back-button rules |
+| Element | `data-vertical-bars="true"` | `data-vertical-bars="false"` | Default when absent or invalid |
+| -------------------------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------- | -------------------------------------------------- |
+| `ion-button` / `ion-menu-button` | Select the action, including supported text actions without an icon-only slot. | Exclude the action from the rail. | `controls.button` and automatic shape rules. |
+| `ion-buttons` | Allow grouping of already-selected actions. | Project selected actions individually. | `controls.buttonGroup` and grouping eligibility. |
+| `ion-back-button` | Select back navigation independently of its group. | Exclude this back button from the rail. | `controls.backButton` and back-button eligibility. |
+| `ion-tab-bar` | Select eligible rail tabs and their native search presentation. | Disable native rail tab selection; existing Web layout remains. | `controls.tabs` and tab-bar eligibility. |
+
+The group's attribute never selects or excludes its children. All opt-ins remain subject to structural exclusions and rail availability.
Empty or unknown attribute values are ignored. To disable automatic action
selection and explicitly select a text button:
@@ -206,9 +256,17 @@ remain opt-outs; those are distinct from the grouping setting.
### Placement versus appearance
-`data-vertical-bars` controls selection or grouping as described above.
-`data-projection="source|system"` controls native appearance and follows its own
-button-then-group inheritance rules. Neither appearance value selects an action.
+| Setting | Values | Responsibility | Applies to |
+| -------------------------- | ----------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
+| `data-native-ui` | `button`, `back-button`, `disabled` | Declare an explicit source or exclude a subtree. | [Explicit source elements](framework-independent.md#2-mark-source-controls); `disabled` also applies to ancestors. |
+| `data-native-ui-placement` | `element`, `rail` | Choose the original element position or request the rail. | Explicit sources; `element` also keeps automatic Ionic controls at their original position. |
+| `data-vertical-bars` | `true`, `false` | Override rail selection or grouping defaults. | Ionic actions and groups listed above. |
+| `data-horizontal-bars` | `true`, `false` | Override ordinary-placement selection or grouping defaults. | [Ordinary Ionic controls](native-ui.md#toolbar-selection-and-grouping). |
+| `data-projection` | `system`, `source` | Choose native appearance without selecting an action. | Native vertical `ion-button` and `ion-menu-button`; inherited from the nearest `ion-buttons`. |
+
+Selection attributes are not inherited. Appearance follows the button-then-group priority described below. An attribute never makes unsupported markup or a missing native rail available.
+
+The rail shares one light/dark appearance across projected controls. See [Shared Vertical Bars appearance](appearance.md#shared-vertical-bars-appearance) when configuring your Web theme.
### Choose button appearance
diff --git a/packages/theme-ios27/docs/adaptive-themes.md b/packages/theme-ios27/docs/adaptive-themes.md
index 89ae951a..efc074f7 100644
--- a/packages/theme-ios27/docs/adaptive-themes.md
+++ b/packages/theme-ios27/docs/adaptive-themes.md
@@ -42,7 +42,17 @@ If compilation reports `Can't find stylesheet to import.` for a `meta.load-css()
Alternatively, use a relative path from the Sass file containing `meta.load-css()` to the installed package, such as `../node_modules/@rdlabo/ionic-theme-ios27/src/styles/default-variables` from `src/styles.scss`. Adjust the `../` prefix for your file's location, and apply the same change to each theme import.
-These checks select styles by browser features, not by iOS version. Browsers without either feature retain Ionic's default iOS appearance. The example uses class-based dark mode: also load [Ionic's matching dark palette](https://ionicframework.com/docs/theming/dark-mode). For system or always-dark mode, replace both `-dark-class` imports with the matching variant. The `md-remove-ios-class-effect` styles prevent iOS-specific classes from affecting Material Design mode.
+### Which styles are selected?
+
+The checks select browser capabilities, not iOS versions:
+
+| Browser capability in Ionic `ios` mode | Result |
+| ---------------------------------------------------- | ------------------------------- |
+| `overflow-anchor: auto` | iOS 27 theme. |
+| `text-wrap: pretty`, without `overflow-anchor: auto` | iOS 26 theme. |
+| Neither feature | Ionic's default iOS appearance. |
+
+The example uses class-based dark mode: also load [Ionic's matching dark palette](https://ionicframework.com/docs/theming/dark-mode). For system or always-dark mode, replace both `-dark-class` imports with the matching variant; see the [dark-mode pairs](setup.md#choose-one-dark-mode-pair). The `md-remove-ios-class-effect` styles prevent iOS-specific classes from affecting Material Design mode.
## 3. Configure animations
diff --git a/packages/theme-ios27/docs/animation.md b/packages/theme-ios27/docs/animation.md
index de56ba8a..675b4496 100644
--- a/packages/theme-ios27/docs/animation.md
+++ b/packages/theme-ios27/docs/animation.md
@@ -61,9 +61,25 @@ const effect = supportSeachbarCancelButtonIcon(searchbar);
// effect.destroy();
```
-Register after the Ionic element has initialized (for example, Angular's `ngAfterViewInit`). The helper preserves Ionic's button and event handlers, and `destroy()` restores its text content. It does not apply to MD mode, `searchbar-classic`, `ios-theme-disabled`, or `ios26-disabled` searchbars. The CSS animation follows `animated` and respects `prefers-reduced-motion`. Standard searchbar CSS variables such as `--background`, `--box-shadow`, `--border-radius`, and `--cancel-button-color` remain available.
+### Registration and exclusions
-The clear button shares the 44px glass design and appears beside the input. It uses Ionic's `clearIcon`, `showClearButton`, and `--clear-button-color`; clearing retains input focus, while cancel ends the search. Clear and cancel can be displayed together. Set `clearIcon` and `cancelButtonIcon` to the same icon to give them the same appearance.
+Register after the Ionic element has initialized (for example, Angular's `ngAfterViewInit`).
+
+| Condition | Behavior |
+| ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
+| Supported iOS-mode searchbar | Preserves Ionic's button and event handlers; `destroy()` restores text content. |
+| MD mode, `searchbar-classic`, `ios-theme-disabled` or `ios26-disabled` | Helper does not apply. |
+| `animated` / `prefers-reduced-motion` | Respected by the CSS animation. |
+| CSS customization | `--background`, `--box-shadow`, `--border-radius` and `--cancel-button-color` remain available. |
+
+### Clear and cancel buttons
+
+| Button | Ionic appearance settings | Action |
+| ------ | ------------------------------------------------------ | ----------------------------------- |
+| Clear | `clearIcon`, `showClearButton`, `--clear-button-color` | Clears while retaining input focus. |
+| Cancel | `cancelButtonIcon`, `--cancel-button-color` | Ends the search. |
+
+Both can appear together beside the input. Set `clearIcon` and `cancelButtonIcon` to the same icon for matching appearance.
Both external glass buttons use a 44px resting size and expand while pressed. Reduced motion disables scaling. The clear button appears outside the text field; Ionic supplies the icons.
diff --git a/packages/theme-ios27/docs/eslint.md b/packages/theme-ios27/docs/eslint.md
index ec6968f0..7280deae 100644
--- a/packages/theme-ios27/docs/eslint.md
+++ b/packages/theme-ios27/docs/eslint.md
@@ -2,6 +2,8 @@
title: Keep lists consistent with ESLint
---
+# Keep lists consistent with ESLint
+
Catch missing list groups before they reach the screen. In Ionic Angular apps, `@rdlabo/eslint-plugin-rules` checks the markup used by iOS 27.
## Enable the list check
diff --git a/packages/theme-ios27/docs/features.md b/packages/theme-ios27/docs/features.md
index 19e2076a..97c7c5b3 100644
--- a/packages/theme-ios27/docs/features.md
+++ b/packages/theme-ios27/docs/features.md
@@ -11,9 +11,13 @@ Customize the theme with CSS variables and Sass mixins, or adopt it one componen
To customize the library's default styles to match your design, several CSS variables are provided. See this file for details:
[Default variables](../src/styles/default-variables.scss)
-For menus, `--ios-theme-menu-background-rgb` sets the surface RGB channels (light: `225, 230, 240`; dark: `26, 31, 34`). `--ios-theme-menu-background-opacity` controls opacity and defaults to `0.96`. Both are public customization variables and can be set on `ion-menu`.
+| Variable | Default | Purpose |
+| ------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------- |
+| `--ios-theme-menu-background-rgb` | Light: `225, 230, 240`; dark: `26, 31, 34` | Menu surface RGB channels; set on `ion-menu`. |
+| `--ios-theme-menu-background-opacity` | `0.96` | Menu surface opacity; set on `ion-menu`. |
+| `--ios-theme-button-press-duration` | `380ms` | Duration of the button's `:active` scaling with slight overshoot. |
-Button press scaling follows `:active` with a slight overshoot. Set `--ios-theme-button-press-duration` (default: `380ms`) to customize its duration instead of the shared activated-transition duration variables.
+Use `--ios-theme-button-press-duration` instead of the shared activated-transition duration variables to customize button press scaling.
## Liquid Glass mixin
diff --git a/packages/theme-ios27/docs/iphone-duo-with-original-theme.md b/packages/theme-ios27/docs/iphone-duo-with-original-theme.md
index b054b7dd..77d8b3c7 100644
--- a/packages/theme-ios27/docs/iphone-duo-with-original-theme.md
+++ b/packages/theme-ios27/docs/iphone-duo-with-original-theme.md
@@ -20,7 +20,7 @@ Read [how projection works](../../capacitor-native-ui/README.md#how-projection-w
The rest of this page is for **Ionic apps**. Both Ionic `ios` and `md` modes are supported. Tabs and supported toolbar actions move to the side; content and horizontal controls keep their existing appearance.
-Available as a **preview** feature. APIs and supported behavior may change.
+iPhone Duo support and Vertical Bars are stable starting with version 2.0.0.
## Try it in your existing Ionic app
diff --git a/packages/theme-ios27/docs/iphone-duo.md b/packages/theme-ios27/docs/iphone-duo.md
index 6ce51d94..a59ab94f 100644
--- a/packages/theme-ios27/docs/iphone-duo.md
+++ b/packages/theme-ios27/docs/iphone-duo.md
@@ -1,14 +1,14 @@
---
-title: iPhone Duo support (preview)
+title: iPhone Duo support
---
-# iPhone Duo support (preview)
+# iPhone Duo support
Adapt your Ionic app to iPhone Duo: place navigation and actions in its vertical system rail, and adjust your split pane as the device opens and closes. Existing Ionic markup remains the source of labels, icons, routing, and click handlers.
**New here?** Start with [iPhone Duo with your existing theme](iphone-duo-with-original-theme.md) to preview the side layout in Chrome. This page explains device events, placement and split panes. [Vertical Bars](../../capacitor-native-ui/docs/vertical-bars.md) documents control projection and the runtime API.
-Available as a **preview** feature alongside [Native UI](../../capacitor-native-ui/docs/native-ui.md). APIs and supported behavior may change before stable status. Stable status is planned after the official release of Xcode 27.1. The real system rail and hinge reporting require iOS 27.1 or later and an app built with Xcode 27.1 or newer.
+iPhone Duo support is stable starting with version 2.0.0. The real system rail and hinge reporting require iOS 27.1 or later and an app built with Xcode 27.1 or newer.
Use the theme layout and native plugin independently for that hardware. Neither requires the full iOS 27 appearance:
@@ -17,6 +17,14 @@ Use the theme layout and native plugin independently for that hardware. Neither
Device state belongs to [`@erkamyaman/capacitor-foldable`](https://github.com/erkamyaman/capacitor-foldable). The **application** subscribes to its events and chooses its layout. The theme reserves space and adapts the split pane; Native UI projects eligible controls. The theme does not monitor hinge state or bar placement.
+| Owner | Responsibility |
+| ------------------- | ----------------------------------------------------------------------------------------------------------- |
+| Foldable | Report bar placement and hinge posture, initially and when they change. |
+| Application / theme | Subscribe to device events and apply Web layout through `setVerticalBarsLayout` or `applyFoldStateClasses`. |
+| Native UI | Project eligible controls and determine native rail availability independently. |
+
+For the native package's integration, including application-owned CSS and plain HTML, see [Use with Capacitor Foldable](../../capacitor-native-ui/docs/vertical-bars.md#use-with-capacitor-foldable).
+
## Choose what to adopt
To keep your existing theme and add only the standalone support, follow [iPhone Duo with your existing theme](iphone-duo-with-original-theme.md). This page covers the shared device-layout rules.
@@ -66,9 +74,31 @@ await listener.remove();
Subscribe to changes, then read the current state. The same helper handles initial values and events.
-`applyFoldStateClasses` keeps one of `ios-theme-fold-flat`, `ios-theme-fold-half-opened`, and `ios-theme-fold-closed` on the supplied root, preserving unrelated classes. It also sets `ios-theme-fold-expanded` for a half-opened state or a flat state with hinge geometry. A flat state without geometry (including the Web fallback) and a closed state clear that class. The helper does not subscribe to the plugin or change Ionic's split-pane `when` property.
+### Posture classes
+
+`applyFoldStateClasses` updates the supplied root while preserving unrelated classes:
+
+| Class | Applied when |
+| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
+| `ios-theme-fold-flat` | Flat posture. |
+| `ios-theme-fold-half-opened` | Half-opened posture. |
+| `ios-theme-fold-closed` | Closed posture. |
+| `ios-theme-fold-expanded` | Half-opened, or flat with hinge geometry. Cleared for flat without geometry (including Web fallback) and closed posture. |
+
+Only one of the first three classes is retained. The helper does not subscribe to the plugin or change Ionic's split-pane `when` property.
+
+### Placement values
+
+For rail placement, import `setVerticalBarsLayout` from the theme and pass the reported logical `edge` with the measured `inset`:
+
+| Value | Web layout effect |
+| ------------------ | ---------------------------------------------------- |
+| `edge: 'leading'` | Physical left in LTR; physical right in RTL. |
+| `edge: 'trailing'` | Physical right in LTR; physical left in RTL. |
+| `edge: null` | Restores ordinary layout. |
+| Measured `inset` | Reserves that width; zero clears the explicit width. |
-For rail placement, use `setVerticalBarsLayout` as shown below. Import this Web layout helper from the theme and pass the reported logical `edge` with the measured `inset`. It does not report native availability or activate projection. Leading is the physical left in LTR and the physical right in RTL. A null edge restores the ordinary layout; an inset of zero clears the explicit width. No start/stop monitoring calls are needed.
+The helper does not report native availability or activate projection. No start/stop monitoring calls are needed. See [Project controls into the rail](#project-controls-into-the-rail) for the subscription.
WebView corner radius remains a rendering concern: `configureNativeTransition()` uses the shell's `getWebViewMetrics()` API, independently of `Foldable`.
diff --git a/packages/theme-ios27/docs/migration.md b/packages/theme-ios27/docs/migration.md
index f3f6369d..b75dc932 100644
--- a/packages/theme-ios27/docs/migration.md
+++ b/packages/theme-ios27/docs/migration.md
@@ -13,7 +13,7 @@ Theme styles and ordinary theme API imports remain supported. If you previously
| `@rdlabo/ionic-theme-ios27/native` | `@rdlabo/capacitor-native-ui` |
| `@rdlabo/ionic-theme-ios27/vertical-bars` | `@rdlabo/capacitor-native-ui/vertical-bars` |
-Add the native package as a direct application dependency, update imports, rebuild Web assets, and run `npx cap sync ios`. Follow the [Native UI migration guide](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/capacitor-native-ui/docs/migration.md) for the intermediate package rename and old Swift package references. Theme-only apps do not need native startup or plugin setup.
+Add the native package as a direct application dependency, update imports, rebuild Web assets, and run `npx cap sync ios`. Follow the [Native UI migration guide](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/capacitor-native-ui/docs/migration.md) for 1.2.1 startup settings and native project changes. Theme-only apps do not need native startup or plugin setup.
## From the iOS 26 theme
@@ -121,9 +121,12 @@ Change the animation import from `@rdlabo/ionic-theme-ios26` to `@rdlabo/ionic-t
For the iOS 27 branch or an iOS 27-only app, use `@rdlabo/ionic-theme-ios27`. Its stylesheets are `ionic-theme-ios27.scss` or `ionic-theme-ios27.css`, including the `-dark-always`, `-dark-system`, and `-dark-class` variants. Keep the iOS 26 stylesheet names in the iOS 26 branch of an adaptive setup.
-Use the version-independent `--ios-theme-*` CSS variables. The corresponding `--ios26-*` variables remain supported as deprecated fallbacks. When both are set, the new name takes precedence. For example, use `--ios-theme-content-box-shadow-rgb` instead of `--ios26-content-box-shadow-rgb`.
+| Customization | Current name | Legacy behavior |
+| ------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
+| CSS variables | `--ios-theme-*`, for example `--ios-theme-content-box-shadow-rgb` | Corresponding `--ios26-*` names remain deprecated fallbacks. Current names win when both are set. |
+| Theme opt-out class | `ios-theme-disabled` | `ios26-disabled` remains a deprecated alias; migrate existing markup when convenient. |
-For opting out of the theme, use the version-independent `ios-theme-disabled` class. The `ios26-disabled` class remains supported as a deprecated alias; migrate existing markup when convenient.
+Use the version-independent names for new customizations.
See [Special markup and classes](special-markup.md) and [Default variables](../src/styles/default-variables.scss) for the current names.
diff --git a/packages/theme-ios27/docs/setup.md b/packages/theme-ios27/docs/setup.md
index 91fecafa..5d316979 100644
--- a/packages/theme-ios27/docs/setup.md
+++ b/packages/theme-ios27/docs/setup.md
@@ -24,7 +24,17 @@ Add these imports to your global stylesheet after Ionic's base styles. Replace a
@import '@rdlabo/ionic-theme-ios27/dist/css/ionic-theme-ios27-dark-system.css';
```
-For a manual dark-mode toggle, use Ionic's `dark.class.css` and the theme's `ionic-theme-ios27-dark-class.css`, then toggle `ion-palette-dark` on the document root. For always-dark styling, use `dark.always.css` and `ionic-theme-ios27-dark-always.css`. Choose one matching pair.
+### Choose one dark-mode pair
+
+Use one matching Ionic palette and theme variant. Paths below are relative to `@ionic/core/css/palettes/` and `@rdlabo/ionic-theme-ios27/dist/css/` respectively.
+
+| Mode | Ionic palette | Theme stylesheet | Activation |
+| ---------------------- | ----------------- | ----------------------------------- | ----------------------------------------------- |
+| System (example above) | `dark.system.css` | `ionic-theme-ios27-dark-system.css` | System appearance. |
+| Manual | `dark.class.css` | `ionic-theme-ios27-dark-class.css` | Toggle `ion-palette-dark` on the document root. |
+| Always dark | `dark.always.css` | `ionic-theme-ios27-dark-always.css` | Always active. |
+
+If you also enable Native UI, these styles provide its light/dark appearance marker. Keep the Ionic palette and theme variant paired so Web and native controls agree. See [native appearance inheritance](../../capacitor-native-ui/docs/appearance.md) for the resolution order and shared rail behavior.
## 3. Configure Ionic animations
diff --git a/packages/theme-ios27/docs/special-markup.md b/packages/theme-ios27/docs/special-markup.md
index 85bc2cf5..a44b73ed 100644
--- a/packages/theme-ios27/docs/special-markup.md
+++ b/packages/theme-ios27/docs/special-markup.md
@@ -28,15 +28,38 @@ buttons: [
];
```
-Buttons with no role or `default` keep the normal text color. `cancel` retains Ionic's cancellation behavior, `selected` remains a selection state, and `destructive` uses `--ios-theme-destructive-color`. An existing `confirm` role is not treated as preferred. Use `preferred` for the recommended action, not simply any action that confirms a choice.
+Choose the role by action semantics:
+
+| Role | Appearance and behavior |
+| -------------------- | ------------------------------------------------------------------------------------------------- |
+| `preferred` | Filled primary action; dismissal reports `preferred`. Does not automatically select or invoke it. |
+| No role or `default` | Normal text color. |
+| `cancel` | Retains Ionic's cancellation behavior. |
+| `selected` | Retains selection state. |
+| `destructive` | Uses `--ios-theme-destructive-color`. |
+| `confirm` | Not treated as preferred. Use `preferred` for a recommended action, not simply any confirmation. |
## Floating iPad sheets
-Set `expandToScroll: false` on a sheet modal to use floating lower corners and a 20px bottom gap on iPad. Ionic then sizes the visible page at each breakpoint, so the theme can style it with CSS alone. Content scrolls within the current breakpoint; dragging the handle still resizes the sheet. With the default `expandToScroll: true`, the sheet keeps Ionic's bottom-attached layout and scroll-to-expand behavior.
+Choose the sheet's scrolling behavior with `expandToScroll`:
+
+| Setting | iPad appearance | Scrolling and resizing |
+| ---------------- | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `false` | Floating lower corners and a 20px bottom gap. | Content scrolls within the current breakpoint; dragging the handle resizes the sheet. Ionic sizes the visible page at each breakpoint so CSS can style it. |
+| `true` (default) | Ionic's bottom-attached layout. | Scroll-to-expand behavior. |
## Tab bar position
-Add one of `tab-bar-position-start`, `tab-bar-position-center`, or `tab-bar-position-end` to an iOS `ion-tab-bar` to position the whole bar within its safe area. These classes work with both `slot="top"` and `slot="bottom"` and preserve the bar's width and press animation. Start and end follow the text direction (reversed in RTL). Without a class, the existing placement is unchanged.
+Choose a class on an iOS `ion-tab-bar` to position the whole bar within its safe area:
+
+| Class | Placement |
+| ------------------------- | ------------------------------- |
+| `tab-bar-position-start` | Start of the reading direction. |
+| `tab-bar-position-center` | Center. |
+| `tab-bar-position-end` | End of the reading direction. |
+| None | Existing placement unchanged. |
+
+All three classes support `slot="top"` and `slot="bottom"`, preserve width and press animation, and resolve start/end in RTL.
```html
@@ -47,9 +70,9 @@ Add one of `tab-bar-position-start`, `tab-bar-position-center`, or `tab-bar-posi
These classes do not reposition a separate `ion-fab`; leave room for it when choosing the bar's position.
-## Support iPhone Duo (preview)
+## Support iPhone Duo
-iPhone Duo support, including standalone vertical bars, is available as a **preview** alongside Native UI. Its APIs and supported behavior may change.
+iPhone Duo support, including standalone Vertical Bars, is stable starting with version 2.0.0.
iPhone Duo support — the vertical system rail, hinge posture, and the posture-driven split-pane layout — is independent of the iOS 27 theme and the full Native UI. See [iPhone Duo support](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/packages/theme-ios27/docs/iphone-duo.md) for the complete setup, including device-layout monitoring without a projection runtime.
@@ -115,7 +138,15 @@ For colored segments, use Ionic's `color` property (for example, `color="primary
Add `.segment-expand` when segment buttons should divide the available width evenly. The class also changes the Liquid Glass effect sizing when `registerSegmentEffect` is used.
-Segments use a 32px minimum height in content and a 48px minimum height inside `ion-toolbar`. `.segment-expand` keeps the compact 32px layout in a toolbar. Compact segments retain Ionic's flat background and indicator colors; only the regular toolbar variant has a glass container and scales its outer container while pressed. Content and expanded segments keep their outer bounds. The optional moving glass lens is independent of the container's background.
+### Segment size and press behavior
+
+| Placement | Minimum height | Container behavior |
+| ----------------------------------------- | -------------- | ---------------------------------------------------------------- |
+| In content | 32px | Compact, flat background and indicator; outer bounds stay fixed. |
+| Inside `ion-toolbar` | 48px | Glass container; outer container scales while pressed. |
+| `.segment-expand`, including in a toolbar | 32px | Compact, flat background and indicator; outer bounds stay fixed. |
+
+The optional moving glass lens is independent of the container's background.
```html preview
diff --git a/packages/theme-ios27/docs/vertical-bars-layout.md b/packages/theme-ios27/docs/vertical-bars-layout.md
index 6c69c9a0..ef025799 100644
--- a/packages/theme-ios27/docs/vertical-bars-layout.md
+++ b/packages/theme-ios27/docs/vertical-bars-layout.md
@@ -22,11 +22,31 @@ Add `.ios-theme-vertical-bars` to `ion-app` to reserve the rail region on the ph
The classes are physical — `-left` always means the physical left edge — because CSS and the native renderer work in physical coordinates. `setVerticalBarsLayout` applies the logical `verticalBarEdge` reported by the device plugin and resolves it through the document's direction, so an RTL app does not need its own conversion.
-For Chrome development, no native plugin is needed — the class alone reserves `80px` to simulate iPhone Duo. When `setVerticalBarsLayout` receives `{ edge, inset }`, the inset replaces the fallback width, even when it is less than `80px`. Override `--ios-theme-vertical-bars-safe-area-left` or `--ios-theme-vertical-bars-safe-area-right` when simulating a different layout.
+### Rail width
-This keeps routers and component backgrounds full-viewport. `ion-content` moves its scroll foreground, `ion-toolbar` moves its container foreground, and `ion-fab` adjusts only when placed beside the system UI. The corresponding Ionic safe-area variable is reset inside those foreground components so descendants do not add the inset again.
+| Source | Reserved width |
+| ---------------------------------------- | --------------------------------------------------------------------------------------------------- |
+| Classes alone, including Chrome previews | `80px`; no native plugin required. |
+| `setVerticalBarsLayout({ edge, inset })` | The supplied inset, even below `80px`. |
+| Custom CSS preview | Override `--ios-theme-vertical-bars-safe-area-left` or `--ios-theme-vertical-bars-safe-area-right`. |
-`ion-modal` applies the same foreground correction when its visible dialog spans the viewport width. With the Vertical Control Area runtime enabled, the topmost full-width modal also projects eligible toolbar buttons into its own rail; centered dialogs keep their toolbar buttons and receive no page-rail inset. This includes full-width sheet modals: their rail follows the visible sheet bounds as the breakpoint changes. Eligibility follows the visible dialog width, not the hinge posture or modal type. `ion-menu` and `ion-popover` are handled as separate surfaces: their internal foreground components do not receive the main-page conversion and retain Ionic's standard safe-area handling. A menu presented beside the system UI keeps Ionic's full-viewport animation host and offsets only its visible container by the corresponding inset; a menu from the opposite side is unchanged. Left and right remain physical coordinates in RTL, while Ionic's `side="start"` and `side="end"` values remain logical.
+### Content and overlay layout
+
+| Surface | Layout with the rail stylesheet |
+| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Routers and component backgrounds | Remain full-viewport. |
+| `ion-content` | Insets its scroll foreground. |
+| `ion-toolbar` | Insets its container foreground. |
+| `ion-fab` | Adjusts only when placed beside the system UI. |
+| Full-width `ion-modal`, including sheets | Applies the foreground inset. With the runtime enabled, the topmost modal projects eligible toolbar controls into its own rail; sheet rails follow visible bounds as the breakpoint changes. |
+| Centered modal | Keeps toolbar buttons and receives no page-rail inset. |
+| `ion-menu` / `ion-popover` | Internal foreground components retain Ionic's standard safe-area handling, without the main-page conversion. |
+| Menu beside the system UI | Keeps its full-viewport animation host; offsets only the visible container by the inset. |
+| Menu on the opposite side | Unchanged. |
+
+The corresponding Ionic safe-area variable is reset inside inset foreground components so descendants do not add the inset again.
+
+Modal eligibility follows the visible dialog width, not hinge posture or modal type. Left and right remain physical coordinates in RTL; Ionic's `side="start"` and `side="end"` remain logical.
The mode is component-mode independent: an app can keep Ionic `mode: 'md'` on iOS and still enable Vertical Bars. No component needs `mode="ios"`.
@@ -40,7 +60,7 @@ setVerticalBarsLayout({ edge: 'trailing', inset: 80 });
setVerticalBarsLayout({ edge: null }); // restore ordinary Web layout
```
-The helper operates on Web CSS only and work without Capacitor or Native UI startup. The application supplies device state and owns subscriptions. Starting, disabling or destroying Native UI does not change these layout classes or the reserved inset. Unsupported native rails retain the chosen Web rail; choose `edge: null` explicitly if the application wants a horizontal layout instead.
+The helper operates on Web CSS only and works without Capacitor or Native UI startup. The application supplies device state and owns subscriptions. Starting, disabling or destroying Native UI does not change these layout classes or the reserved inset. Unsupported native rails retain the chosen Web rail; choose `edge: null` explicitly if the application wants a horizontal layout instead.
## Migration