From b12e660349cd2b5225cee190682269065aa8873d Mon Sep 17 00:00:00 2001 From: rdlabo Date: Sat, 26 Sep 2026 22:02:57 +0900 Subject: [PATCH 1/2] docs: update Ionic iOS27 to 1.2.0-1 transition adapter --- package-lock.json | 48 +++++++++++++++++-- package.json | 3 +- .../generated/project-catalog.generated.ts | 12 ++--- .../ionic-theme-ios27.en.generated.ts | 42 +++++++++++----- .../ionic-theme-ios27.ja.generated.ts | 44 ++++++++++++----- .../docs/src/ionic-theme-ios27/docs/api.md | 8 +++- .../docs/src/ionic-theme-ios27/docs/ja/api.md | 8 +++- .../src/ionic-theme-ios27/docs/ja/features.md | 2 +- .../docs/ja/iphone-duo-with-original-theme.md | 40 +++++++++++++--- .../ionic-theme-ios27/docs/ja/migration.md | 25 +++++++++- .../docs/ja/native-ui-shell.md | 6 +-- .../src/ionic-theme-ios27/docs/ja/readme.md | 10 ++-- .../docs/ja/using-ion-item-group.md | 2 +- scripts/build-output.test.ts | 29 +++++++++++ scripts/package-repository.test.ts | 6 +-- scripts/project-manifest.ts | 4 +- scripts/site-contract.test.ts | 8 ++-- 17 files changed, 235 insertions(+), 62 deletions(-) diff --git a/package-lock.json b/package-lock.json index aad4b2d..210f918 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,7 @@ "@capacitor-community/stripe-identity": "8.2.1", "@capacitor/docgen": "0.3.1", "@eslint/js": "^10.0.1", + "@ionic/core": "8.8.19", "@rdlabo/capacitor-brotherprint": "8.2.1", "@rdlabo/capacitor-codescanner": "8.0.3", "@rdlabo/capacitor-docgen": "0.4.1", @@ -49,7 +50,7 @@ "@rdlabo/ionic-angular-photo-editor": "22.0.3", "@rdlabo/ionic-angular-scroll-header": "22.0.3", "@rdlabo/ionic-theme-ios26": "9.2.0", - "@rdlabo/ionic-theme-ios27": "1.2.0-0", + "@rdlabo/ionic-theme-ios27": "1.2.0-1", "@rdlabo/ionic-theme-md3": "9.1.0", "@rdlabo/ngx-cdk-scroll-strategies": "22.0.3", "@rdlabo/workers-hono-kit": "0.12.2", @@ -3438,6 +3439,45 @@ } } }, + "node_modules/@ionic/core": { + "version": "8.8.19", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.19.tgz", + "integrity": "sha512-eAAKZi/qR0U44OxH3MXAyoyLxTl1AeQCoOWvGdzywKbpl/JTVlGnfZJCiJGTmXszQlRsNXsl7fMDOvpsOtf6sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@stencil/core": "4.43.5", + "ionicons": "^8.0.13", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">= 16" + } + }, + "node_modules/@ionic/core/node_modules/@stencil/core": { + "version": "4.43.5", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.43.5.tgz", + "integrity": "sha512-cgWD+GeuvJpTe1WQn40p02+BJ2j0j1YJ17GdkF2qKIQ23s2e3Zivq5yISXS3dcuV6oUJFN93jprdk+nk/sq99Q==", + "dev": true, + "license": "MIT", + "bin": { + "stencil": "bin/stencil" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.10.0" + }, + "optionalDependencies": { + "@rollup/rollup-darwin-arm64": "4.44.0", + "@rollup/rollup-darwin-x64": "4.44.0", + "@rollup/rollup-linux-arm64-gnu": "4.44.0", + "@rollup/rollup-linux-arm64-musl": "4.44.0", + "@rollup/rollup-linux-x64-gnu": "4.44.0", + "@rollup/rollup-linux-x64-musl": "4.44.0", + "@rollup/rollup-win32-arm64-msvc": "4.44.0", + "@rollup/rollup-win32-x64-msvc": "4.44.0" + } + }, "node_modules/@ionic/utils-terminal": { "version": "2.3.5", "resolved": "https://registry.npmjs.org/@ionic/utils-terminal/-/utils-terminal-2.3.5.tgz", @@ -5304,9 +5344,9 @@ } }, "node_modules/@rdlabo/ionic-theme-ios27": { - "version": "1.2.0-0", - "resolved": "https://registry.npmjs.org/@rdlabo/ionic-theme-ios27/-/ionic-theme-ios27-1.2.0-0.tgz", - "integrity": "sha512-XTa7poOyWKaox7TUvD9iUBhvzbiEjMOy9nQs//ZwjFCoRkCtsO6K1D2VxZcuLtx6XHPLNaHvid6jDhs3/19VAw==", + "version": "1.2.0-1", + "resolved": "https://registry.npmjs.org/@rdlabo/ionic-theme-ios27/-/ionic-theme-ios27-1.2.0-1.tgz", + "integrity": "sha512-TVW6mmmvQ948cxPPJ/4hEnTiLq+EGRJnlG24uL6JsXYQOjVm8DA47JV9iMZjVymYdzvc25KeNX6BeLIzQoutXA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index d4d7f9d..593b07f 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "@capacitor-community/stripe-identity": "8.2.1", "@capacitor/docgen": "0.3.1", "@eslint/js": "^10.0.1", + "@ionic/core": "8.8.19", "@rdlabo/capacitor-brotherprint": "8.2.1", "@rdlabo/capacitor-codescanner": "8.0.3", "@rdlabo/capacitor-docgen": "0.4.1", @@ -83,7 +84,7 @@ "@rdlabo/ionic-angular-photo-editor": "22.0.3", "@rdlabo/ionic-angular-scroll-header": "22.0.3", "@rdlabo/ionic-theme-ios26": "9.2.0", - "@rdlabo/ionic-theme-ios27": "1.2.0-0", + "@rdlabo/ionic-theme-ios27": "1.2.0-1", "@rdlabo/ionic-theme-md3": "9.1.0", "@rdlabo/ngx-cdk-scroll-strategies": "22.0.3", "@rdlabo/workers-hono-kit": "0.12.2", diff --git a/projects/docs/src/app/generated/project-catalog.generated.ts b/projects/docs/src/app/generated/project-catalog.generated.ts index 6dbbdc6..3418a40 100644 --- a/projects/docs/src/app/generated/project-catalog.generated.ts +++ b/projects/docs/src/app/generated/project-catalog.generated.ts @@ -484,10 +484,10 @@ export const PROJECTS_EN = [ "packageName": "@rdlabo/ionic-theme-ios27", "repositoryUrl": "https://github.com/rdlabo-dev/ionic-theme-ios27", "demoUrl": "https://ionic-theme-ios27.rdlabo.dev/", - "releaseNotesUrl": "https://github.com/rdlabo-dev/ionic-theme-ios27/releases/tag/ios27-v1.2.0-0", + "releaseNotesUrl": "https://github.com/rdlabo-dev/ionic-theme-ios27/releases/tag/ios27-v1.2.0-1", "category": "frontend-tools", "icon": "theme", - "version": "1.2.0-0", + "version": "1.2.0-1", "description": "Stable iOS 27 styling and motion for Ionic, with an optional experimental Native UI Shell.", "headline": "Bring iOS 27 design to Ionic apps", "overview": "Bring iOS 27 Liquid Glass and motion to Ionic components. Install alongside the iOS 26 theme to select styles by browser capability; Native UI Shell remains experimental.", @@ -509,7 +509,7 @@ export const PROJECTS_EN = [ "description": "Adopt dark-mode styles and migrate component by component when needed." } ], - "overviewHtml": "

\n \"iOS\n \"iOS\n \"iOS\n

", + "overviewHtml": "

\n \"iOS\n \"iOS\n \"iOS\n

", "path": "/projects/ionic-theme-ios27", "pages": [ { @@ -2660,10 +2660,10 @@ export const PROJECTS_JA = [ "packageName": "@rdlabo/ionic-theme-ios27", "repositoryUrl": "https://github.com/rdlabo-dev/ionic-theme-ios27", "demoUrl": "https://ionic-theme-ios27.rdlabo.dev/", - "releaseNotesUrl": "https://github.com/rdlabo-dev/ionic-theme-ios27/releases/tag/ios27-v1.2.0-0", + "releaseNotesUrl": "https://github.com/rdlabo-dev/ionic-theme-ios27/releases/tag/ios27-v1.2.0-1", "category": "frontend-tools", "icon": "theme", - "version": "1.2.0-0", + "version": "1.2.0-1", "description": "Ionic向けの安定版iOS 27スタイルとアニメーション。実験的なNative UI Shellも選択できます。", "headline": "IonicアプリにiOS 27デザインを取り入れる", "overview": "IonicコンポーネントにiOS 27のLiquid Glassとアニメーションを適用します。iOS 26テーマと併用するとブラウザ機能に応じてスタイルを切り替えられます。Native UI Shellは実験的機能です。", @@ -2685,7 +2685,7 @@ export const PROJECTS_JA = [ "description": "必要に応じてダークモードスタイルを導入し、コンポーネント単位で移行できます。" } ], - "overviewHtml": "

\n \"iOS\n \"iOS\n \"iOS\n

", + "overviewHtml": "

\n \"iOS\n \"iOS\n \"iOS\n

", "path": "/projects/ionic-theme-ios27", "pages": [ { diff --git a/projects/docs/src/app/generated/projects/ionic-theme-ios27.en.generated.ts b/projects/docs/src/app/generated/projects/ionic-theme-ios27.en.generated.ts index 0b90cce..4bf1b27 100644 --- a/projects/docs/src/app/generated/projects/ionic-theme-ios27.en.generated.ts +++ b/projects/docs/src/app/generated/projects/ionic-theme-ios27.en.generated.ts @@ -7,10 +7,10 @@ export const PROJECT = { "packageName": "@rdlabo/ionic-theme-ios27", "repositoryUrl": "https://github.com/rdlabo-dev/ionic-theme-ios27", "demoUrl": "https://ionic-theme-ios27.rdlabo.dev/", - "releaseNotesUrl": "https://github.com/rdlabo-dev/ionic-theme-ios27/releases/tag/ios27-v1.2.0-0", + "releaseNotesUrl": "https://github.com/rdlabo-dev/ionic-theme-ios27/releases/tag/ios27-v1.2.0-1", "category": "frontend-tools", "icon": "theme", - "version": "1.2.0-0", + "version": "1.2.0-1", "description": "Stable iOS 27 styling and motion for Ionic, with an optional experimental Native UI Shell.", "headline": "Bring iOS 27 design to Ionic apps", "overview": "Bring iOS 27 Liquid Glass and motion to Ionic components. Install alongside the iOS 26 theme to select styles by browser capability; Native UI Shell remains experimental.", @@ -32,7 +32,7 @@ export const PROJECT = { "description": "Adopt dark-mode styles and migrate component by component when needed." } ], - "overviewHtml": "

\n \"iOS\n \"iOS\n \"iOS\n

", + "overviewHtml": "

\n \"iOS\n \"iOS\n \"iOS\n

", "path": "/projects/ionic-theme-ios27", "pages": [ { @@ -43,7 +43,7 @@ export const PROJECT = { "file": "readme.md", "section": "Quickstart", "path": "/projects/ionic-theme-ios27/docs/readme", - "html": "

A theme for Ionic apps that brings iOS 27 Liquid Glass and motion to the Web. Capacitor iOS apps can also opt into an experimental Native UI Shell for supported controls.

\n

Ionic 9 demo · Ionic 8 demo · Documentation

\n

Features

\n

Bring the iOS 27 look to Ionic

\n

Give familiar Ionic screens the iOS 27 visual language: Liquid Glass, styled toolbars and tabs, lists, buttons, search, overlays, page transitions, and coordinated light and dark appearances. See the result in the Ionic 9 demo and Ionic 8 demo.

\n

Project your Ionic UI into Native UI

\n

On Capacitor iOS, the optional, experimental Native UI Shell reads supported fixed controls from your existing Ionic markup. It projects their text, resolved ion-icon artwork or supported static SVGs, and selection state into UIKit controls with system Liquid Glass. Changes and native actions flow through the original Ionic components, so the Web and native presentations share one UI definition. Page content and routing stay in the WebView; unsupported layouts keep their Web presentation.

\n

Tab drag on iOS 27: The same Library screen with Native UI Shell off (Web) and on (UIKit). Both frames were captured while dragging the selected tab; the lower panels enlarge the glass around the tab bar.

\n

\"Native

\n

Follow the user's device

\n

Pair the iOS 26 and iOS 27 themes so supported Safari versions can present the design of each generation: the iOS 26 look for iOS 26 users and the iOS 27 look for iOS 27 users. The default setup uses browser feature checks to select the corresponding styles; it does not read the iOS version. When both packages are installed, keep the page transition on the iOS 27 animation. On even earlier iOS versions, Ionic's default iOS appearance remains when Safari supports neither feature. In a Capacitor iOS app, Native UI Shell's UIKit material follows the installed iOS version.

\n

Get started

\n

Install both themes in an existing Ionic 8 or 9 app (@ionic/core 8.8.1 or later):

\n
npm install @rdlabo/ionic-theme-ios26 @rdlabo/ionic-theme-ios27\n

In your global Sass stylesheet (for example, src/styles.scss), load the styles by browser capability:

\n
@use 'sass:meta';\n\n@supports (overflow-anchor: auto) {\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/default-variables');\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27');\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27-dark-class');\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/md-remove-ios-class-effect');\n}\n\n@supports (text-wrap: pretty) and (not (overflow-anchor: auto)) {\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/default-variables');\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26');\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class');\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect');\n}\n

If compilation reports Can't find stylesheet to import. for a meta.load-css() call, make node_modules available to Sass. In Angular, add this under the app's build options in angular.json:

\n
\"stylePreprocessorOptions\": {\n  \"includePaths\": [\"node_modules\"]\n}\n

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.

\n

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. 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.

\n

Configure animations

\n

Keep the iOS 27 page transition and popover animations for both styled generations. Resolve these options before Ionic initializes. For Angular:

\n
import { isPlatform, provideIonicAngular } from '@ionic/angular/standalone'; // Ionic 8\nimport { iosTransitionAnimation, popoverEnterAnimation, popoverLeaveAnimation } from '@rdlabo/ionic-theme-ios27';\n\nfunction loadIOSAnimations() {\n  if (typeof CSS === 'undefined') return {};\n  if (!CSS.supports('overflow-anchor: auto') && !CSS.supports('text-wrap: pretty')) return {};\n\n  return {\n    navAnimation: iosTransitionAnimation,\n    popoverEnter: popoverEnterAnimation,\n    popoverLeave: popoverLeaveAnimation,\n  };\n}\n\nprovideIonicAngular(isPlatform('ios') ? loadIOSAnimations() : {});\n

For Ionic 9 Angular, import isPlatform and provideIonicAngular from @ionic/angular. React and Vue can pass the same options to setupIonicReact or IonicVue during initialization. In server-rendered apps, run the selection during browser initialization.

\n

The page-transition radius defaults to 0. Native apps can update it after measuring the WebView:

\n
import { setConfig } from '@rdlabo/ionic-theme-ios27';\n\nsetConfig({ radius });\n

Check the theme

\n

Test on iOS. When previewing on desktop, set Ionic mode to ios in your existing framework initialization config (for example mode: 'ios').

\n

Use this markup to preview the inset grouped list look. For the list structure the theme expects, see Using ion-item-group.

\n
<ion-list mode=\"ios\" inset=\"true\">\n  <ion-item-group>\n    <ion-item><ion-label>Notifications</ion-label></ion-item>\n    <ion-item><ion-label>Appearance</ion-label></ion-item>\n  </ion-item-group>\n</ion-list>\n

Optional setups

\n

Support iPhone Duo without the iOS 27 theme (experimental)

\n

Keep your existing Ionic theme and move tabs and supported toolbar actions into a vertical side area. Start in Chrome with one stylesheet, an app class, and enableVerticalControlArea(); then connect the layout to iPhone Duo device events for the system rail and hinge posture.

\n

Follow iPhone Duo with your existing theme for the browser preview and iOS setup. For shared layout rules and APIs, see iPhone Duo support. Available in 1.2.0-0 as an experimental feature; APIs and supported behavior may change.

\n

Use only the iOS 27 theme

\n

Install only @rdlabo/ionic-theme-ios27 and import its styles unconditionally in your global stylesheet:

\n
@import '@rdlabo/ionic-theme-ios27/dist/css/default-variables.css';\n@import '@rdlabo/ionic-theme-ios27/dist/css/ionic-theme-ios27.css';\n@import '@rdlabo/ionic-theme-ios27/dist/css/md-remove-ios-class-effect.css';\n@import '@rdlabo/ionic-theme-ios27/dist/css/ionic-theme-ios27-dark-class.css';\n

The last import uses class-based dark mode; choose the -dark-system or -dark-always variant and matching Ionic palette for another mode. Configure the iOS 27 animations with isPlatform('ios') as above, without the browser feature checks.

\n

Other options

\n\n

Use with the MD3 theme

\n

Install all three themes to use iOS 27 on supported Safari versions, fall back to iOS 26 on the preceding Safari generation, and use MD3 whenever Ionic runs in Material Design mode. All three themes require @ionic/core 8.8.1 or later:

\n
npm install @rdlabo/ionic-theme-ios26 @rdlabo/ionic-theme-ios27 @rdlabo/ionic-theme-md3\n

Keep the two iOS themes behind the same browser feature checks used by the default setup, then load MD3 unconditionally. Using meta.load-css() throughout also keeps the MD3 styles after the conditional iOS styles in the generated CSS:

\n
@use 'sass:meta';\n\n@supports (overflow-anchor: auto) {\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/default-variables');\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27');\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27-dark-class');\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/md-remove-ios-class-effect');\n}\n\n@supports (text-wrap: pretty) and (not (overflow-anchor: auto)) {\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/default-variables');\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26');\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class');\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect');\n}\n\n@include meta.load-css('@rdlabo/ionic-theme-md3/dist/css/default-variables.css');\n@include meta.load-css('@rdlabo/ionic-theme-md3/dist/css/ionic-theme-md3.css');\n

The iOS styles apply only to Ionic's ios mode, while MD3 applies to md mode. The md-remove-ios-class-effect stylesheet in each iOS branch prevents iOS-only utility classes from leaking into MD mode. MD3 already includes its inset-list styles, so do not load either iOS package's optional md-ion-list-inset stylesheet in this configuration.

\n

Load Ionic's matching dark palette too. The example uses class-based dark mode; for system or always-dark mode, select the matching variant for Ionic and both iOS themes.

\n

Keep the iOS 27 transition for both iOS theme generations and select the MD3 transition in Material Design mode. Extend the animation setup above as follows:

\n
import { isPlatform, provideIonicAngular } from '@ionic/angular/standalone'; // Ionic 8\nimport { iosTransitionAnimation, popoverEnterAnimation, popoverLeaveAnimation } from '@rdlabo/ionic-theme-ios27';\nimport { mdTransitionAnimation } from '@rdlabo/ionic-theme-md3';\n\nfunction loadAnimations() {\n  if (!isPlatform('ios')) return { navAnimation: mdTransitionAnimation };\n  if (typeof CSS === 'undefined') return {};\n  if (!CSS.supports('overflow-anchor: auto') && !CSS.supports('text-wrap: pretty')) return {};\n\n  return {\n    navAnimation: iosTransitionAnimation,\n    popoverEnter: popoverEnterAnimation,\n    popoverLeave: popoverLeaveAnimation,\n  };\n}\n\nprovideIonicAngular(loadAnimations());\n

For Ionic 9 Angular, import isPlatform and provideIonicAngular from @ionic/angular. React and Vue can pass the same returned options to setupIonicReact or IonicVue. If Sass cannot resolve a package in meta.load-css(), use the stylePreprocessorOptions.includePaths or relative-path setup described in Get started.

\n

Documentation

\n

Full documentation: Ionic Theme iOS27

\n\n", + "html": "

A theme for Ionic apps that brings iOS 27 Liquid Glass and motion to the Web. Capacitor iOS apps can also opt into an experimental Native UI Shell for supported controls.

\n

Ionic 9 demo · Ionic 8 demo · Documentation

\n

Features

\n

Bring the iOS 27 look to Ionic

\n

Give familiar Ionic screens the iOS 27 visual language: Liquid Glass, styled toolbars and tabs, lists, buttons, search, overlays, page transitions, and coordinated light and dark appearances. See the result in the Ionic 9 demo and Ionic 8 demo.

\n

Project your Ionic UI into Native UI

\n

On Capacitor iOS, the optional, experimental Native UI Shell reads supported fixed controls from your existing Ionic markup. It projects their text, resolved ion-icon artwork or supported static SVGs, and selection state into UIKit controls with system Liquid Glass. Changes and native actions flow through the original Ionic components, so the Web and native presentations share one UI definition. Page content and routing stay in the WebView; unsupported layouts keep their Web presentation.

\n

Tab drag on iOS 27: The same Library screen with Native UI Shell off (Web) and on (UIKit). Both frames were captured while dragging the selected tab; the lower panels enlarge the glass around the tab bar.

\n

\"Native

\n

Follow the user's device

\n

Pair the iOS 26 and iOS 27 themes so supported Safari versions can present the design of each generation: the iOS 26 look for iOS 26 users and the iOS 27 look for iOS 27 users. The default setup uses browser feature checks to select the corresponding styles; it does not read the iOS version. When both packages are installed, keep the page transition on the iOS 27 animation. On even earlier iOS versions, Ionic's default iOS appearance remains when Safari supports neither feature. In a Capacitor iOS app, Native UI Shell's UIKit material follows the installed iOS version.

\n

Get started

\n

Install both themes in an existing Ionic 8 or 9 app (@ionic/core 8.8.1 or later):

\n
npm install @rdlabo/ionic-theme-ios26 @rdlabo/ionic-theme-ios27\n

In your global Sass stylesheet (for example, src/styles.scss), load the styles by browser capability:

\n
@use 'sass:meta';\n\n@supports (overflow-anchor: auto) {\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/default-variables');\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27');\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27-dark-class');\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/md-remove-ios-class-effect');\n}\n\n@supports (text-wrap: pretty) and (not (overflow-anchor: auto)) {\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/default-variables');\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26');\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class');\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect');\n}\n

If compilation reports Can't find stylesheet to import. for a meta.load-css() call, make node_modules available to Sass. In Angular, add this under the app's build options in angular.json:

\n
\"stylePreprocessorOptions\": {\n  \"includePaths\": [\"node_modules\"]\n}\n

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.

\n

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. 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.

\n

Configure animations

\n

Keep the iOS 27 page transition and popover animations for both styled generations. Resolve these options before Ionic initializes. For Angular:

\n
import { isPlatform, provideIonicAngular } from '@ionic/angular/standalone'; // Ionic 8\nimport { iosTransitionAnimation, popoverEnterAnimation, popoverLeaveAnimation } from '@rdlabo/ionic-theme-ios27';\n\nfunction loadIOSAnimations() {\n  if (typeof CSS === 'undefined') return {};\n  if (!CSS.supports('overflow-anchor: auto') && !CSS.supports('text-wrap: pretty')) return {};\n\n  return {\n    navAnimation: iosTransitionAnimation,\n    popoverEnter: popoverEnterAnimation,\n    popoverLeave: popoverLeaveAnimation,\n  };\n}\n\nprovideIonicAngular(isPlatform('ios') ? loadIOSAnimations() : {});\n

For Ionic 9 Angular, import isPlatform and provideIonicAngular from @ionic/angular. React and Vue can pass the same options to setupIonicReact or IonicVue during initialization. In server-rendered apps, run the selection during browser initialization.

\n

The page-transition radius defaults to 0. Native apps can update it after measuring the WebView:

\n
import { setConfig } from '@rdlabo/ionic-theme-ios27';\n\nsetConfig({ radius });\n

Check the theme

\n

Test on iOS. When previewing on desktop, set Ionic mode to ios in your existing framework initialization config (for example mode: 'ios').

\n

Use this markup to preview the inset grouped list look. For the list structure the theme expects, see Using ion-item-group.

\n
<ion-list mode=\"ios\" inset=\"true\">\n  <ion-item-group>\n    <ion-item><ion-label>Notifications</ion-label></ion-item>\n    <ion-item><ion-label>Appearance</ion-label></ion-item>\n  </ion-item-group>\n</ion-list>\n

Optional setups

\n

Support iPhone Duo without the iOS 27 theme (experimental)

\n

Keep your existing Ionic theme and move tabs and supported toolbar actions into a vertical side area. Start in Chrome with one stylesheet, an app class, and enableVerticalControlArea(); then connect the layout to iPhone Duo device events for the system rail and hinge posture.

\n

Follow iPhone Duo with your existing theme for the browser preview and iOS setup. For shared layout rules and APIs, see iPhone Duo support. Available in 1.2.0-0 as an experimental feature; APIs and supported behavior may change.

\n

Use only the iOS 27 theme

\n

Install only @rdlabo/ionic-theme-ios27 and import its styles unconditionally in your global stylesheet:

\n
@import '@rdlabo/ionic-theme-ios27/dist/css/default-variables.css';\n@import '@rdlabo/ionic-theme-ios27/dist/css/ionic-theme-ios27.css';\n@import '@rdlabo/ionic-theme-ios27/dist/css/md-remove-ios-class-effect.css';\n@import '@rdlabo/ionic-theme-ios27/dist/css/ionic-theme-ios27-dark-class.css';\n

The last import uses class-based dark mode; choose the -dark-system or -dark-always variant and matching Ionic palette for another mode. Configure the iOS 27 animations with isPlatform('ios') as above, without the browser feature checks.

\n

Other options

\n\n

Use with the MD3 theme

\n

Install all three themes to use iOS 27 on supported Safari versions, fall back to iOS 26 on the preceding Safari generation, and use MD3 whenever Ionic runs in Material Design mode. All three themes require @ionic/core 8.8.1 or later:

\n
npm install @rdlabo/ionic-theme-ios26 @rdlabo/ionic-theme-ios27 @rdlabo/ionic-theme-md3\n

Keep the two iOS themes behind the same browser feature checks used by the default setup, then load MD3 unconditionally. Using meta.load-css() throughout also keeps the MD3 styles after the conditional iOS styles in the generated CSS:

\n
@use 'sass:meta';\n\n@supports (overflow-anchor: auto) {\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/default-variables');\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27');\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27-dark-class');\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/md-remove-ios-class-effect');\n}\n\n@supports (text-wrap: pretty) and (not (overflow-anchor: auto)) {\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/default-variables');\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26');\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class');\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect');\n}\n\n@include meta.load-css('@rdlabo/ionic-theme-md3/dist/css/default-variables.css');\n@include meta.load-css('@rdlabo/ionic-theme-md3/dist/css/ionic-theme-md3.css');\n

The iOS styles apply only to Ionic's ios mode, while MD3 applies to md mode. The md-remove-ios-class-effect stylesheet in each iOS branch prevents iOS-only utility classes from leaking into MD mode. MD3 already includes its inset-list styles, so do not load either iOS package's optional md-ion-list-inset stylesheet in this configuration.

\n

Load Ionic's matching dark palette too. The example uses class-based dark mode; for system or always-dark mode, select the matching variant for Ionic and both iOS themes.

\n

Keep the iOS 27 transition for both iOS theme generations and select the MD3 transition in Material Design mode. Extend the animation setup above as follows:

\n
import { isPlatform, provideIonicAngular } from '@ionic/angular/standalone'; // Ionic 8\nimport { iosTransitionAnimation, popoverEnterAnimation, popoverLeaveAnimation } from '@rdlabo/ionic-theme-ios27';\nimport { mdTransitionAnimation } from '@rdlabo/ionic-theme-md3';\n\nfunction loadAnimations() {\n  if (!isPlatform('ios')) return { navAnimation: mdTransitionAnimation };\n  if (typeof CSS === 'undefined') return {};\n  if (!CSS.supports('overflow-anchor: auto') && !CSS.supports('text-wrap: pretty')) return {};\n\n  return {\n    navAnimation: iosTransitionAnimation,\n    popoverEnter: popoverEnterAnimation,\n    popoverLeave: popoverLeaveAnimation,\n  };\n}\n\nprovideIonicAngular(loadAnimations());\n

For Ionic 9 Angular, import isPlatform and provideIonicAngular from @ionic/angular. React and Vue can pass the same returned options to setupIonicReact or IonicVue. If Sass cannot resolve a package in meta.load-css(), use the stylePreprocessorOptions.includePaths or relative-path setup described in Get started.

\n

Documentation

\n

Full documentation: Ionic Theme iOS27

\n\n", "headings": [ { "id": "features", @@ -123,7 +123,7 @@ export const PROJECT = { "file": "using-ion-item-group.md", "section": "Guides", "path": "/projects/ionic-theme-ios27/docs/using-ion-item-group", - "html": "

Most Ionic markup works without changes. When an ion-list uses inset=\"true\", wrap its items in ion-item-group and keep ion-list-header outside the group.

\n

The examples use framework-neutral Web Component markup. In React or Vue, use the equivalent component and property syntax.

\n
<ion-list inset=\"true\">\n  <ion-list-header><ion-label>Connections</ion-label></ion-list-header>\n  <ion-item-group>\n    <ion-item>...</ion-item>\n    <ion-item>...</ion-item>\n  </ion-item-group>\n</ion-list>\n

No wrapper is required for lists that do not use inset=\"true\".

\n

Check list structure with ESLint.

\n

Why the wrapper is required

\n

Ionic normally gives ion-list its background, which makes ion-list-header appear inside the same surface as the items. The iOS 27 layout treats the header and item surface separately.

\n

\"Inset

\n

The theme therefore:

\n\n

Sharing the markup with Material Design

\n

@rdlabo/ionic-theme-md3 supports the same grouped markup, so one template can be used for both Ionic modes.

\n

When an application uses this package without @rdlabo/ionic-theme-md3, import the optional stylesheet to apply the same grouped layout in Material mode:

\n
@import '@rdlabo/ionic-theme-ios27/dist/css/md-ion-list-inset.css';\n

For two-line items and section-header groups, see Special markup and classes.

\n", + "html": "

Most Ionic markup works without changes. When an ion-list uses inset=\"true\", wrap its items in ion-item-group and keep ion-list-header outside the group.

\n

The examples use framework-neutral Web Component markup. In React or Vue, use the equivalent component and property syntax.

\n
<ion-list inset=\"true\">\n  <ion-list-header><ion-label>Connections</ion-label></ion-list-header>\n  <ion-item-group>\n    <ion-item>...</ion-item>\n    <ion-item>...</ion-item>\n  </ion-item-group>\n</ion-list>\n

No wrapper is required for lists that do not use inset=\"true\".

\n

Check list structure with ESLint.

\n

Why the wrapper is required

\n

Ionic normally gives ion-list its background, which makes ion-list-header appear inside the same surface as the items. The iOS 27 layout treats the header and item surface separately.

\n

\"Inset

\n

The theme therefore:

\n\n

Sharing the markup with Material Design

\n

@rdlabo/ionic-theme-md3 supports the same grouped markup, so one template can be used for both Ionic modes.

\n

When an application uses this package without @rdlabo/ionic-theme-md3, import the optional stylesheet to apply the same grouped layout in Material mode:

\n
@import '@rdlabo/ionic-theme-ios27/dist/css/md-ion-list-inset.css';\n

For two-line items and section-header groups, see Special markup and classes.

\n", "headings": [ { "id": "why-the-wrapper-is-required", @@ -250,7 +250,7 @@ export const PROJECT = { "file": "native-ui-shell.md", "section": "Guides", "path": "/projects/ionic-theme-ios27/docs/native-ui-shell", - "html": "

Native UI Shell is experimental. Its API and supported controls may change.

\n

Native UI Shell gives an Ionic app native navigation and action controls around its Web content. The optional Capacitor iOS plugin renders supported fixed Ionic controls with UIKit or SwiftUI and the system's Liquid Glass material. Page content, scrolling, application state and routing remain in Ionic's WebView.

\n

Background

\n

Basecamp described this hybrid approach in Hybrid sweet spot: Native navigation, web content (May 8, 2014): keep Web content at the core and use native UI where it improves the experience. The Capacitor 1.0.0 Alpha announcement (February 27, 2018) explicitly included Native UI Shell in its roadmap and linked to that article. Combining native UI with Web content was part of Capacitor's direction from the beginning.

\n

This package revisits that idea for Ionic and Liquid Glass. Existing Ionic markup defines the shell: eligible toolbar controls, tabs, fixed FABs and searchable tabs acquire native presentation. UIKit handles their appearance and interaction; the bridge synchronizes DOM state and returns actions to the original Ionic components. Ionic continues to own the navigation stack, page transitions and application logic. The shell's scope is the supported fixed controls described below.

\n

Enable the shell

\n

After installing the theme CSS described in the README, enable the shell once at application startup:

\n
import { enableNativeUIShell } from '@rdlabo/ionic-theme-ios27/native';\n\nvoid enableNativeUIShell();\n

enableNativeUIShell() also reads the WebView's effective top-left corner radius and applies it to page transitions. To configure only the transition without enabling native controls, call:

\n
import { configureNativeTransition } from '@rdlabo/ionic-theme-ios27/native';\n\nawait configureNativeTransition();\n

Keep the existing navAnimation: iosTransitionAnimation setting. 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 native plugin uses Swift Package Manager (SPM). For an existing CocoaPods app, 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.

\n

This is an opt-in feature. The ordinary package entry point does not import Capacitor, and @capacitor/core is an optional peer dependency. Native sources are still detected and built by Capacitor's sync when this package is installed in a Capacitor project, even if the application does not call enableNativeUIShell().

\n

Native appearance follows the applied class, system or always-dark theme CSS. System theme changes are synchronized while the runtime is active.

\n

Supported markup

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Ionic componentSupported appearance and placementNative rendering
ion-buttonfill=\"default\", standard glass, in a fixed header/footer toolbarGlass UIButton
ion-buttonsFixed toolbar, two or more direct clear ion-button / ion-menu-button children sharing the theme glassOne UIGlassEffect surface with independent native buttons
ion-back-buttonStandard icon and color in a fixed header/footer toolbarGlass UIButton, using the resolved Ionic label/icon
ion-menu-buttonFixed toolbar, inside the theme glass ion-buttonsGlass UIButton; original Ionic menu toggle
ion-tab-barFixed tabs, icon-only/label-only items, one icon per item, dot/text badges, selection and disabled stateUITabBar and UITabBarItem
ion-segmentFixed toolbar, non-scrollable, text or one icon per itemUISegmentedControl
ion-fab / ion-fab-button / ion-fab-listGlass FAB in an ion-content fixed slot; one main button and optional directional listsPersistent glass UIButton per button; one FAB synchronization group
\n

For the ordinary Native UI Shell, only iOS-mode components with the theme variables installed are eligible. Explicitly enabled Vertical Bars is mode-independent as described below. ionic-theme-disabled, 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.

\n

Use ios-theme-shell-disabled to disable only the iOS Native UI Shell while keeping the Web theme. It excludes the element and all its descendants. Adding or removing the class at runtime automatically restores Web rendering or re-evaluates native eligibility.

\n
<ion-toolbar class=\"ios-theme-shell-disabled\">\n  <ion-button>Web glass button</ion-button>\n</ion-toolbar>\n

If a child inside a shared native surface opts out, the entire surface stays on the Web: this includes button groups, tab bars, segments and FAB lists. 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.

\n

Placement is required even when the appearance is glass. In the ordinary Native UI Shell, 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. Overlays, collapsed headers, opted-out controls and departed pages are excluded.

\n

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.

\n

Standalone clear, solid and outline buttons are excluded. A glass ion-buttons group of two or more clear buttons is projected as one surface; its children retain separate actions. Menu buttons can also share that group. A single menu button uses its parent ion-buttons as the glass surface, so no Web glass is left underneath the native button. A menu button outside this theme glass remains on the Web. Mixed fills, unsupported children, or a theme-disabled child keep the group on the Web. Single clear buttons remain on the Web. 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.

\n

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.

\n

While a Web input owns the software keyboard, ordinary controls return to Web rendering and are re-evaluated when it closes. This also covers iPad layouts where the keyboard does not move the visual viewport. A native search field keeps its own native keyboard and search surface.

\n

When an existing control becomes unsupported, its Web source is painted before its native cover is removed. This avoids a blank handoff, but Web/UIKit updates are not atomic and can briefly overlap. This boundary behavior differs from ordinary page navigation, where unchanged shared native tabs are retained.

\n

State and events

\n

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.

\n

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.

\n

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.

\n

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, preserving their colors. Tab SVGs that follow text color use native template rendering, so icons and labels change selection color together without waiting for another bridge image. Explicit multicolor artwork retains its original colors. External references, <use>, animation, embedded HTML/images, SVG text and stylesheets are excluded. Web fonts and arbitrary slot layouts are not reproduced exactly.

\n

The native host accepts input only within native controls. Tab interaction and accessibility are provided by the standard 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.

\n

Searchable tabs

\n

Existing attachTabBarSearchable(tabBar, fabButton, footer) registrations automatically use native search when their bottom tab bar and glass search controls are supported. No new component option, route, native setup or page listener is required. Ordinary tab bars continue using UITabBar; searchable groups use a persistent UITabBarController, UITab / UISearchTab, and UISearchController. The original Capacitor WebView continues to render results and handle navigation.

\n

Search registration does not bypass placement restrictions. Its searchbar and close button must be in fixed footer toolbars. Its trigger must belong to an ion-fab[slot=\"fixed\"] directly inside ion-content, or directly on the existing non-scrolling .ion-page layout. A wrapper inside scrolling content is not a fixed slot.

\n

While a registration is alive, Native UI Shell keeps the searchable controller even when the page is transitioning or temporarily unavailable (available: false), instead of demoting back to a separate ordinary control identity. Resting chrome (including Album before search opens) uses the same UITabBar + ShellTabBar.fit path as ordinary tabs, with the search trigger pinned to the FAB when available; UISearchTab is shown only while the search session is active. Flipping availability or entering/leaving search crossfades between those layers. Register before the destination page finishes entering (for example in ionViewWillEnter) so the first visit does not paint ordinary tabs and then swap.

\n

Opening search preserves the selected Ionic tab and does not automatically show the keyboard (automaticallyActivatesSearch stays off). Tap the field or call ion-searchbar.setFocus() for the keyboard. While search is active, Native UI Shell freezes Web layout projection and holds Capacitor Keyboard resize at none; UIKit owns tab/search chrome (no Ionic fit mid-session). Leave re-fits ordinary tabs to ion-tab-bar and search to the FAB. Ordinary native tabs keep an optimistic selection until the Web selected state catches up. Input events and application value updates continue across the bridge until search closes. The trigger's resolved SVG and the search icon are projected from Ionic, including ion-icon name.

\n

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.

\n

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.

\n

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.

\n

Replacing the registered ion-searchbar or its input retires the old editing session. The replacement keeps its own application value and starts a new native session when reopened.

\n

While enabled, Native UI Shell suppresses the WebView’s top scroll-edge effect because Ionic already paints the header edge. This prevents a second dark gradient when the OS and Web themes differ. The original setting is restored on destroy.

\n

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.

\n

Transitions and recovery

\n

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.

\n

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.

\n

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.

\n

The connection in src/transition/ios.transition.ts 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.

\n

Tab switches skip the Web/native crossfade so a retiring UIKit snapshot cannot linger over the next tab. Detection uses the router URL versus the still-selected tab on ionViewWillLeave (and vanilla ionTabsWillChange / ionTabsDidChange DOM events). Stack pushes and pops keep the normal 180ms handoff.

\n

Standard Ionic overlays suspend native projection until dismissal. Unsupported searchable-tab configurations use the existing Web animation and share this ownership with Web glass gestures. CSS motion of supported containing surfaces also causes temporary Web rendering.

\n

During retirement, the source is restored and allowed to paint before its native cover is removed. During acquisition, the source is hidden only after a successful, current native response. Delayed responses are revalidated per control: existing eligible controls retain their native cover while content updates catch up. Only removed or ineligible sources return to Web; newly acquired sources require an exact acknowledgement. Ordinary page mutations never call the global clear operation. UIKit tab instances and items are retained, and equal frames/selections are not reapplied. Duplicate/stale activations are discarded. WebKit and UIKit still render separately: the implementation avoids an intentional blank frame, but does not provide an OS-level atomic compositing guarantee. Validate custom transitions and overlays on the app's supported simulators before rollout; unknown overlay systems are outside the automatic integration contract.

\n

If a bridge update fails or times out, the runtime stops and restores Web rendering. It does not automatically reconnect. getStatus() reports stopped and the reason; to retry deliberately, call destroy() on that handle and then enableNativeUIShell() again.

\n

For diagnostics or application teardown:

\n
const shell = await enableNativeUIShell(); // repeated calls share the runtime\nconsole.log(shell.getStatus()); // state, projected control count, update count, failure reason\nawait shell.destroy(); // restore DOM, remove native controls and release listeners/cache\n

Projection is globally enabled by default. Limit it to selected Ionic components when an application only wants part of the native shell, or disable it globally while retaining the same configuration path:

\n
const shell = await enableNativeUIShell({\n  enabled: true,\n  controls: {\n    tabs: true,\n  },\n});\n\n// Equivalent to leaving Native UI Shell off; all controls remain on the Web.\nconst disabledShell = await enableNativeUIShell({ enabled: false });\n

Omitting controls enables every supported control for backward compatibility. When controls is present, only entries set to true are native-eligible. Available entries are tabs, toolbar, segment, and fab.

\n

For a custom modal or overlay that Native UI Shell cannot detect, acquire a suspension before presenting it. The resolved suspension means projected controls have returned to Web rendering. Always release it after dismissal:

\n
const suspension = await shell.suspend();\n\ntry {\n  await modal.present();\n  await modal.onDidDismiss();\n} finally {\n  await suspension.resume();\n}\n

Suspensions are nestable and resume() is idempotent. Native projection resumes only after every active suspension has been released, using the current DOM rather than a stale snapshot.

\n

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.

\n

Support iPhone Duo (experimental)

\n

iPhone Duo support is experimental, including standalone use without Native UI Shell. It is available in the 1.2.0-0 prerelease alongside Native UI Shell. Its APIs and supported behavior may change.

\n

The standalone Vertical Control Area entry point (@rdlabo/ionic-theme-ios27/vertical-bars) and dist/css/vertical-bars.css work without loading the iOS 27 theme. Call enableVerticalControlArea() for this use case; it projects only controls placed in the vertical area. Apps already calling enableNativeUIShell() should keep that single runtime rather than starting both. See iPhone Duo support for the complete setup, including hinge posture and the split-pane layout for apps that do not use this shell at all.

\n

On supported iOS versions, adding .ios-theme-vertical-bars changes only controls that the system relocates into the physical side rail. Native UI Shell 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. Older toolchains that cannot report an edge trust the DOM placement directly. 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.

\n

The SwiftUI surface is clipped and hit-tested to the system rail. Web content remains visible and interactive outside that physical region. The runtime optimistically updates tab selection before forwarding the action to the original ion-tab-button, using the same event and stale-revision protection as the other native controls. Menus, modals, and popovers remain independent surfaces and are not moved into the main-page rail.

\n

This mode accepts standard fill=\"default\" and fill=\"clear\" fixed-toolbar buttons because SwiftUI determines their compact representation. Solid, outline, custom-color, scrolling, and explicitly opted-out controls remain on the Web. Add .ios-theme-horizontal-only to an ion-buttons group or individual ion-button to keep it in the horizontal Web toolbar. On Web, Android, older iOS, or when native projection is unavailable during setup, the Web projection remains the fallback.

\n

For standalone setup while keeping your existing theme, see iPhone Duo with your existing theme.

\n

Native UI Shell API

\n

The generated reference below documents the handle returned by enableNativeUIShell(). The underlying Capacitor bridge and its control-snapshot protocol are implementation details.

\n

<docgen-index>

\n\n

</docgen-index>

\n

<docgen-api>
\n

\n

getStatus()

\n
getStatus() => NativeUIShellStatus\n

Returns the current Web/native projection state.

\n

Returns: NativeUIShellStatus

\n
\n

suspend()

\n
suspend() => Promise<NativeUIShellSuspension>\n

Restores projected controls to the Web until the returned lease is resumed.

\n

Returns: Promise<NativeUIShellSuspension>

\n
\n

destroy()

\n
destroy() => Promise<void>\n

Stops synchronization, restores Web controls and releases native resources.

\n
\n

Interfaces

\n

NativeUIShellStatus

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropType
state'native' \\| 'stopped' \\| 'web'
projectednumber
updatesnumber
reasonstring
\n

NativeUIShellSuspension

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MethodSignatureDescription
resume() => Promise<void>Releases this suspension. Native projection resumes after all active suspensions are released.
\n

</docgen-api>

\n

Source layout

\n

Each TypeScript module in src/native/components declares its Ionic tag and DOM reader. components/index.ts combines those exports into discovery selectors and the component type. Shared DOM measurements, item data and SVG rendering live in src/native/shared; runtime.ts owns synchronization, visibility handoffs and lifecycle events.

\n

On iOS, Components owns UIKit control creation, updates and component names. ShellButton shares the native button implementation used by ordinary, back and menu buttons. Shared owns the host view, typed snapshots, geometry, colors and image cache. Capacitor decodes each complete snapshot once using Decodable; renderers consume typed models and compare content with Equatable. Invalid batches are rejected before visible controls are changed. IonicNativeUIShellPlugin.swift coordinates Capacitor calls, revisions and native view lifetimes.

\n

Demo and verification

\n

The demo includes a native-ui-shell page for exercising fixed controls. From the repository root, build the library and run the browser tests:

\n
npm ci\nnpm run build\ncd demo\nnpm ci\nnpx --no-install playwright install chromium\nnpx --no-install playwright test e2e/native-ui-shell.spec.ts e2e/native-ui-shell-edge.spec.ts\n

For native interaction and placement tests, use Xcode 26 or later, XcodeGen and a booted iOS 26+ Simulator. From the repository root, run sh scripts/verify-native-ui-shell.sh SIMULATOR_UDID. This also builds an independent Swift Package Manager consumer from the npm package. Run sh scripts/verify-native-search.sh SIMULATOR_UDID for searchable-tab integration, or add edge to that command for placement, navigation and keyboard edge cases. The scripts print the location of their local test artifacts.

\n

Search controllers retain their UIKit-managed transition and are excluded from the ordinary control acquisition crossfade.

\n", + "html": "

Native UI Shell is experimental. Its API and supported controls may change.

\n

Native UI Shell gives an Ionic app native navigation and action controls around its Web content. The optional Capacitor iOS plugin renders supported fixed Ionic controls with UIKit or SwiftUI and the system's Liquid Glass material. Page content, scrolling, application state and routing remain in Ionic's WebView.

\n

Background

\n

Basecamp described this hybrid approach in Hybrid sweet spot: Native navigation, web content (May 8, 2014): keep Web content at the core and use native UI where it improves the experience. The Capacitor 1.0.0 Alpha announcement (February 27, 2018) explicitly included Native UI Shell in its roadmap and linked to that article. Combining native UI with Web content was part of Capacitor's direction from the beginning.

\n

This package revisits that idea for Ionic and Liquid Glass. Existing Ionic markup defines the shell: eligible toolbar controls, tabs, fixed FABs and searchable tabs acquire native presentation. UIKit handles their appearance and interaction; the bridge synchronizes DOM state and returns actions to the original Ionic components. Ionic continues to own the navigation stack, page transitions and application logic. The shell's scope is the supported fixed controls described below.

\n

Enable the shell

\n

After installing the theme CSS described in the README, enable the shell once at application startup:

\n
import { enableNativeUIShell } from '@rdlabo/ionic-theme-ios27/native';\n\nvoid enableNativeUIShell();\n

enableNativeUIShell() also reads the WebView's effective top-left corner radius and applies it to page transitions. To configure only the transition without enabling native controls, call:

\n
import { configureNativeTransition } from '@rdlabo/ionic-theme-ios27/native';\n\nawait configureNativeTransition();\n

Keep the existing navAnimation: iosTransitionAnimation setting. 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 native plugin uses Swift Package Manager (SPM). For an existing CocoaPods app, 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.

\n

This is an opt-in feature. The ordinary package entry point does not import Capacitor, and @capacitor/core is an optional peer dependency. Native sources are still detected and built by Capacitor's sync when this package is installed in a Capacitor project, even if the application does not call enableNativeUIShell().

\n

Native appearance follows the applied class, system or always-dark theme CSS. System theme changes are synchronized while the runtime is active.

\n

Supported markup

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Ionic componentSupported appearance and placementNative rendering
ion-buttonfill=\"default\", standard glass, in a fixed header/footer toolbarGlass UIButton
ion-buttonsFixed toolbar, two or more direct clear ion-button / ion-menu-button children sharing the theme glassOne UIGlassEffect surface with independent native buttons
ion-back-buttonStandard icon and color in a fixed header/footer toolbarGlass UIButton, using the resolved Ionic label/icon
ion-menu-buttonFixed toolbar, inside the theme glass ion-buttonsGlass UIButton; original Ionic menu toggle
ion-tab-barFixed tabs, icon-only/label-only items, one icon per item, dot/text badges, selection and disabled stateUITabBar and UITabBarItem
ion-segmentFixed toolbar, non-scrollable, text or one icon per itemUISegmentedControl
ion-fab / ion-fab-button / ion-fab-listGlass FAB in an ion-content fixed slot; one main button and optional directional listsPersistent glass UIButton per button; one FAB synchronization group
\n

For the ordinary Native UI Shell, only iOS-mode components with the theme variables installed are eligible. Explicitly enabled Vertical Bars is mode-independent as described below. ionic-theme-disabled, 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.

\n

Use ios-theme-shell-disabled to disable only the iOS Native UI Shell while keeping the Web theme. It excludes the element and all its descendants. Adding or removing the class at runtime automatically restores Web rendering or re-evaluates native eligibility.

\n
<ion-toolbar class=\"ios-theme-shell-disabled\">\n  <ion-button>Web glass button</ion-button>\n</ion-toolbar>\n

If a child inside a shared native surface opts out, the entire surface stays on the Web: this includes button groups, tab bars, segments and FAB lists. 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.

\n

Placement is required even when the appearance is glass. In the ordinary Native UI Shell, 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. Overlays, collapsed headers, opted-out controls and departed pages are excluded.

\n

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.

\n

Standalone clear, solid and outline buttons are excluded. A glass ion-buttons group of two or more clear buttons is projected as one surface; its children retain separate actions. Menu buttons can also share that group. A single menu button uses its parent ion-buttons as the glass surface, so no Web glass is left underneath the native button. A menu button outside this theme glass remains on the Web. Mixed fills, unsupported children, or a theme-disabled child keep the group on the Web. Single clear buttons remain on the Web. 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.

\n

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.

\n

While a Web input owns the software keyboard, ordinary controls return to Web rendering and are re-evaluated when it closes. This also covers iPad layouts where the keyboard does not move the visual viewport. A native search field keeps its own native keyboard and search surface.

\n

When an existing control becomes unsupported, its Web source is painted before its native cover is removed. This avoids a blank handoff, but Web/UIKit updates are not atomic and can briefly overlap. This boundary behavior differs from ordinary page navigation, where unchanged shared native tabs are retained.

\n

State and events

\n

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.

\n

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.

\n

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.

\n

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, preserving their colors. Tab SVGs that follow text color use native template rendering, so icons and labels change selection color together without waiting for another bridge image. Explicit multicolor artwork retains its original colors. External references, <use>, animation, embedded HTML/images, SVG text and stylesheets are excluded. Web fonts and arbitrary slot layouts are not reproduced exactly.

\n

The native host accepts input only within native controls. Tab interaction and accessibility are provided by the standard 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.

\n

Searchable tabs

\n

Existing attachTabBarSearchable(tabBar, fabButton, footer) registrations automatically use native search when their bottom tab bar and glass search controls are supported. No new component option, route, native setup or page listener is required. Ordinary tab bars continue using UITabBar; searchable groups use a persistent UITabBarController, UITab / UISearchTab, and UISearchController. The original Capacitor WebView continues to render results and handle navigation.

\n

Search registration does not bypass placement restrictions. Its searchbar and close button must be in fixed footer toolbars. Its trigger must belong to an ion-fab[slot=\"fixed\"] directly inside ion-content, or directly on the existing non-scrolling .ion-page layout. A wrapper inside scrolling content is not a fixed slot.

\n

While a registration is alive, Native UI Shell keeps the searchable controller even when the page is transitioning or temporarily unavailable (available: false), instead of demoting back to a separate ordinary control identity. Resting chrome (including Album before search opens) uses the same UITabBar + ShellTabBar.fit path as ordinary tabs, with the search trigger pinned to the FAB when available; UISearchTab is shown only while the search session is active. Flipping availability or entering/leaving search crossfades between those layers. Register before the destination page finishes entering (for example in ionViewWillEnter) so the first visit does not paint ordinary tabs and then swap.

\n

Opening search preserves the selected Ionic tab and does not automatically show the keyboard (automaticallyActivatesSearch stays off). Tap the field or call ion-searchbar.setFocus() for the keyboard. While search is active, Native UI Shell freezes Web layout projection and holds Capacitor Keyboard resize at none; UIKit owns tab/search chrome (no Ionic fit mid-session). Leave re-fits ordinary tabs to ion-tab-bar and search to the FAB. Ordinary native tabs keep an optimistic selection until the Web selected state catches up. Input events and application value updates continue across the bridge until search closes. The trigger's resolved SVG and the search icon are projected from Ionic, including ion-icon name.

\n

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.

\n

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.

\n

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.

\n

Replacing the registered ion-searchbar or its input retires the old editing session. The replacement keeps its own application value and starts a new native session when reopened.

\n

While enabled, Native UI Shell suppresses the WebView’s top scroll-edge effect because Ionic already paints the header edge. This prevents a second dark gradient when the OS and Web themes differ. The original setting is restored on destroy.

\n

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.

\n

Transitions and recovery

\n

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.

\n

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.

\n

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.

\n

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 withNativeUIShellTransition(), exported from both the package root and /vertical-bars. See Connect your navigation animation for the setup.

\n

Tab switches skip the Web/native crossfade so a retiring UIKit snapshot cannot linger over the next tab. Detection uses the router URL versus the still-selected tab on ionViewWillLeave (and vanilla ionTabsWillChange / ionTabsDidChange DOM events). Stack pushes and pops keep the normal 180ms handoff.

\n

Standard Ionic overlays suspend native projection until dismissal. Unsupported searchable-tab configurations use the existing Web animation and share this ownership with Web glass gestures. CSS motion of supported containing surfaces also causes temporary Web rendering.

\n

During retirement, the source is restored and allowed to paint before its native cover is removed. During acquisition, the source is hidden only after a successful, current native response. Delayed responses are revalidated per control: existing eligible controls retain their native cover while content updates catch up. Only removed or ineligible sources return to Web; newly acquired sources require an exact acknowledgement. Ordinary page mutations never call the global clear operation. UIKit tab instances and items are retained, and equal frames/selections are not reapplied. Duplicate/stale activations are discarded. WebKit and UIKit still render separately: the implementation avoids an intentional blank frame, but does not provide an OS-level atomic compositing guarantee. Validate custom transitions and overlays on the app's supported simulators before rollout; unknown overlay systems are outside the automatic integration contract.

\n

If a bridge update fails or times out, the runtime stops and restores Web rendering. It does not automatically reconnect. getStatus() reports stopped and the reason; to retry deliberately, call destroy() on that handle and then enableNativeUIShell() again.

\n

For diagnostics or application teardown:

\n
const shell = await enableNativeUIShell(); // repeated calls share the runtime\nconsole.log(shell.getStatus()); // state, projected control count, update count, failure reason\nawait shell.destroy(); // restore DOM, remove native controls and release listeners/cache\n

Projection is globally enabled by default. Limit it to selected Ionic components when an application only wants part of the native shell, or disable it globally while retaining the same configuration path:

\n
const shell = await enableNativeUIShell({\n  enabled: true,\n  controls: {\n    tabs: true,\n  },\n});\n\n// Equivalent to leaving Native UI Shell off; all controls remain on the Web.\nconst disabledShell = await enableNativeUIShell({ enabled: false });\n

Omitting controls enables every supported control for backward compatibility. When controls is present, only entries set to true are native-eligible. Available entries are tabs, toolbar, segment, and fab.

\n

For a custom modal or overlay that Native UI Shell cannot detect, acquire a suspension before presenting it. The resolved suspension means projected controls have returned to Web rendering. Always release it after dismissal:

\n
const suspension = await shell.suspend();\n\ntry {\n  await modal.present();\n  await modal.onDidDismiss();\n} finally {\n  await suspension.resume();\n}\n

Suspensions are nestable and resume() is idempotent. Native projection resumes only after every active suspension has been released, using the current DOM rather than a stale snapshot.

\n

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.

\n

Support iPhone Duo (experimental)

\n

iPhone Duo support is experimental, including standalone use without Native UI Shell. It is available in the 1.2.0-0 prerelease alongside Native UI Shell. Its APIs and supported behavior may change.

\n

The standalone Vertical Control Area entry point (@rdlabo/ionic-theme-ios27/vertical-bars) and dist/css/vertical-bars.css work without loading the iOS 27 theme. Call enableVerticalControlArea() for this use case; it projects only controls placed in the vertical area. Apps already calling enableNativeUIShell() should keep that single runtime rather than starting both. See iPhone Duo support for the complete setup, including hinge posture and the split-pane layout for apps that do not use this shell at all.

\n

On supported iOS versions, adding .ios-theme-vertical-bars changes only controls that the system relocates into the physical side rail. Native UI Shell 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. Older toolchains that cannot report an edge trust the DOM placement directly. 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.

\n

The SwiftUI surface is clipped and hit-tested to the system rail. Web content remains visible and interactive outside that physical region. The runtime optimistically updates tab selection before forwarding the action to the original ion-tab-button, using the same event and stale-revision protection as the other native controls. Menus, modals, and popovers remain independent surfaces and are not moved into the main-page rail.

\n

This mode accepts standard fill=\"default\" and fill=\"clear\" fixed-toolbar buttons because SwiftUI determines their compact representation. Solid, outline, custom-color, scrolling, and explicitly opted-out controls remain on the Web. Add .ios-theme-horizontal-only to an ion-buttons group or individual ion-button to keep it in the horizontal Web toolbar. On Web, Android, older iOS, or when native projection is unavailable during setup, the Web projection remains the fallback.

\n

For standalone setup while keeping your existing theme, see iPhone Duo with your existing theme.

\n

Native UI Shell API

\n

The generated reference below documents the handle returned by enableNativeUIShell(). The underlying Capacitor bridge and its control-snapshot protocol are implementation details.

\n

<docgen-index>

\n\n

</docgen-index>

\n

<docgen-api>
\n

\n

getStatus()

\n
getStatus() => NativeUIShellStatus\n

Returns the current Web/native projection state.

\n

Returns: NativeUIShellStatus

\n
\n

suspend()

\n
suspend() => Promise<NativeUIShellSuspension>\n

Restores projected controls to the Web until the returned lease is resumed.

\n

Returns: Promise<NativeUIShellSuspension>

\n
\n

destroy()

\n
destroy() => Promise<void>\n

Stops synchronization, restores Web controls and releases native resources.

\n
\n

Interfaces

\n

NativeUIShellStatus

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropType
state'native' \\| 'stopped' \\| 'web'
projectednumber
updatesnumber
reasonstring
\n

NativeUIShellSuspension

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MethodSignatureDescription
resume() => Promise<void>Releases this suspension. Native projection resumes after all active suspensions are released.
\n

</docgen-api>

\n

Source layout

\n

Each TypeScript module in src/native/components declares its Ionic tag and DOM reader. components/index.ts combines those exports into discovery selectors and the component type. Shared DOM measurements, item data and SVG rendering live in src/native/shared; runtime.ts owns synchronization, visibility handoffs and lifecycle events.

\n

On iOS, Components owns UIKit control creation, updates and component names. ShellButton shares the native button implementation used by ordinary, back and menu buttons. Shared owns the host view, typed snapshots, geometry, colors and image cache. Capacitor decodes each complete snapshot once using Decodable; renderers consume typed models and compare content with Equatable. Invalid batches are rejected before visible controls are changed. IonicNativeUIShellPlugin.swift coordinates Capacitor calls, revisions and native view lifetimes.

\n

Demo and verification

\n

The demo includes a native-ui-shell page for exercising fixed controls. From the repository root, build the library and run the browser tests:

\n
npm ci\nnpm run build\ncd demo\nnpm ci\nnpx --no-install playwright install chromium\nnpx --no-install playwright test e2e/native-ui-shell.spec.ts e2e/native-ui-shell-edge.spec.ts\n

For native interaction and placement tests, use Xcode 26 or later, XcodeGen and a booted iOS 26+ Simulator. From the repository root, run sh scripts/verify-native-ui-shell.sh SIMULATOR_UDID. This also builds an independent Swift Package Manager consumer from the npm package. Run sh scripts/verify-native-search.sh SIMULATOR_UDID for searchable-tab integration, or add edge to that command for placement, navigation and keyboard edge cases. The scripts print the location of their local test artifacts.

\n

Search controllers retain their UIKit-managed transition and are excluded from the ordinary control acquisition crossfade.

\n", "headings": [ { "id": "background", @@ -440,7 +440,7 @@ export const PROJECT = { "file": "iphone-duo-with-original-theme.md", "section": "Guides", "path": "/projects/ionic-theme-ios27/docs/iphone-duo-with-original-theme", - "html": "

Add a vertical navigation area to your Ionic app while keeping its existing theme. Tabs and supported toolbar actions move to the side of the screen; your content and horizontal controls keep their current appearance. Both Ionic ios and md modes are supported.

\n

Try it in Chrome first. You can preview the layout with Web controls before setting up an iPhone Duo or an iOS build. On supported Capacitor iOS, the same Ionic markup supplies native SwiftUI controls in the system rail.

\n

Available in 1.2.0-0 as an experimental feature. APIs and supported behavior may change.

\n

Try it in your existing Ionic app

\n

1. Install and load the standalone stylesheet

\n

This guide assumes an existing Ionic app with Ionic >=8.8.1 <10 and Capacitor Core >=8 <9. Keep your existing Capacitor 8 installation. If your app uses another Capacitor major, migrate its Core, CLI, and platform packages together before following this guide. For a Web-only app without Capacitor, also install @capacitor/core@^8; the JavaScript entry point needs it even in Chrome.

\n
npm install @rdlabo/ionic-theme-ios27@1.2.0-0\n

Keep your existing theme imports. Add this to your global Sass file:

\n
@use '@rdlabo/ionic-theme-ios27/dist/css/vertical-bars.css';\n

The standalone JavaScript entry point needs @capacitor/core even in Chrome. The iOS 27 theme stylesheets are not required.

\n

2. Opt your app into the side layout

\n

Add the class to your existing app root and keep the content inside it:

\n
<ion-app class=\"ios-theme-vertical-bars\">\n  <!-- Keep your existing pages, tabs, and toolbar controls here. -->\n</ion-app>\n

The preview reserves 80px on the physical right. To preview the left side, also add ios-theme-vertical-bars-left.

\n

3. Configure page transitions before Ionic initializes

\n

Import the page transition and register it as Ionic's navAnimation. Starting the rail runtime does not register this option. The animation coordinates native control retirement before navigation and avoids animating the horizontal back button in the vertical layout. Your existing theme styles remain in use, but iOS page transitions use this package's animation.

\n
import { iosTransitionAnimation } from '@rdlabo/ionic-theme-ios27';\n\nconst ionicConfig = {\n  navAnimation: iosTransitionAnimation,\n};\n

Merge this option into your existing Ionic configuration for ios mode before initialization: pass it to Angular's provideIonicAngular(), React's setupIonicReact(), or Vue's IonicVue plugin options. Keep your existing md animation configuration. Importing the JavaScript entry point does not load the iOS 27 theme stylesheets.

\n

4. Start the controls after the app root is mounted

\n

Call this once from your application startup after ion-app exists in the DOM:

\n
import { enableVerticalControlArea } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\nconst rail = await enableVerticalControlArea();\n

What you should see: your existing tab bar moves to the side, and supported icon-based fixed-toolbar actions appear there too. Content keeps its existing theme and leaves room for the controls. The Web tab rail displays icons; pressing and dragging reveals tab labels.

\n

Use your existing Ionic click handlers and routing. Text-only toolbar actions remain horizontal. Add .ios-theme-horizontal-only to an ion-buttons group or individual ion-button to keep an action in the horizontal toolbar.

\n

When the application owner is disposed, call await rail.destroy() to restore the original controls and release the runtime. If you already use enableNativeUIShell(), keep that runtime and follow the shared placement guide.

\n

If the preview does not appear

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
What you seeWhat to check
No space at the sideLoad vertical-bars.css and put the class on ion-app.
Space appears, but controls stay horizontalStart enableVerticalControlArea() after mounting the app root. Use existing tabs or supported icon-based actions in a fixed toolbar.
One action stays horizontalText-only actions, custom fills, and explicitly excluded controls keep their original presentation. See control requirements.
\n

Connect an iPhone Duo

\n

For Capacitor iOS, run npx cap sync ios. Build with Xcode 27.1 or newer and link against the iOS 27.1 SDK or later to receive the actual rail edge, safe-area inset, and hinge posture. The plugin uses Swift Package Manager; existing CocoaPods apps can follow Native UI Shell setup.

\n

Replace the browser-only startup above with this after ion-app is mounted:

\n
import { Capacitor, type PluginListenerHandle } from '@capacitor/core';\nimport { enableVerticalControlArea, IonicNativeUIShell } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\nconst rail = await enableVerticalControlArea();\nlet layoutListener: PluginListenerHandle | undefined;\n\nif (Capacitor.getPlatform() === 'ios') {\n  // The runtime already monitors device layout; only subscribe.\n  layoutListener = await IonicNativeUIShell.addListener('deviceLayoutChange', ({ placement }) =>\n    rail.setPlacement(placement),\n  );\n  rail.setPlacement((await IonicNativeUIShell.getDeviceLayout()).placement);\n}\n\n// Call when the application owner is disposed.\nconst stopVerticalArea = async () => {\n  await layoutListener?.remove();\n  await rail.destroy();\n};\n

setPlacement() applies the measured inset and resolves the logical edge through the document direction. A null edge restores the ordinary layout. Devices without a rail and apps built with older SDKs report null, so this example restores the ordinary layout there. To deliberately preview a DOM rail on such an iOS build, have your application choose a fixed edge with rail.setPlacement('trailing') instead of applying that null placement. This simulates the layout; it does not provide a real system rail or hinge measurements.

\n

On supported iOS, controls in the rail use the system SwiftUI appearance; your custom Web styling still applies to ordinary content and horizontal controls. Web and Android use Web clones.

\n

Use hinge posture without projecting controls

\n

If your existing theme needs only a posture-driven split pane or a layout switch, do not start a projection runtime or add .ios-theme-vertical-bars. Use getDeviceLayout() and deviceLayoutChange directly, pairing startDeviceLayoutMonitoring() with stopDeviceLayoutMonitoring() and removing the listener when finished.

\n

See Read the device layout for the subscription example, null values, and monitoring lifetime. See Adapt the split pane for the opt-in width rules and half-open state.

\n

Shared layout rules and API

\n

Safe-area handling, overlays, RTL, control eligibility, Web simulation, and the handle API are documented in iPhone Duo support. Those rules apply to this standalone setup too.

\n", + "html": "

Add a vertical navigation area to your Ionic app while keeping its existing theme. Tabs and supported toolbar actions move to the side of the screen; your content and horizontal controls keep their current appearance. Both Ionic ios and md modes are supported.

\n

Try it in Chrome first. You can preview the layout with Web controls before setting up an iPhone Duo or an iOS build. On supported Capacitor iOS, the same Ionic markup supplies native SwiftUI controls in the system rail.

\n

Available in 1.2.0-0 as an experimental feature. APIs and supported behavior may change.

\n

Try it in your existing Ionic app

\n

1. Install and load the standalone stylesheet

\n

This guide assumes an existing Ionic app with Ionic >=8.8.1 <10 and Capacitor Core >=8 <9. Keep your existing Capacitor 8 installation. If your app uses another Capacitor major, migrate its Core, CLI, and platform packages together before following this guide. For a Web-only app without Capacitor, also install @capacitor/core@^8; the JavaScript entry point needs it even in Chrome.

\n
npm install @rdlabo/ionic-theme-ios27@1.2.0-1\n

Keep your existing theme imports. Add this to your global Sass file:

\n
@use '@rdlabo/ionic-theme-ios27/dist/css/vertical-bars.css';\n

The standalone JavaScript entry point needs @capacitor/core even in Chrome. The iOS 27 theme stylesheets are not required.

\n

2. Opt your app into the side layout

\n

Add the class to your existing app root and keep the content inside it:

\n
<ion-app class=\"ios-theme-vertical-bars\">\n  <!-- Keep your existing pages, tabs, and toolbar controls here. -->\n</ion-app>\n

The preview reserves 80px on the physical right. To preview the left side, also add ios-theme-vertical-bars-left.

\n

3. Connect your navigation animation

\n

Configure navAnimation before Ionic initializes. Starting the rail runtime does not register this option. The adapter waits for native control retirement and coordinates swipe progress and cancellation while keeping your existing animation.

\n

Keep Ionic's default animation

\n

If you have not configured navAnimation, wrap Ionic's standard builders. Select the builder from Ionic's transition mode so both ios and md keep their usual animation:

\n
import { iosTransitionAnimation, mdTransitionAnimation, type AnimationBuilder } from '@ionic/core';\nimport { withNativeUIShellTransition } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\nconst defaultTransition: AnimationBuilder = (baseEl, opts) =>\n  (opts.mode === 'ios' ? iosTransitionAnimation : mdTransitionAnimation)(baseEl, opts);\n\nconst ionicConfig = {\n  navAnimation: withNativeUIShellTransition(defaultTransition),\n};\n

Merge this option into your existing Ionic configuration before initialization: pass it to Angular's provideIonicAngular(), React's setupIonicReact(), or Vue's IonicVue plugin options. Keep your existing theme stylesheet imports. No iOS 27 theme stylesheet is required.

\n

Keep your custom animation

\n

If your app already configures navAnimation, wrap that builder instead:

\n
import type { AnimationBuilder } from '@ionic/core';\nimport { withNativeUIShellTransition } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\n// Pass the animation builder your app already uses.\nconst configureNavigation = (existingTransition: AnimationBuilder) => ({\n  navAnimation: withNativeUIShellTransition(existingTransition),\n});\n

The adapter returns the original Animation, preserving its effects, duration, and easing. Use it only for navigation, not modal or popover animations. The builder must return a fresh Animation for each navigation; Ionic destroys it after the transition. Keep lifecycle events for control registration and transitions without animation.

\n

The adapter keeps the builder's animation targets, including any horizontal back-button effect. If you need the iOS 27 transition with that effect excluded in vertical layouts, use iosTransitionAnimation from @rdlabo/ionic-theme-ios27 as your navAnimation instead. It already includes the adapter, so no wrapper is needed.

\n

withNativeUIShellTransition() is available in 1.2.0-1 and later.

\n

4. Start the controls after the app root is mounted

\n

Call this once from your application startup after ion-app exists in the DOM:

\n
import { enableVerticalControlArea } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\nconst rail = await enableVerticalControlArea();\n

What you should see: your existing tab bar moves to the side, and supported icon-based fixed-toolbar actions appear there too. Content keeps its existing theme and leaves room for the controls. The Web tab rail displays icons; pressing and dragging reveals tab labels.

\n

Use your existing Ionic click handlers and routing. Text-only toolbar actions remain horizontal. Add .ios-theme-horizontal-only to an ion-buttons group or individual ion-button to keep an action in the horizontal toolbar.

\n

When the application owner is disposed, call await rail.destroy() to restore the original controls and release the runtime. If you already use enableNativeUIShell(), keep that runtime and follow the shared placement guide.

\n

If the preview does not appear

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
What you seeWhat to check
No space at the sideLoad vertical-bars.css and put the class on ion-app.
Space appears, but controls stay horizontalStart enableVerticalControlArea() after mounting the app root. Use existing tabs or supported icon-based actions in a fixed toolbar.
One action stays horizontalText-only actions, custom fills, and explicitly excluded controls keep their original presentation. See control requirements.
\n

Connect an iPhone Duo

\n

For Capacitor iOS, run npx cap sync ios. Build with Xcode 27.1 or newer and link against the iOS 27.1 SDK or later to receive the actual rail edge, safe-area inset, and hinge posture. The plugin uses Swift Package Manager; existing CocoaPods apps can follow Native UI Shell setup.

\n

Replace the browser-only startup above with this after ion-app is mounted:

\n
import { Capacitor, type PluginListenerHandle } from '@capacitor/core';\nimport { enableVerticalControlArea, IonicNativeUIShell } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\nconst rail = await enableVerticalControlArea();\nlet layoutListener: PluginListenerHandle | undefined;\n\nif (Capacitor.getPlatform() === 'ios') {\n  // The runtime already monitors device layout; only subscribe.\n  layoutListener = await IonicNativeUIShell.addListener('deviceLayoutChange', ({ placement }) =>\n    rail.setPlacement(placement),\n  );\n  rail.setPlacement((await IonicNativeUIShell.getDeviceLayout()).placement);\n}\n\n// Call when the application owner is disposed.\nconst stopVerticalArea = async () => {\n  await layoutListener?.remove();\n  await rail.destroy();\n};\n

setPlacement() applies the measured inset and resolves the logical edge through the document direction. A null edge restores the ordinary layout. Devices without a rail and apps built with older SDKs report null, so this example restores the ordinary layout there. To deliberately preview a DOM rail on such an iOS build, have your application choose a fixed edge with rail.setPlacement('trailing') instead of applying that null placement. This simulates the layout; it does not provide a real system rail or hinge measurements.

\n

On supported iOS, controls in the rail use the system SwiftUI appearance; your custom Web styling still applies to ordinary content and horizontal controls. Web and Android use Web clones.

\n

Use hinge posture without projecting controls

\n

If your existing theme needs only a posture-driven split pane or a layout switch, do not start a projection runtime or add .ios-theme-vertical-bars. Use getDeviceLayout() and deviceLayoutChange directly, pairing startDeviceLayoutMonitoring() with stopDeviceLayoutMonitoring() and removing the listener when finished.

\n

See Read the device layout for the subscription example, null values, and monitoring lifetime. See Adapt the split pane for the opt-in width rules and half-open state.

\n

Shared layout rules and API

\n

Safe-area handling, overlays, RTL, control eligibility, Web simulation, and the handle API are documented in iPhone Duo support. Those rules apply to this standalone setup too.

\n", "headings": [ { "id": "try-it-in-your-existing-ionic-app", @@ -458,10 +458,20 @@ export const PROJECT = { "level": 3 }, { - "id": "3.-configure-page-transitions-before-ionic-initializes", - "text": "3. Configure page transitions before Ionic initializes", + "id": "3.-connect-your-navigation-animation", + "text": "3. Connect your navigation animation", "level": 3 }, + { + "id": "keep-ionic's-default-animation", + "text": "Keep Ionic's default animation", + "level": 4 + }, + { + "id": "keep-your-custom-animation", + "text": "Keep your custom animation", + "level": 4 + }, { "id": "4.-start-the-controls-after-the-app-root-is-mounted", "text": "4. Start the controls after the app root is mounted", @@ -575,8 +585,13 @@ export const PROJECT = { "file": "migration.md", "section": "Guides", "path": "/projects/ionic-theme-ios27/docs/migration", - "html": "

From the iOS 26 theme

\n

For an app using @rdlabo/ionic-theme-ios26, the recommended migration keeps that package and adds @rdlabo/ionic-theme-ios27. The README setup selects iOS 27 or iOS 26 styles by browser capability and leaves Ionic's default iOS appearance on older browsers.

\n

1. Add the new package

\n

Keep the iOS 26 package and add iOS 27. The new theme requires @ionic/core 8.8.1 or later (Ionic 8 or 9).

\n
npm install @rdlabo/ionic-theme-ios27\n

2. Make the styles adaptive

\n

Replace unconditional iOS 26 imports in your global Sass stylesheet with two mutually exclusive branches. This example uses class-based dark mode:

\n
+ @use 'sass:meta';\n+\n- @use '@rdlabo/ionic-theme-ios26/src/styles/default-variables.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect.scss';\n+ @supports (overflow-anchor: auto) {\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/default-variables');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27-dark-class');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/md-remove-ios-class-effect');\n+ }\n+\n+ @supports (text-wrap: pretty) and (not (overflow-anchor: auto)) {\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/default-variables');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect');\n+ }\n

Keep Ionic's matching dark palette. For system or always-dark mode, replace both -dark-class imports with the matching variant. If you use md-ion-list-inset, load the corresponding package's stylesheet inside each branch. Browsers without either feature retain Ionic's default styling.

\n

3. Switch the animations

\n

Replace the iOS 26 animation import and gate the iOS 27 animations on the same browser features. Resolve the options before Ionic initializes:

\n
- import { iosTransitionAnimation, popoverEnterAnimation, popoverLeaveAnimation } from '@rdlabo/ionic-theme-ios26';\n+ import { iosTransitionAnimation, popoverEnterAnimation, popoverLeaveAnimation } from '@rdlabo/ionic-theme-ios27';\n+\n+ function loadIOSAnimations() {\n+  if (typeof CSS === 'undefined') return {};\n+  if (!CSS.supports('overflow-anchor: auto') && !CSS.supports('text-wrap: pretty')) return {};\n+\n+  return {\n+    navAnimation: iosTransitionAnimation,\n+    popoverEnter: popoverEnterAnimation,\n+    popoverLeave: popoverLeaveAnimation,\n+  };\n+ }\n\n  provideIonicAngular({\n-  navAnimation: isPlatform('ios') ? iosTransitionAnimation : undefined,\n-  popoverEnter: isPlatform('ios') ? popoverEnterAnimation : undefined,\n-  popoverLeave: isPlatform('ios') ? popoverLeaveAnimation : undefined,\n+  ...(isPlatform('ios') ? loadIOSAnimations() : {}),\n  });\n

The iOS 27 page transition and popover animations serve both styled generations. Older browsers keep Ionic's defaults. The example uses Angular; pass the same options to React's setupIonicReact or Vue's IonicVue.

\n

4. Update customizations

\n

Rename theme variables and opt-out classes used by your app. For example:

\n
  ion-content {\n-  --ios26-content-box-shadow-rgb: 0, 0, 0;\n+  --ios-theme-content-box-shadow-rgb: 0, 0, 0;\n  }\n\n- <ion-button class=\"ios26-disabled\">Standard Ionic button</ion-button>\n+ <ion-button class=\"ios-theme-disabled\">Standard Ionic button</ion-button>\n

The old names remain as deprecated fallbacks. Check the resulting screens in light and dark modes on the browsers you support.

\n

iOS 27 only

\n

To switch entirely to iOS 27, remove the iOS 26 package and replace its stylesheet and animation imports. The stylesheet changes are:

\n
- @use '@rdlabo/ionic-theme-ios26/src/styles/default-variables.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/default-variables.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27-dark-class.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/md-remove-ios-class-effect.scss';\n

Change the animation import from @rdlabo/ionic-theme-ios26 to @rdlabo/ionic-theme-ios27; the existing isPlatform('ios') configuration can stay. See the README's iOS 27-only setup. Unconditional imports apply the new styles on every browser using Ionic iOS mode.

\n

iOS 27 naming

\n

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.

\n

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.

\n

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.

\n

See Special markup and classes and Default variables for the current names.

\n

Earlier migration notes are preserved in the iOS 26 migration guide.

\n

Submit button appearance

\n

Submit buttons now use each Ionic color's standard contrast value and an iOS 27 directional edge treatment. Remove the theme-specific brightness variables.

\n
  :root {\n-  --ion-color-primary-brightness-rgb: 130, 255, 255;\n-  --ion-color-primary-brightness: #96feff;\n  }\n
", + "html": "

Native UI Shell transition adapter

\n

Use withNativeUIShellTransition() to keep your existing Ionic navigation animation while coordinating Native UI Shell controls.

\n\n
+ import { withNativeUIShellTransition } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\n  const ionicConfig = {\n-   navAnimation: existingTransition,\n+   navAnimation: withNativeUIShellTransition(existingTransition),\n  };\n

Merge this option into your existing Ionic configuration before initialization. The adapter preserves the animation's effects, duration, and easing while coordinating native retirement, swipe progress, and cancellation. Keep your existing theme stylesheet imports and Native UI Shell or Vertical Control Area startup.

\n

Use the adapter only for navigation; leave modal and popover animations unchanged. Your builder must create a fresh Animation for each navigation because Ionic destroys it afterward. Keep lifecycle events for control registration and transitions without animation. If the custom builder animates a horizontal back button separately, exclude that effect while .ios-theme-vertical-bars is active.

\n

See Connect your navigation animation for the setup and supported scope.

\n

From the iOS 26 theme

\n

For an app using @rdlabo/ionic-theme-ios26, the recommended migration keeps that package and adds @rdlabo/ionic-theme-ios27. The README setup selects iOS 27 or iOS 26 styles by browser capability and leaves Ionic's default iOS appearance on older browsers.

\n

1. Add the new package

\n

Keep the iOS 26 package and add iOS 27. The new theme requires @ionic/core 8.8.1 or later (Ionic 8 or 9).

\n
npm install @rdlabo/ionic-theme-ios27\n

2. Make the styles adaptive

\n

Replace unconditional iOS 26 imports in your global Sass stylesheet with two mutually exclusive branches. This example uses class-based dark mode:

\n
+ @use 'sass:meta';\n+\n- @use '@rdlabo/ionic-theme-ios26/src/styles/default-variables.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect.scss';\n+ @supports (overflow-anchor: auto) {\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/default-variables');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27-dark-class');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/md-remove-ios-class-effect');\n+ }\n+\n+ @supports (text-wrap: pretty) and (not (overflow-anchor: auto)) {\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/default-variables');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect');\n+ }\n

Keep Ionic's matching dark palette. For system or always-dark mode, replace both -dark-class imports with the matching variant. If you use md-ion-list-inset, load the corresponding package's stylesheet inside each branch. Browsers without either feature retain Ionic's default styling.

\n

3. Switch the animations

\n

Replace the iOS 26 animation import and gate the iOS 27 animations on the same browser features. Resolve the options before Ionic initializes:

\n
- import { iosTransitionAnimation, popoverEnterAnimation, popoverLeaveAnimation } from '@rdlabo/ionic-theme-ios26';\n+ import { iosTransitionAnimation, popoverEnterAnimation, popoverLeaveAnimation } from '@rdlabo/ionic-theme-ios27';\n+\n+ function loadIOSAnimations() {\n+  if (typeof CSS === 'undefined') return {};\n+  if (!CSS.supports('overflow-anchor: auto') && !CSS.supports('text-wrap: pretty')) return {};\n+\n+  return {\n+    navAnimation: iosTransitionAnimation,\n+    popoverEnter: popoverEnterAnimation,\n+    popoverLeave: popoverLeaveAnimation,\n+  };\n+ }\n\n  provideIonicAngular({\n-  navAnimation: isPlatform('ios') ? iosTransitionAnimation : undefined,\n-  popoverEnter: isPlatform('ios') ? popoverEnterAnimation : undefined,\n-  popoverLeave: isPlatform('ios') ? popoverLeaveAnimation : undefined,\n+  ...(isPlatform('ios') ? loadIOSAnimations() : {}),\n  });\n

The iOS 27 page transition and popover animations serve both styled generations. Older browsers keep Ionic's defaults. The example uses Angular; pass the same options to React's setupIonicReact or Vue's IonicVue.

\n

4. Update customizations

\n

Rename theme variables and opt-out classes used by your app. For example:

\n
  ion-content {\n-  --ios26-content-box-shadow-rgb: 0, 0, 0;\n+  --ios-theme-content-box-shadow-rgb: 0, 0, 0;\n  }\n\n- <ion-button class=\"ios26-disabled\">Standard Ionic button</ion-button>\n+ <ion-button class=\"ios-theme-disabled\">Standard Ionic button</ion-button>\n

The old names remain as deprecated fallbacks. Check the resulting screens in light and dark modes on the browsers you support.

\n

iOS 27 only

\n

To switch entirely to iOS 27, remove the iOS 26 package and replace its stylesheet and animation imports. The stylesheet changes are:

\n
- @use '@rdlabo/ionic-theme-ios26/src/styles/default-variables.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/default-variables.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27-dark-class.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/md-remove-ios-class-effect.scss';\n

Change the animation import from @rdlabo/ionic-theme-ios26 to @rdlabo/ionic-theme-ios27; the existing isPlatform('ios') configuration can stay. See the README's iOS 27-only setup. Unconditional imports apply the new styles on every browser using Ionic iOS mode.

\n

iOS 27 naming

\n

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.

\n

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.

\n

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.

\n

See Special markup and classes and Default variables for the current names.

\n

Earlier migration notes are preserved in the iOS 26 migration guide.

\n

Submit button appearance

\n

Submit buttons now use each Ionic color's standard contrast value and an iOS 27 directional edge treatment. Remove the theme-specific brightness variables.

\n
  :root {\n-  --ion-color-primary-brightness-rgb: 130, 255, 255;\n-  --ion-color-primary-brightness: #96feff;\n  }\n
", "headings": [ + { + "id": "native-ui-shell-transition-adapter", + "text": "Native UI Shell transition adapter", + "level": 2 + }, { "id": "from-the-ios-26-theme", "text": "From the iOS 26 theme", @@ -630,7 +645,7 @@ export const PROJECT = { "file": "api.md", "section": "Reference", "path": "/projects/ionic-theme-ios27/docs/api", - "html": "

Reference for the JavaScript API exported by @rdlabo/ionic-theme-ios27 v1.2.0-0. CSS and Sass entry points remain documented in the README.

\n

Effects

\n

function registerTabBarEffect

(targetElement: HTMLElement) => registeredEffect | undefined

Registers the liquid-glass selection effect for an Ionic tab bar.

\n\n\n

function registerSegmentEffect

(targetElement: HTMLElement) => registeredEffect | undefined

Registers the liquid-glass selection effect for an Ionic segment.

\n\n\n

interface registeredEffect

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberTypeDescription
destroy() => voidRemoves listeners and effect elements created by registration.
\n\n

interface EffectScales

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropTypeDescription
smallstringSmall effect scale.
mediumstringMedium effect scale.
largestringLarge effect scale.
xlargestringExtra-large effect scale.
\n\n

Searchable tab bar

\n

function attachTabBarSearchable

(ionTabBar: HTMLElement, ionFabButton: HTMLElement, ionFooter: HTMLElement) => TabBarSearchableFunction

Attaches the searchable tab-bar transition and returns its event handler.

\n\n\n

enum TabBarSearchableType

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberValueDescription
Enter\"enter\"Enters searchable mode.
Leave\"leave\"Leaves searchable mode.
\n\n

type alias TabBarSearchableFunction

(event: Event, type: TabBarSearchableType) => Promise<void>

\n\n

Animations

\n

function iosTransitionAnimation

(navEl: HTMLElement, opts: TransitionOptions) => Animation

Builds the package's iOS navigation transition.

\n\n\n

function setConfig

(config: Partial<IosTransitionConfig>) => void

Sets the page-transition radius. It defaults to 0; native apps can supply the measured WebView radius.

\n\n\n

interface IosTransitionConfig

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropTypeDescription
radiusnumberPage-transition corner radius.
\n\n

function popoverEnterAnimation

(baseEl: HTMLElement, opts?: any) => Animation

Builds the iOS popover enter animation.

\n\n\n

function popoverLeaveAnimation

(baseEl: HTMLElement) => Animation

Builds the iOS popover leave animation.

\n\n\n

Searchbar

\n

function supportSeachbarCancelButtonIcon

(searchbar: HTMLIonSearchbarElement) => SearchbarCancelButtonIconSupport

Temporary rendering support for Ionic's cancelButtonIcon in iOS mode. Import Seachbar with this spelling, as exported by the package. Pass an initialized element.

\n\n\n

interface SearchbarCancelButtonIconSupport

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberTypeDescription
refresh() => voidRe-read cancelButtonIcon after changing the JavaScript property.
destroy() => voidRemove the observer and inserted icon, restoring text content.
\n\n

Native UI Shell (Experimental)

\n

Import these APIs and types from @rdlabo/ionic-theme-ios27/native. See the Native UI Shell guide for requirements and fallback behavior.

\n

function enableNativeUIShell

(options?: NativeUIShellOptions) => Promise<NativeUIShellHandle>

Call once at startup. Repeated calls with the same configuration share the active runtime; a different configuration while it is active throws. Unsupported environments return a handle in the Web state. Set enabled: false to stop active projection and use Web controls.

\n\n\n

function configureNativeTransition

() => Promise<WebViewMetrics>

Reads the native WebView radius and applies it to page transitions without enabling native controls. On other platforms, the radius is 0.

\n\n\n

interface NativeUIShellOptions

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropTypeDescription
enabledbooleanEnable native projection globally; defaults to true.
controlsNativeUIShellControlsWhen set, only controls explicitly set to true qualify.
\n\n

interface NativeUIShellControls

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropTypeDescription
tabsbooleanTab bars and native search.
toolbarbooleanToolbar, back, and menu buttons.
segmentbooleanSegments.
fabbooleanFloating action buttons.
\n\n

interface NativeUIShellHandle

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberTypeDescription
getStatus() => NativeUIShellStatusRead the current status.
suspend() => Promise<NativeUIShellSuspension>Restore controls to the Web until the lease is resumed.
destroy() => Promise<void>Restore Web rendering and release native controls and the runtime.
\n\n

interface NativeUIShellSuspension

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberTypeDescription
resume() => Promise<void>Release this suspension. Native projection resumes after all active suspensions are released.
\n\n

interface WebViewMetrics

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropTypeDescription
radiusnumberNative WebView corner radius.
\n\n

interface NativeUIShellStatus

interface NativeUIShellStatus {\n  state: 'web' | 'native' | 'stopped';\n  projected: number;\n  updates: number;\n  reason?: string;\n}\n

projected counts projected controls, updates counts updates, and reason explains Web fallback or stopping. A stopped runtime does not automatically reconnect after a bridge failure; destroy the handle before enabling again.

\n\n

type alias NativeUIShellComponent

'ion-button' | 'ion-buttons' | 'ion-back-button' | 'ion-menu-button' | 'ion-tab-bar' | 'ion-segment' | 'ion-fab'

Union of component tags handled by the runtime. See the guide for individual eligibility requirements.

\n\n\n

iPhone Duo / Vertical Control Area (Experimental)

\n

Import these APIs from @rdlabo/ionic-theme-ios27/vertical-bars or @rdlabo/ionic-theme-ios27/native. The standalone entry point works without the iOS 27 theme or the full Native UI Shell. See iPhone Duo support for setup, toolchain requirements, and Web fallback.

\n

function enableVerticalControlArea

() => Promise<VerticalControlAreaHandle>

Starts the runtime for controls in the vertical area only. Start either this runtime or enableNativeUIShell(). Repeated calls with the same configuration share it; a different active configuration throws.

\n\n\n

function setVerticalControlAreaPlacement

(placement: VerticalBarEdge | VerticalBarPlacement, rtl?: boolean) => void

Applies the application's chosen placement to CSS and Web/native controls after ion-app is mounted. Logical edges resolve through the nearest dir attribute or explicit rtl. Pass null to restore ordinary layout.

\n\n\n

interface VerticalControlAreaHandle

Extends NativeUIShellHandle with setPlacement, the same function as setVerticalControlAreaPlacement.

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberTypeDescription
setPlacement(placement: VerticalBarEdge | VerticalBarPlacement, rtl?: boolean) => voidApply placement to Web and native controls.
\n\n\n

type alias VerticalBarEdge

'leading' | 'trailing' | null

Logical edge in the reading direction; null means no vertical rail.

\n\n\n

interface VerticalBarPlacement

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropTypeDescription
edgeVerticalBarEdgeLogical rail edge.
insetnumberUIKit safe-area inset in points.
\n\n

enum HingeStatus

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberValue
Closed\"closed\"
PartiallyOpen\"partiallyOpen\"
FullyOpen\"fullyOpen\"
\n\n

interface DeviceLayout

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropTypeDescription
placementVerticalBarPlacementRail edge and measured safe-area inset.
hingeStatusHingeStatus | nullHinge posture; null when unavailable.
webViewMetricsWebViewMetricsEffective WebView corner radius.
\n\n

module IonicNativeUIShell

The bundled Capacitor plugin exposes device-layout reporting without starting a projection runtime. It has no Web implementation; guard calls with Capacitor.getPlatform() === 'ios'.

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberTypeDescription
getDeviceLayout() => Promise<DeviceLayout>Read the initial layout or take a one-shot measurement.
startDeviceLayoutMonitoring() => Promise<void>Acquire a monitoring reference.
stopDeviceLayoutMonitoring() => Promise<void>Release a reference; events stop after the last release.
addListener(name: 'deviceLayoutChange', listener: (event: DeviceLayout) => void) => Promise<PluginListenerHandle>Subscribe to layout changes; remove the returned listener when finished.

An active native projection runtime already holds a monitoring reference, so its consumers only subscribe and read the initial value. Bridge snapshot and activation methods are internal implementation details.

\n\n\n\n
", + "html": "

Reference for the JavaScript API exported by @rdlabo/ionic-theme-ios27 v1.2.0-1. CSS and Sass entry points remain documented in the README.

\n

Effects

\n

function registerTabBarEffect

(targetElement: HTMLElement) => registeredEffect | undefined

Registers the liquid-glass selection effect for an Ionic tab bar.

\n\n\n

function registerSegmentEffect

(targetElement: HTMLElement) => registeredEffect | undefined

Registers the liquid-glass selection effect for an Ionic segment.

\n\n\n

interface registeredEffect

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberTypeDescription
destroy() => voidRemoves listeners and effect elements created by registration.
\n\n

interface EffectScales

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropTypeDescription
smallstringSmall effect scale.
mediumstringMedium effect scale.
largestringLarge effect scale.
xlargestringExtra-large effect scale.
\n\n

Searchable tab bar

\n

function attachTabBarSearchable

(ionTabBar: HTMLElement, ionFabButton: HTMLElement, ionFooter: HTMLElement) => TabBarSearchableFunction

Attaches the searchable tab-bar transition and returns its event handler.

\n\n\n

enum TabBarSearchableType

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberValueDescription
Enter\"enter\"Enters searchable mode.
Leave\"leave\"Leaves searchable mode.
\n\n

type alias TabBarSearchableFunction

(event: Event, type: TabBarSearchableType) => Promise<void>

\n\n

Animations

\n

function withNativeUIShellTransition

(builder: AnimationBuilder) => AnimationBuilder

Wraps an Ionic navigation animation builder to coordinate native control retirement, swipe progress, and cancellation while preserving the returned animation. Exported from the package root and /vertical-bars. Register it as navAnimation; use a fresh Animation for each navigation. The package's iosTransitionAnimation already includes this adapter. See existing-theme setup for Ionic's default and custom builders.

\n\n\n

function iosTransitionAnimation

(navEl: HTMLElement, opts: TransitionOptions) => Animation

Builds the package's iOS navigation transition.

\n\n\n

function setConfig

(config: Partial<IosTransitionConfig>) => void

Sets the page-transition radius. It defaults to 0; native apps can supply the measured WebView radius.

\n\n\n

interface IosTransitionConfig

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropTypeDescription
radiusnumberPage-transition corner radius.
\n\n

function popoverEnterAnimation

(baseEl: HTMLElement, opts?: any) => Animation

Builds the iOS popover enter animation.

\n\n\n

function popoverLeaveAnimation

(baseEl: HTMLElement) => Animation

Builds the iOS popover leave animation.

\n\n\n

Searchbar

\n

function supportSeachbarCancelButtonIcon

(searchbar: HTMLIonSearchbarElement) => SearchbarCancelButtonIconSupport

Temporary rendering support for Ionic's cancelButtonIcon in iOS mode. Import Seachbar with this spelling, as exported by the package. Pass an initialized element.

\n\n\n

interface SearchbarCancelButtonIconSupport

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberTypeDescription
refresh() => voidRe-read cancelButtonIcon after changing the JavaScript property.
destroy() => voidRemove the observer and inserted icon, restoring text content.
\n\n

Native UI Shell (Experimental)

\n

Import these APIs and types from @rdlabo/ionic-theme-ios27/native. See the Native UI Shell guide for requirements and fallback behavior.

\n

function enableNativeUIShell

(options?: NativeUIShellOptions) => Promise<NativeUIShellHandle>

Call once at startup. Repeated calls with the same configuration share the active runtime; a different configuration while it is active throws. Unsupported environments return a handle in the Web state. Set enabled: false to stop active projection and use Web controls.

\n\n\n

function configureNativeTransition

() => Promise<WebViewMetrics>

Reads the native WebView radius and applies it to page transitions without enabling native controls. On other platforms, the radius is 0.

\n\n\n

interface NativeUIShellOptions

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropTypeDescription
enabledbooleanEnable native projection globally; defaults to true.
controlsNativeUIShellControlsWhen set, only controls explicitly set to true qualify.
\n\n

interface NativeUIShellControls

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropTypeDescription
tabsbooleanTab bars and native search.
toolbarbooleanToolbar, back, and menu buttons.
segmentbooleanSegments.
fabbooleanFloating action buttons.
\n\n

interface NativeUIShellHandle

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberTypeDescription
getStatus() => NativeUIShellStatusRead the current status.
suspend() => Promise<NativeUIShellSuspension>Restore controls to the Web until the lease is resumed.
destroy() => Promise<void>Restore Web rendering and release native controls and the runtime.
\n\n

interface NativeUIShellSuspension

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberTypeDescription
resume() => Promise<void>Release this suspension. Native projection resumes after all active suspensions are released.
\n\n

interface WebViewMetrics

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropTypeDescription
radiusnumberNative WebView corner radius.
\n\n

interface NativeUIShellStatus

interface NativeUIShellStatus {\n  state: 'web' | 'native' | 'stopped';\n  projected: number;\n  updates: number;\n  reason?: string;\n}\n

projected counts projected controls, updates counts updates, and reason explains Web fallback or stopping. A stopped runtime does not automatically reconnect after a bridge failure; destroy the handle before enabling again.

\n\n

type alias NativeUIShellComponent

'ion-button' | 'ion-buttons' | 'ion-back-button' | 'ion-menu-button' | 'ion-tab-bar' | 'ion-segment' | 'ion-fab'

Union of component tags handled by the runtime. See the guide for individual eligibility requirements.

\n\n\n

iPhone Duo / Vertical Control Area (Experimental)

\n

Import these APIs from @rdlabo/ionic-theme-ios27/vertical-bars or @rdlabo/ionic-theme-ios27/native. The standalone entry point works without the iOS 27 theme or the full Native UI Shell. See iPhone Duo support for setup, toolchain requirements, and Web fallback.

\n

function enableVerticalControlArea

() => Promise<VerticalControlAreaHandle>

Starts the runtime for controls in the vertical area only. Start either this runtime or enableNativeUIShell(). Repeated calls with the same configuration share it; a different active configuration throws.

\n\n\n

function setVerticalControlAreaPlacement

(placement: VerticalBarEdge | VerticalBarPlacement, rtl?: boolean) => void

Applies the application's chosen placement to CSS and Web/native controls after ion-app is mounted. Logical edges resolve through the nearest dir attribute or explicit rtl. Pass null to restore ordinary layout.

\n\n\n

interface VerticalControlAreaHandle

Extends NativeUIShellHandle with setPlacement, the same function as setVerticalControlAreaPlacement.

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberTypeDescription
setPlacement(placement: VerticalBarEdge | VerticalBarPlacement, rtl?: boolean) => voidApply placement to Web and native controls.
\n\n\n

type alias VerticalBarEdge

'leading' | 'trailing' | null

Logical edge in the reading direction; null means no vertical rail.

\n\n\n

interface VerticalBarPlacement

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropTypeDescription
edgeVerticalBarEdgeLogical rail edge.
insetnumberUIKit safe-area inset in points.
\n\n

enum HingeStatus

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberValue
Closed\"closed\"
PartiallyOpen\"partiallyOpen\"
FullyOpen\"fullyOpen\"
\n\n

interface DeviceLayout

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropTypeDescription
placementVerticalBarPlacementRail edge and measured safe-area inset.
hingeStatusHingeStatus | nullHinge posture; null when unavailable.
webViewMetricsWebViewMetricsEffective WebView corner radius.
\n\n

module IonicNativeUIShell

The bundled Capacitor plugin exposes device-layout reporting without starting a projection runtime. It has no Web implementation; guard calls with Capacitor.getPlatform() === 'ios'.

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberTypeDescription
getDeviceLayout() => Promise<DeviceLayout>Read the initial layout or take a one-shot measurement.
startDeviceLayoutMonitoring() => Promise<void>Acquire a monitoring reference.
stopDeviceLayoutMonitoring() => Promise<void>Release a reference; events stop after the last release.
addListener(name: 'deviceLayoutChange', listener: (event: DeviceLayout) => void) => Promise<PluginListenerHandle>Subscribe to layout changes; remove the returned listener when finished.

An active native projection runtime already holds a monitoring reference, so its consumers only subscribe and read the initial value. Bridge snapshot and activation methods are internal implementation details.

\n\n\n\n
", "headings": [ { "id": "effects", @@ -682,6 +697,11 @@ export const PROJECT = { "text": "Animations", "level": 2 }, + { + "id": "function-withnativeuishelltransition", + "text": "function withNativeUIShellTransition", + "level": 4 + }, { "id": "function-iostransitionanimation", "text": "function iosTransitionAnimation", diff --git a/projects/docs/src/app/generated/projects/ionic-theme-ios27.ja.generated.ts b/projects/docs/src/app/generated/projects/ionic-theme-ios27.ja.generated.ts index 2306e70..a0e1a5e 100644 --- a/projects/docs/src/app/generated/projects/ionic-theme-ios27.ja.generated.ts +++ b/projects/docs/src/app/generated/projects/ionic-theme-ios27.ja.generated.ts @@ -7,10 +7,10 @@ export const PROJECT = { "packageName": "@rdlabo/ionic-theme-ios27", "repositoryUrl": "https://github.com/rdlabo-dev/ionic-theme-ios27", "demoUrl": "https://ionic-theme-ios27.rdlabo.dev/", - "releaseNotesUrl": "https://github.com/rdlabo-dev/ionic-theme-ios27/releases/tag/ios27-v1.2.0-0", + "releaseNotesUrl": "https://github.com/rdlabo-dev/ionic-theme-ios27/releases/tag/ios27-v1.2.0-1", "category": "frontend-tools", "icon": "theme", - "version": "1.2.0-0", + "version": "1.2.0-1", "description": "Ionic向けの安定版iOS 27スタイルとアニメーション。実験的なNative UI Shellも選択できます。", "headline": "IonicアプリにiOS 27デザインを取り入れる", "overview": "IonicコンポーネントにiOS 27のLiquid Glassとアニメーションを適用します。iOS 26テーマと併用するとブラウザ機能に応じてスタイルを切り替えられます。Native UI Shellは実験的機能です。", @@ -32,7 +32,7 @@ export const PROJECT = { "description": "必要に応じてダークモードスタイルを導入し、コンポーネント単位で移行できます。" } ], - "overviewHtml": "

\n \"iOS\n \"iOS\n \"iOS\n

", + "overviewHtml": "

\n \"iOS\n \"iOS\n \"iOS\n

", "path": "/projects/ionic-theme-ios27", "pages": [ { @@ -43,7 +43,7 @@ export const PROJECT = { "file": "readme.md", "section": "クイックスタート", "path": "/projects/ionic-theme-ios27/docs/readme", - "html": "

IonicアプリにiOS 27のLiquid Glassとアニメーションを適用するテーマです。Capacitor iOSアプリでは、対応する操作部品に実験的なNative UI Shellを選択できます。

\n

Ionic 9デモ · Ionic 8デモ · 1.2.0-0リリースノート

\n

機能

\n

IonicにiOS 27の外観を適用する

\n

Liquid Glass、ツールバー、タブ、リスト、ボタン、検索、オーバーレイ、画面遷移をiOS 27の外観に整えます。ライト・ダーク両モードに対応します。Ionic 9デモとIonic 8デモで確認できます。

\n

IonicのUIをネイティブ表示する

\n

任意の実験的なNative UI Shellは、Capacitor iOS上で対応する固定コントロールを既存のIonicマークアップから読み取り、UIKitで描画します。テキスト、対応するアイコン、選択状態を反映し、ネイティブ側の操作は元のIonicコンポーネントへ戻します。ページ内容とルーティングはWebViewに残り、非対応のレイアウトはWebで表示します。

\n

iOS 27のタブドラッグ: 同じLibrary画面をNative UI Shellオフ(Web)とオン(UIKit)で比較しました。下段はタブバー周辺を拡大しています。

\n

\"Native

\n

利用者の端末に合わせる

\n

iOS 26とiOS 27の両テーマを導入すると、ブラウザの機能に応じて各世代のスタイルを選択できます。OSのバージョン番号は読み取りません。どちらの条件も満たさない古いブラウザはIonic標準のiOS外観を維持します。両テーマを使う場合も画面遷移にはiOS 27のアニメーションを使用します。Capacitor iOSのNative UI Shellは実行中のOSの材質に従います。

\n

導入

\n

既存のIonic 8または9アプリで、両テーマをインストールします(~@ionic/core~ 8.8.1以降が必要です)。

\n
npm install @rdlabo/ionic-theme-ios26 @rdlabo/ionic-theme-ios27\n

グローバルSass(例: ~src/styles.scss~)で、ブラウザの機能に応じてスタイルを読み込みます。

\n
@use 'sass:meta';\n\n@supports (overflow-anchor: auto) {\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/default-variables');\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27');\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27-dark-class');\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/md-remove-ios-class-effect');\n}\n\n@supports (text-wrap: pretty) and (not (overflow-anchor: auto)) {\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/default-variables');\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26');\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class');\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect');\n}\n

~meta.load-css()~ で ~Can't find stylesheet to import.~ と表示された場合は、Sassから ~node_modules~ を参照できるようにします。Angularでは ~angular.json~ のアプリのビルド ~options~ に次を追加してください。

\n
\"stylePreprocessorOptions\": {\n  \"includePaths\": [\"node_modules\"]\n}\n

または、~meta.load-css()~ を書いたSassファイルからインストール済みパッケージへの相対パスを使います。例えば ~src/styles.scss~ なら ~../node_modules/@rdlabo/ionic-theme-ios27/src/styles/default-variables~ です。ファイルの位置に合わせて ~../~ の数を調整し、各テーマの読み込み先にも同じ変更を適用してください。

\n

これはOS判定ではなくブラウザの機能判定です。どちらにも対応しないブラウザはIonic標準のiOS外観を維持します。例ではclassベースのダークモードを使うため、Ionic側の対応するダークパレットも読み込んでください。system/alwaysの場合は両方の ~-dark-class~ を対応するvariantに置き換えます。~md-remove-ios-class-effect~ は、同じマークアップをMaterial Designモードでも使う場合のiOS固有クラスの影響を防ぎます。

\n

アニメーションを設定する

\n

どちらの世代のスタイルでも、iOS 27の画面遷移とpopoverアニメーションを使います。Ionicの初期化前に設定してください。Angularの例です。

\n
import { isPlatform, provideIonicAngular } from '@ionic/angular/standalone'; // Ionic 8\nimport { iosTransitionAnimation, popoverEnterAnimation, popoverLeaveAnimation } from '@rdlabo/ionic-theme-ios27';\n\nfunction loadIOSAnimations() {\n  if (typeof CSS === 'undefined') return {};\n  if (!CSS.supports('overflow-anchor: auto') && !CSS.supports('text-wrap: pretty')) return {};\n\n  return {\n    navAnimation: iosTransitionAnimation,\n    popoverEnter: popoverEnterAnimation,\n    popoverLeave: popoverLeaveAnimation,\n  };\n}\n\nprovideIonicAngular(isPlatform('ios') ? loadIOSAnimations() : {});\n

Ionic 9のAngularでは ~isPlatform~ と ~provideIonicAngular~ を ~@ionic/angular~ からimportします。ReactとVueでは同じオプションを初期化時に ~setupIonicReact~ または ~IonicVue~ へ渡します。SSRではブラウザ初期化時に判定してください。

\n

画面遷移の角丸半径の既定値は ~0~ です。ネイティブアプリではWebViewを計測した後に変更できます。

\n
import { setConfig } from '@rdlabo/ionic-theme-ios27';\n\nsetConfig({ radius });\n

テーマを確認する

\n

iOS上で確認してください。デスクトップでプレビューする場合は、既存のIonic初期化設定で ~mode: 'ios'~ を指定します。inset listは~ion-item-group~ を使う構造が必要です。

\n
<ion-list mode=\"ios\" inset=\"true\">\n  <ion-item-group>\n    <ion-item><ion-label>Notifications</ion-label></ion-item>\n    <ion-item><ion-label>Appearance</ion-label></ion-item>\n  </ion-item-group>\n</ion-list>\n

オプション構成

\n

iOS 27テーマなしでiPhone Duoに対応する(実験的機能)

\n

既存のIonicテーマを維持し、タブと対応するtoolbarの操作部品を縦の側面領域へ移します。まずChromeで試せます。 スタイルシート、appのクラス、enableVerticalControlArea() で始め、iPhone Duoの端末イベントと接続してシステム操作領域とヒンジの状態を利用できます。

\n

ブラウザでのプレビューとiOSの設定は既存テーマでiPhone Duoに対応する、共通のレイアウト規則とAPIはiPhone Duo対応を参照してください。1.2.0-0 で利用できる実験的機能で、APIと対応する動作は変更される可能性があります。

\n

iOS 27テーマだけを使う

\n

~@rdlabo/ionic-theme-ios27~ だけをインストールし、グローバルスタイルシートで無条件に読み込みます。

\n
@import '@rdlabo/ionic-theme-ios27/dist/css/default-variables.css';\n@import '@rdlabo/ionic-theme-ios27/dist/css/ionic-theme-ios27.css';\n@import '@rdlabo/ionic-theme-ios27/dist/css/md-remove-ios-class-effect.css';\n@import '@rdlabo/ionic-theme-ios27/dist/css/ionic-theme-ios27-dark-class.css';\n

最後のimportはclassベースのダークモード用です。別の方式では ~-dark-system~ または ~-dark-always~ と、対応するIonicパレットを選びます。アニメーションは上の例からブラウザ機能判定を外し、~isPlatform('ios')~ で設定します。

\n

その他のオプション

\n\n

MD3テーマと併用する

\n

iOS 27対応SafariではiOS 27テーマ、その直前のSafari世代ではiOS 26テーマを使い、IonicがMaterial Designモードで動作するときはMD3テーマを使うには、3つのテーマをすべてインストールします。いずれも@ionic/core 8.8.1以降が必要です。

\n
npm install @rdlabo/ionic-theme-ios26 @rdlabo/ionic-theme-ios27 @rdlabo/ionic-theme-md3\n

2つのiOSテーマにはデフォルト構成と同じブラウザ機能判定を適用し、MD3は無条件に読み込みます。すべてmeta.load-css()で読み込むことで、生成されるCSSでも条件付きのiOSスタイルより後にMD3スタイルが配置されます。

\n
@use 'sass:meta';\n\n@supports (overflow-anchor: auto) {\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/default-variables');\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27');\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27-dark-class');\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/md-remove-ios-class-effect');\n}\n\n@supports (text-wrap: pretty) and (not (overflow-anchor: auto)) {\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/default-variables');\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26');\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class');\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect');\n}\n\n@include meta.load-css('@rdlabo/ionic-theme-md3/dist/css/default-variables.css');\n@include meta.load-css('@rdlabo/ionic-theme-md3/dist/css/ionic-theme-md3.css');\n

iOSスタイルはIonicのiosモードだけに適用され、MD3はmdモードに適用されます。各iOS分岐のmd-remove-ios-class-effectは、iOS専用のユーティリティクラスがMDモードへ影響するのを防ぎます。MD3にはinset listのスタイルが含まれるため、この構成ではiOSパッケージの任意のmd-ion-list-insetを読み込まないでください。

\n

Ionicの対応するダークパレットも読み込んでください。この例はclassベースのダークモードです。systemまたはalways-darkを使う場合は、Ionicと両方のiOSテーマで同じ方式を選びます。

\n

両方のiOSテーマ世代ではiOS 27の画面遷移を使い、Material DesignモードではMD3の画面遷移を選択します。上記のアニメーション設定を次のように拡張します。

\n
import { isPlatform, provideIonicAngular } from '@ionic/angular/standalone'; // Ionic 8\nimport { iosTransitionAnimation, popoverEnterAnimation, popoverLeaveAnimation } from '@rdlabo/ionic-theme-ios27';\nimport { mdTransitionAnimation } from '@rdlabo/ionic-theme-md3';\n\nfunction loadAnimations() {\n  if (!isPlatform('ios')) return { navAnimation: mdTransitionAnimation };\n  if (typeof CSS === 'undefined') return {};\n  if (!CSS.supports('overflow-anchor: auto') && !CSS.supports('text-wrap: pretty')) return {};\n\n  return {\n    navAnimation: iosTransitionAnimation,\n    popoverEnter: popoverEnterAnimation,\n    popoverLeave: popoverLeaveAnimation,\n  };\n}\n\nprovideIonicAngular(loadAnimations());\n

Ionic 9のAngularでは、isPlatformとprovideIonicAngularを@ionic/angularからimportします。ReactとVueでは、同じ戻り値をsetupIonicReactまたはIonicVueへ渡せます。Sassがmeta.load-css()内のパッケージを解決できない場合は、Get startedに記載したstylePreprocessorOptions.includePathsまたは相対パスの設定を使ってください。

\n

ドキュメント

\n\n", + "html": "

IonicアプリにiOS 27のLiquid Glassとアニメーションを適用するテーマです。Capacitor iOSアプリでは、対応する操作部品に実験的なNative UI Shellを選択できます。

\n

Ionic 9デモ · Ionic 8デモ · 1.2.0-0リリースノート

\n

機能

\n

IonicにiOS 27の外観を適用する

\n

Liquid Glass、ツールバー、タブ、リスト、ボタン、検索、オーバーレイ、画面遷移をiOS 27の外観に整えます。ライト・ダーク両モードに対応します。Ionic 9デモとIonic 8デモで確認できます。

\n

IonicのUIをネイティブ表示する

\n

任意の実験的なNative UI Shellは、Capacitor iOS上で対応する固定コントロールを既存のIonicマークアップから読み取り、UIKitで描画します。テキスト、対応するアイコン、選択状態を反映し、ネイティブ側の操作は元のIonicコンポーネントへ戻します。ページ内容とルーティングはWebViewに残り、非対応のレイアウトはWebで表示します。

\n

iOS 27のタブドラッグ: 同じLibrary画面をNative UI Shellオフ(Web)とオン(UIKit)で比較しました。下段はタブバー周辺を拡大しています。

\n

\"Native

\n

利用者の端末に合わせる

\n

iOS 26とiOS 27の両テーマを導入すると、ブラウザの機能に応じて各世代のスタイルを選択できます。OSのバージョン番号は読み取りません。どちらの条件も満たさない古いブラウザはIonic標準のiOS外観を維持します。両テーマを使う場合も画面遷移にはiOS 27のアニメーションを使用します。Capacitor iOSのNative UI Shellは実行中のOSの材質に従います。

\n

導入

\n

既存のIonic 8または9アプリで、両テーマをインストールします(~@ionic/core~ 8.8.1以降が必要です)。

\n
npm install @rdlabo/ionic-theme-ios26 @rdlabo/ionic-theme-ios27\n

グローバルSass(例: ~src/styles.scss~)で、ブラウザの機能に応じてスタイルを読み込みます。

\n
@use 'sass:meta';\n\n@supports (overflow-anchor: auto) {\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/default-variables');\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27');\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27-dark-class');\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/md-remove-ios-class-effect');\n}\n\n@supports (text-wrap: pretty) and (not (overflow-anchor: auto)) {\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/default-variables');\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26');\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class');\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect');\n}\n

~meta.load-css()~ で ~Can't find stylesheet to import.~ と表示された場合は、Sassから ~node_modules~ を参照できるようにします。Angularでは ~angular.json~ のアプリのビルド ~options~ に次を追加してください。

\n
\"stylePreprocessorOptions\": {\n  \"includePaths\": [\"node_modules\"]\n}\n

または、~meta.load-css()~ を書いたSassファイルからインストール済みパッケージへの相対パスを使います。例えば ~src/styles.scss~ なら ~../node_modules/@rdlabo/ionic-theme-ios27/src/styles/default-variables~ です。ファイルの位置に合わせて ~../~ の数を調整し、各テーマの読み込み先にも同じ変更を適用してください。

\n

これはOS判定ではなくブラウザの機能判定です。どちらにも対応しないブラウザはIonic標準のiOS外観を維持します。例ではclassベースのダークモードを使うため、Ionic側の対応するダークパレットも読み込んでください。system/alwaysの場合は両方の ~-dark-class~ を対応するvariantに置き換えます。~md-remove-ios-class-effect~ は、同じマークアップをMaterial Designモードでも使う場合のiOS固有クラスの影響を防ぎます。

\n

アニメーションを設定する

\n

どちらの世代のスタイルでも、iOS 27の画面遷移とpopoverアニメーションを使います。Ionicの初期化前に設定してください。Angularの例です。

\n
import { isPlatform, provideIonicAngular } from '@ionic/angular/standalone'; // Ionic 8\nimport { iosTransitionAnimation, popoverEnterAnimation, popoverLeaveAnimation } from '@rdlabo/ionic-theme-ios27';\n\nfunction loadIOSAnimations() {\n  if (typeof CSS === 'undefined') return {};\n  if (!CSS.supports('overflow-anchor: auto') && !CSS.supports('text-wrap: pretty')) return {};\n\n  return {\n    navAnimation: iosTransitionAnimation,\n    popoverEnter: popoverEnterAnimation,\n    popoverLeave: popoverLeaveAnimation,\n  };\n}\n\nprovideIonicAngular(isPlatform('ios') ? loadIOSAnimations() : {});\n

Ionic 9のAngularでは ~isPlatform~ と ~provideIonicAngular~ を ~@ionic/angular~ からimportします。ReactとVueでは同じオプションを初期化時に ~setupIonicReact~ または ~IonicVue~ へ渡します。SSRではブラウザ初期化時に判定してください。

\n

画面遷移の角丸半径の既定値は ~0~ です。ネイティブアプリではWebViewを計測した後に変更できます。

\n
import { setConfig } from '@rdlabo/ionic-theme-ios27';\n\nsetConfig({ radius });\n

テーマを確認する

\n

iOS上で確認してください。デスクトップでプレビューする場合は、既存のIonic初期化設定で ~mode: 'ios'~ を指定します。inset listは~ion-item-group~ を使う構造が必要です。

\n
<ion-list mode=\"ios\" inset=\"true\">\n  <ion-item-group>\n    <ion-item><ion-label>Notifications</ion-label></ion-item>\n    <ion-item><ion-label>Appearance</ion-label></ion-item>\n  </ion-item-group>\n</ion-list>\n

オプション構成

\n

iOS 27テーマなしでiPhone Duoに対応する(実験的機能)

\n

既存のIonicテーマを維持し、タブと対応するtoolbarの操作部品を縦の側面領域へ移します。まずChromeで試せます。 スタイルシート、appのクラス、enableVerticalControlArea() で始め、iPhone Duoの端末イベントと接続してシステム操作領域とヒンジの状態を利用できます。

\n

ブラウザでのプレビューとiOSの設定は既存テーマでiPhone Duoに対応する、共通のレイアウト規則とAPIはiPhone Duo対応を参照してください。1.2.0-0 で利用できる実験的機能で、APIと対応する動作は変更される可能性があります。

\n

iOS 27テーマだけを使う

\n

~@rdlabo/ionic-theme-ios27~ だけをインストールし、グローバルスタイルシートで無条件に読み込みます。

\n
@import '@rdlabo/ionic-theme-ios27/dist/css/default-variables.css';\n@import '@rdlabo/ionic-theme-ios27/dist/css/ionic-theme-ios27.css';\n@import '@rdlabo/ionic-theme-ios27/dist/css/md-remove-ios-class-effect.css';\n@import '@rdlabo/ionic-theme-ios27/dist/css/ionic-theme-ios27-dark-class.css';\n

最後のimportはclassベースのダークモード用です。別の方式では ~-dark-system~ または ~-dark-always~ と、対応するIonicパレットを選びます。アニメーションは上の例からブラウザ機能判定を外し、~isPlatform('ios')~ で設定します。

\n

その他のオプション

\n\n

MD3テーマと併用する

\n

iOS 27対応SafariではiOS 27テーマ、その直前のSafari世代ではiOS 26テーマを使い、IonicがMaterial Designモードで動作するときはMD3テーマを使うには、3つのテーマをすべてインストールします。いずれも@ionic/core 8.8.1以降が必要です。

\n
npm install @rdlabo/ionic-theme-ios26 @rdlabo/ionic-theme-ios27 @rdlabo/ionic-theme-md3\n

2つのiOSテーマにはデフォルト構成と同じブラウザ機能判定を適用し、MD3は無条件に読み込みます。すべてmeta.load-css()で読み込むことで、生成されるCSSでも条件付きのiOSスタイルより後にMD3スタイルが配置されます。

\n
@use 'sass:meta';\n\n@supports (overflow-anchor: auto) {\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/default-variables');\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27');\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27-dark-class');\n  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/md-remove-ios-class-effect');\n}\n\n@supports (text-wrap: pretty) and (not (overflow-anchor: auto)) {\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/default-variables');\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26');\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class');\n  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect');\n}\n\n@include meta.load-css('@rdlabo/ionic-theme-md3/dist/css/default-variables.css');\n@include meta.load-css('@rdlabo/ionic-theme-md3/dist/css/ionic-theme-md3.css');\n

iOSスタイルはIonicのiosモードだけに適用され、MD3はmdモードに適用されます。各iOS分岐のmd-remove-ios-class-effectは、iOS専用のユーティリティクラスがMDモードへ影響するのを防ぎます。MD3にはinset listのスタイルが含まれるため、この構成ではiOSパッケージの任意のmd-ion-list-insetを読み込まないでください。

\n

Ionicの対応するダークパレットも読み込んでください。この例はclassベースのダークモードです。systemまたはalways-darkを使う場合は、Ionicと両方のiOSテーマで同じ方式を選びます。

\n

両方のiOSテーマ世代ではiOS 27の画面遷移を使い、Material DesignモードではMD3の画面遷移を選択します。上記のアニメーション設定を次のように拡張します。

\n
import { isPlatform, provideIonicAngular } from '@ionic/angular/standalone'; // Ionic 8\nimport { iosTransitionAnimation, popoverEnterAnimation, popoverLeaveAnimation } from '@rdlabo/ionic-theme-ios27';\nimport { mdTransitionAnimation } from '@rdlabo/ionic-theme-md3';\n\nfunction loadAnimations() {\n  if (!isPlatform('ios')) return { navAnimation: mdTransitionAnimation };\n  if (typeof CSS === 'undefined') return {};\n  if (!CSS.supports('overflow-anchor: auto') && !CSS.supports('text-wrap: pretty')) return {};\n\n  return {\n    navAnimation: iosTransitionAnimation,\n    popoverEnter: popoverEnterAnimation,\n    popoverLeave: popoverLeaveAnimation,\n  };\n}\n\nprovideIonicAngular(loadAnimations());\n

Ionic 9のAngularでは、isPlatformとprovideIonicAngularを@ionic/angularからimportします。ReactとVueでは、同じ戻り値をsetupIonicReactまたはIonicVueへ渡せます。Sassがmeta.load-css()内のパッケージを解決できない場合は、Get startedに記載したstylePreprocessorOptions.includePathsまたは相対パスの設定を使ってください。

\n

ドキュメント

\n\n", "headings": [ { "id": "%E6%A9%9F%E8%83%BD", @@ -123,7 +123,7 @@ export const PROJECT = { "file": "using-ion-item-group.md", "section": "ガイド", "path": "/projects/ionic-theme-ios27/docs/using-ion-item-group", - "html": "

ほとんどのIonicマークアップは変更せずに使えます。ion-list で inset=\"true\" を使う場合は、itemを ion-item-group で囲み、ion-list-header はgroupの外に置きます。

\n

以下はframeworkに依存しないWeb Component形式のマークアップです。ReactまたはVueでは、各frameworkに対応するcomponentとpropertyの構文に置き換えてください。

\n
<ion-list inset=\"true\">\n  <ion-list-header><ion-label>Connections</ion-label></ion-list-header>\n  <ion-item-group>\n    <ion-item>...</ion-item>\n    <ion-item>...</ion-item>\n  </ion-item-group>\n</ion-list>\n

inset=\"true\" を使わないlistでは、このwrapperは不要です。

\n

ESLintでリストの構造を整える。

\n

wrapperが必要な理由

\n

Ionicは通常、ion-list 自体に背景を設定するため、ion-list-header もitemと同じsurface内に表示されます。iOS 27のlayoutではheaderとitemのsurfaceを分離します。

\n

\"ion-item-groupが必要な理由を示すinset

\n

そのため、このテーマは次のようにstyleを適用します。

\n\n

Material Designとのマークアップ共有

\n

@rdlabo/ionic-theme-md3 も同じgroup構造に対応しているため、両方のIonic modeで1つのtemplateを共有できます。

\n

このpackageを @rdlabo/ionic-theme-md3 なしで使うapplicationでは、Material modeにも同じgroup layoutを適用するため、任意のstylesheetをimportします。

\n
@import '@rdlabo/ionic-theme-ios27/dist/css/md-ion-list-inset.css';\n

2行itemとsection header groupについては 特別なマークアップとクラス を参照してください。

\n", + "html": "

ほとんどのIonicマークアップは変更せずに使えます。ion-list で inset=\"true\" を使う場合は、itemを ion-item-group で囲み、ion-list-header はgroupの外に置きます。

\n

以下はframeworkに依存しないWeb Component形式のマークアップです。ReactまたはVueでは、各frameworkに対応するcomponentとpropertyの構文に置き換えてください。

\n
<ion-list inset=\"true\">\n  <ion-list-header><ion-label>Connections</ion-label></ion-list-header>\n  <ion-item-group>\n    <ion-item>...</ion-item>\n    <ion-item>...</ion-item>\n  </ion-item-group>\n</ion-list>\n

inset=\"true\" を使わないlistでは、このwrapperは不要です。

\n

ESLintでリストの構造を整える。

\n

wrapperが必要な理由

\n

Ionicは通常、ion-list 自体に背景を設定するため、ion-list-header もitemと同じsurface内に表示されます。iOS 27のlayoutではheaderとitemのsurfaceを分離します。

\n

\"ion-item-groupが必要な理由を示すinset

\n

そのため、このテーマは次のようにstyleを適用します。

\n\n

Material Designとのマークアップ共有

\n

@rdlabo/ionic-theme-md3 も同じgroup構造に対応しているため、両方のIonic modeで1つのtemplateを共有できます。

\n

このpackageを @rdlabo/ionic-theme-md3 なしで使うapplicationでは、Material modeにも同じgroup layoutを適用するため、任意のstylesheetをimportします。

\n
@import '@rdlabo/ionic-theme-ios27/dist/css/md-ion-list-inset.css';\n

2行itemとsection header groupについては 特別なマークアップとクラス を参照してください。

\n", "headings": [ { "id": "wrapper%E3%81%8C%E5%BF%85%E8%A6%81%E3%81%AA%E7%90%86%E7%94%B1", @@ -250,7 +250,7 @@ export const PROJECT = { "file": "native-ui-shell.md", "section": "ガイド", "path": "/projects/ionic-theme-ios27/docs/native-ui-shell", - "html": "

Native UI Shellは実験的機能です。APIと対応するコントロールは変更される可能性があります。

\n

IonicアプリのWebコンテンツの周囲に、ネイティブのナビゲーションと操作部品を表示します。同梱の任意のCapacitor iOSプラグインが、対応する固定IonicコントロールをUIKitまたはSwiftUIとシステムのLiquid Glassで描画します。ページ内容、スクロール、アプリの状態、ルーティングはIonicのWebViewに残ります。

\n

背景

\n

Basecampは2014年5月8日のHybrid sweet spot: Native navigation, web contentで、Webコンテンツを中心に据え、体験が向上する部分にネイティブUIを使う構成を紹介しました。2018年2月27日のCapacitor 1.0.0 Alphaの発表も、ロードマップに Native UI Shell を明記し、この記事を参照しています。WebとネイティブUIの組み合わせは、Capacitorの初期からの方向性です。

\n

このパッケージは、その考え方をIonicとLiquid Glassへ適用します。既存のIonicマークアップがshellを定義し、対応するtoolbarの操作部品、タブ、固定FAB、検索付きタブがネイティブ表示になります。UIKitが外観と操作を担当し、bridgeがDOM状態を同期して元のIonicコンポーネントへ操作を返します。ナビゲーションスタック、画面遷移、アプリのロジックは引き続きIonicが管理します。対象は以下の固定コントロールです。

\n

有効化

\n

READMEに従ってテーマCSSを読み込んだ後、アプリの起動時に一度呼びます。

\n
import { enableNativeUIShell } from '@rdlabo/ionic-theme-ios27/native';\n\nvoid enableNativeUIShell();\n

enableNativeUIShell() はWebViewの左上の有効な角丸半径も読み取り、画面遷移へ適用します。ネイティブ部品を有効にせず、画面遷移だけを設定する場合は次を呼びます。

\n
import { configureNativeTransition } from '@rdlabo/ionic-theme-ios27/native';\n\nawait configureNativeTransition();\n

既存の navAnimation: iosTransitionAnimation 設定を維持してください。ページごとの登録、コンポーネント一覧、ネイティブcallback、Swift view controllerは不要です。インストール・更新後に npx cap sync ios を実行します。ネイティブプラグインはSwift Package Manager(SPM)を使います。既存のCocoaPodsアプリでは npx cap spm-migration-assistant を実行し、生成された CapApp-SPM パッケージをXcodeでアプリのtargetにリンクします。Xcode 26以降とCapacitor 8でビルドし、ネイティブglassにはiOS 26以降が必要です。Web、Android、SSR、古いiOSではWeb実装を維持します。

\n

明示的に有効化する機能です。通常のパッケージentry pointはCapacitorをimportせず、@capacitor/core は任意のpeer dependencyです。ただし、Capacitorプロジェクトへパッケージをインストールすると、enableNativeUIShell() を呼ばなくてもsyncがネイティブソースを検出してビルドします。

\n

ネイティブの外観は、適用したclass、system、always-darkのテーマCSSに従います。runtimeの稼働中はシステムのテーマ変更も同期します。

\n

対応マークアップ

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Ionicコンポーネント対応する外観と配置ネイティブ描画
ion-button固定header/footerのtoolbar内、fill=\"default\" の標準glassglassの UIButton
ion-buttons固定toolbar内、テーマのglassを共有する2個以上の直接のclear ion-button / ion-menu-button1つの UIGlassEffect 面と独立したネイティブボタン
ion-back-button固定header/footerのtoolbar内、標準のアイコンと色Ionicが解決したラベル・アイコンを使うglassの UIButton
ion-menu-button固定toolbar内のテーマglassの ion-buttonsglassの UIButton、元のIonicのメニュー切り替え
ion-tab-bar固定タブ、アイコンのみ・ラベルのみ、各項目1アイコン、点・文字バッジ、選択・disabled状態UITabBar と UITabBarItem
ion-segment固定toolbar内、スクロールなし、各項目は文字または1アイコンUISegmentedControl
ion-fab / ion-fab-button / ion-fab-listion-content のfixed slot内のglass FAB、主ボタン1つと任意の方向別リスト各ボタンの永続的なglass UIButton、FAB単位の同期
\n

通常のNative UI Shellでは、テーマ変数を読み込んだiOS modeのコンポーネントが対象です。明示的に有効化したVertical Barsは、後述のとおりmodeから独立しています。要素または祖先の ionic-theme-disabled、ios-theme-disabled、旧 ios26-disabled は常に対象外にします。タブ・segmentの1項目でテーマを無効化すると、そのグループ全体がWeb描画になります。

\n

Webテーマを維持したままNative UI Shellだけを無効にするには、ios-theme-shell-disabled を使います。要素とすべての子孫が対象外になります。実行時にclassを追加・削除すると、自動的にWeb描画を復元したりネイティブ描画の適格性を再評価したりします。

\n
<ion-toolbar class=\"ios-theme-shell-disabled\">\n  <ion-button>Web glass button</ion-button>\n</ion-toolbar>\n

共有面内の子が対象外になれば、ボタングループ、タブバー、segment、FABリストの面全体がWeb描画になります。検索FABや検索footerの一部を対象外にするとネイティブ検索連携が無効になりますが、タブバー自体は条件を満たせばネイティブ表示できます。

\n

glassの外観でも配置条件を満たす必要があります。通常のNative UI Shellでは、ボタン、戻るボタン、メニューボタングループ、segmentは ion-header または ion-footer 直下のtoolbarに置き、コントロールの祖先に ion-content がないことが必要です。header/footer直下のボタン、単独toolbar、スクロール内容内のtoolbarやheaderはWeb描画を維持します。slot=\"fixed\" のないFABも対象外です。ネイティブ表示中の部品を対象外の場所へ動かすとWebに戻り、元に戻すと再評価します。.ios-theme-vertical-bars を有効にすると、標準の ion-back-button はrouteのコンテンツ内や常設のapp shellなど固定toolbarの外からもVertical Control Areaへ描画できます。モードを有効にする場所とIonicのコンポーネントmodeはアプリが選び、Vertical Barsの描画に ios modeのクラスは不要です。overlay、縮小header、明示的に対象外にした部品、退出済みページは除外します。

\n

ネイティブタブは等幅項目とIonic既定の layout=\"icon-top\" に対応します。バー内部だけで横方向compact・縦方向regularのsize classを使い、iPadと横向きでもWebのアイコン・ラベルの縦積みを維持します。アプリのsize classは変更しません。ラベルのサイズと太さはWebのsnapshotに従います。icon-start、icon-end、icon-bottom、icon-hide、label-hide や不均等幅ではバー全体がWeb描画です。start・center・end配置はRTLを含め元の ion-tab-bar に従い、方向性のある ion-icon のRTL反転も維持します。

\n

単独のclear、solid、outlineボタンは対象外です。2個以上のclearボタンを持つglass ion-buttons は1つの面として描画し、各操作は独立します。メニューボタンも共有でき、単独のメニューボタンは親 ion-buttons をglass面として使うため、その下にWeb glassが残りません。テーマglass外のメニューボタンはWeb描画です。fillの混在、非対応の子、テーマ無効の子があればグループ全体がWebになります。単独clearボタンもWebのままです。独自のボタン色・戻るアイコンと色、縮小するheader、スクロール内toolbar、modal内、スクロール可能・expandedのsegment、segment-view連携もWeb描画です。複雑なslotや非対応SVGも同様です。任意のアプリCSSをUIKitへ変換するものではありません。

\n

ネイティブglassは実際に背後へ描画されたWeb内容を参照します。既存のtoolbar背景とheader blurも影響します。headerの背後を内容がスクロールするには、通常のtranslucent headerとfullscreen contentを使います。プラグインが内容を移動したり、アプリの不透明toolbar背景を上書きしたりはしません。

\n

Web入力がソフトウェアキーボードを使用中は通常の操作部品をWeb描画へ戻し、閉じると再評価します。キーボードがvisual viewportを動かさないiPadも対象です。ネイティブ検索欄は自身のキーボードと検索面を維持します。

\n

既存の部品が非対応になったときは、Webの元要素を描画してからネイティブの覆いを外します。空白を避けますが、WebとUIKitの更新は不可分ではなく短時間重なる可能性があります。通常の画面遷移で変更のない共有タブを維持する動作とは異なります。

\n

状態とイベント

\n

メニューボタンはIonicが解決した既定・設定済みアイコンか、対応するslotのアイコン・ラベルを使います。ネイティブ操作は元の ion-menu-button をクリックし、menu の対象指定を維持します。submit / reset はWebに残ります。disabled、autoHide、メニューの利用可否、split pane表示は実際のDOMに従います。メニューを開くとWeb表示を復元してネイティブ表示を取り除き、閉じると条件を満たす部品を再表示します。

\n

ラベル、SVG、配置、選択値、アプリ動作はDOMが管理します。配置基準は ion-tab-bar 自体です。ネイティブタブはその矩形をサイズの提案と配置基準として使い、UITabBarのより大きい外枠を考慮します。tab-bar-position-start、tab-bar-position-center、tab-bar-position-end がRTLを含む水平基準を決め、slot=\"bottom\" は下端、slot=\"top\" は上端を維持します。class変更も自動反映します。内部余白と背景面のサイズはUIKitが管理し、対応する縦積み配置と文字はWebに従います。幅いっぱいの指定でもUIKitが幅を制限することがあります。

\n

幅768px以上では、Webテーマの標準2・3・4・5項目バーの上限を、iPadの計測値に近い188・274・336・414ptとします。狭い画面では電話向けサイズを維持します。標準バーは高さ62pt、選択面54pt、内側余白4ptです。隣接ボタンはUIKit同様に重なり、アイコンとラベルはSimulator画像と照合しています。これは標準外観の目安で、独自フォント・アイコン・ラベルを保証する値ではありません。固有幅はUIKitが決め、ネイティブの幅・高さが異なるだけでは対象外になりません。バッジ・タイトル更新でも項目のidentityと配置基準を維持して再計測します。CSSの幅へ強制的に合わせるためにアイコンを伸ばしたりUIKit内部を変更したりしません。

\n

privateなclass名や固定のinset補正を使わず、ネイティブタブを含むview subtreeを計測します。認識できない配置はWebへ戻します。runtimeは構造、関係するshadow root、サイズ、ページのlifecycle、overlayを監視します。祖先の display: none、hidden、テーマclass、部品削除、disabled変更も自動反映し、現在のDOMとrevisionを確認してから元のIonic要素をクリックします。

\n

フォームは ion-button type=\"submit\" と既存のsubmit handlerを維持します。フォーム外からは引き続き [form]=\"formRef\" で渡します。プラグインは form.submit() を呼ばず、別の送信経路を追加せず、Angularのフォーム管理を変更しません。segmentも元の ion-segment-button をクリックするため値の型を維持し、プログラムによる値変更では合成 ionChange を発火しません。

\n

ラベルはネイティブ文字です。ローカルの静的SVGと解決済み ion-icon SVG(name とその変更も含む)は表示倍率でラスタライズし、色を維持してキャッシュします。文字色に従うタブSVGはtemplate描画で、bridgeからの画像更新を待たずラベルと選択色を揃えます。明示的な多色画像は元の色を維持します。外部参照、<use>、アニメーション、埋め込みHTML・画像、SVG文字、stylesheetは対象外です。Web fontや任意のslot配置は完全には再現しません。

\n

ネイティブhostは操作部品内だけで入力を受け、空白部分のtouchはWebViewへ通します。タブの操作とアクセシビリティは標準のUITabBarを使います。Ionic iOSは空バッジを通常隠しますが、空の可視 ion-badge は通知の点、空でなければ文字として表示します。背景・文字色はIonic color パレットを含むDOMの計算済みstyleから取得し、非表示・削除でネイティブバッジも消します。選択更新で項目のidentityを維持します。内部配置はUIKitが管理し、任意のCSS配置は再現しません。ネイティブ側は名前、disabled・selected特性、バッジをアクセシビリティへ公開し、描画中の元要素はWebアクセシビリティから隠します。WebとUIKitのVoiceOver巡回順序が同一になる保証はありません。

\n

検索付きタブ

\n

既存の attachTabBarSearchable(tabBar, fabButton, footer) 登録は、下部タブバーとglass検索部品が対応していれば自動的にネイティブ検索を使います。追加の設定・route・ネイティブ初期化・ページlistenerは不要です。通常タブは UITabBar、検索グループは永続的な UITabBarController、UITab / UISearchTab、UISearchController を使います。元のCapacitor WebViewが結果とナビゲーションを担当します。

\n

登録しても配置制限は変わりません。検索バーと閉じるボタンは固定footer toolbarへ置きます。トリガーは ion-content 直下の ion-fab[slot=\"fixed\"]、または既存の非スクロール .ion-page 直下の配置が必要です。スクロール内容内のwrapperはfixed slotではありません。

\n

登録が有効な間は、ページ遷移中や一時的に利用不可(available: false)でも検索コントローラーを維持し、別の通常タブとして作り直しません。検索前のAlbumを含む通常の表示は、一般のタブと同じ UITabBar と ShellTabBar.fit で配置します。利用可能な場合は検索トリガーをFABに固定し、検索中だけ UISearchTab を表示します。利用可否の変化や検索の開始・終了では両レイヤーをcrossfadeします。初回に通常タブを描画してから切り替わるのを防ぐため、遷移先ページの表示が終わる前(例: ionViewWillEnter)に登録してください。

\n

検索を開いても選択中のIonicタブを維持し、キーボードは自動表示しません(automaticallyActivatesSearch は無効のままです)。検索欄をタップするか ion-searchbar.setFocus() を呼びます。検索中はWebの配置投影を固定し、Capacitor Keyboardのresizeを none に保ちます。UIKitがタブと検索の操作部を管理するため、その間Ionic側の fit を繰り返しません。閉じると通常タブを ion-tab-bar、検索をFABに合わせて再計測します。通常タブの選択はWebの selected 状態が追いつくまで先行表示を維持します。検索が閉じるまで入力イベントとアプリからの value 更新をbridgeで同期します。トリガーSVGと検索アイコンも ion-icon name を含めIonicから取得します。

\n

ネイティブ編集はIonicの入力handlerを経由し、ionInput のdebounce、ionChange、ionFocus、ionBlur、ionClear を維持します。プログラムの value 変更では ionInput を発火せず、アプリによる同期的修正と古いネイティブ入力を区別します。変換中の文字とcaretはネイティブ編集が管理します。footerの閉じる操作は値を保持し、ionCancel や ionClear を発火しません。

\n

初期対応は、標準検索キーボード、既定clear、内部cancelなし、既定の自動修正・大文字化設定を持つiOS modeのglass検索バーです。独自input mode、return key hint、最小・最大文字数、autocomplete、自動修正、spellcheck、clear icon、classic検索バーはWebに残ります。対応グループの disabled、placeholder、value、setFocus() を同期します。一般の単独検索バーは対象外です。

\n

ページの表示終了、overlay、テーマ除外、ネイティブ制御の喪失時は検索を閉じ、最後に同期した値またはアプリの値を保持します。次の検索は閉じた状態から始まります。登録はキャッシュされたページ間の遷移でも維持され、戻るたびの再登録は不要です。bridgeの待機には期限があり、検索開始中にbridgeを失った場合は保留中のEnterを既存のWebアニメーションで完了できます。ただしJavaScriptへ届かなかったネイティブ文字は復元できません。

\n

登録済み ion-searchbar またはinputの置換で旧編集sessionを終了します。置換先は自身のアプリの値を維持し、再表示時に新sessionを開始します。

\n

有効な間はWebView上端のscroll-edge effectを抑えます。Ionicがheaderの縁を描画するため、OSとWebテーマが異なる際の二重の暗いgradientを防ぎます。destroy時に元の設定へ戻します。

\n

ネイティブ検索controllerは自身のキーボード上で表示を維持します。他のネイティブ部品はWeb入力のキーボード表示中は隠します。標準部品にはUIKitのアクセシビリティとReduce Motionが適用されますが、VoiceOverの完全な同等性は検証済みの保証ではありません。

\n

画面遷移と復旧

\n

FABはIonicの activated、子ごとの show、close()、元のclick handlerを維持します。複数リスト、初期展開、小さいボタン、edge は各ボタンの実測配置を使います。別のtimerや開閉controllerを追加せず、Ionicの順次表示を反映します。主ボタンのアイコン変更は解決済み closeIcon でcrossfadeし、Reduce Motionでは無効です。ネイティブFABのinstanceと元FABのネイティブ表示は通常の開閉中も維持します。

\n

標準の円形glass、文字、解決済み静的SVG・ion-icon、RTL反転に対応します。非対応の子が1つでもあれば、リストを閉じていてもFAB全体がWeb表示になります。色付きsolid、submit/reset・href FAB、独自背景・形・動き、fixed slot外、非対応画像もWebです。デモの赤背景 floating-action-button-fixed ページもWebの外観を維持します。fixed slotのFABは ion-content の直下が必要で、ページの兄弟要素に slot=\"fixed\" を付けてもcontent slotにはなりません。

\n

FAB、リスト、ボタンに独自host animationやtransitionがあれば、削除するまでWebです。transformは標準のidentityと隠れた子のscale(0)に対応し、独自scaleは非対応です。display:none 内の子では独自transformがあっても計算値が none になる場合があるため、配置可能になった時に検査し、必要ならFAB全体をWebへ戻します。stylesheetの解析や、非表示配置を予測するための一時的なリスト展開はしません。

\n

src/transition/ios.transition.ts はネイティブ表示の終了を待ってWebアニメーションを始めます。待機中のinteractive progressと完了・キャンセルはqueueに保持します。動かない共有タブは維持します。初回描画とanimation builderなしの遷移は、起動runtimeとIonic lifecycle eventで対応します。

\n

タブ切り替えではWeb・ネイティブ間のcrossfadeを省き、終了するUIKitのsnapshotが次のタブへ重なるのを防ぎます。ionViewWillLeave でrouterのURLとまだ選択中のタブを比較し、vanilla環境では ionTabsWillChange / ionTabsDidChange のDOMイベントも使って検出します。スタックのpush・popでは通常の180msの引き継ぎを維持します。

\n

標準Ionic overlayはdismissまでネイティブ表示を一時停止します。非対応の検索付きタブは既存Webアニメーションを使い、Web glass gestureと制御を共有します。対応する親面のCSS motion中も一時的にWeb表示になります。

\n

ネイティブ表示を終了するときは元要素を復元して描画させてから覆いを外し、開始するときは有効な最新のネイティブ応答を確認してから元要素を隠します。遅延応答は部品ごとに再検証します。既存の対象部品は内容更新中もネイティブ表示を維持し、削除・対象外の部品だけWebへ戻します。新規取得には正確な応答確認が必要です。通常のページ変更では全体clearを呼びません。UIKitタブinstanceと項目を維持し、同一frame・選択を再適用せず、重複・古い操作は破棄します。意図的な空白frameは避けますが、WebKitとUIKitは別描画でOSレベルの不可分な合成を保証しません。独自遷移・overlayは対象Simulatorで検証してください。未知のoverlay systemは自動連携の対象外です。

\n

bridge更新の失敗・timeout時はruntimeを停止してWeb表示を復元し、自動再接続はしません。getStatus() が stopped と理由を返します。再試行するにはhandleの destroy() 後に enableNativeUIShell() を再度呼びます。

\n

診断やアプリの終了処理には次を使います。

\n
const shell = await enableNativeUIShell(); // 繰り返し呼んでもruntimeを共有します\nconsole.log(shell.getStatus()); // 状態、描画部品数、更新数、失敗理由\nawait shell.destroy(); // DOM復元、ネイティブ部品削除、listenerとcacheの解放\n

ネイティブ描画は既定ですべての対応部品に有効です。一部だけを使う場合は、対象を指定します。全体を無効にすると、すべての部品がWeb描画に戻ります。

\n
const shell = await enableNativeUIShell({\n  enabled: true,\n  controls: {\n    tabs: true,\n  },\n});\n\n// Native UI Shellを使わず、すべての部品をWebで描画します。\nconst disabledShell = await enableNativeUIShell({ enabled: false });\n

controls を省略するとすべての対応部品が対象です。指定した場合は true の部品だけがネイティブ描画の対象になります。指定できる項目は tabs、toolbar、segment、fab です。

\n

自動検出できない独自のmodalやoverlayを表示する前は、一時停止を取得します。取得が完了すると対象部品はWeb描画に戻っています。閉じた後は必ず再開してください。

\n
const suspension = await shell.suspend();\n\ntry {\n  await modal.present();\n  await modal.onDidDismiss();\n} finally {\n  await suspension.resume();\n}\n

一時停止は重ねて取得でき、resume() は繰り返し呼んでも安全です。すべての一時停止を解除すると、現在のDOMからネイティブ描画を再評価します。

\n

ネイティブの素材と外観は実行中のiOSに従います。このテーマを入れるだけでiOS 26端末がiOS 27の外観になるわけではありません。

\n

iPhone Duo対応(実験的機能)

\n

Native UI Shellを使わない独立した利用も含め、iPhone Duo対応は実験的機能です。Native UI Shellとともに 1.2.0-0 のプレリリースで利用でき、APIと対応する動作は変更される可能性があります。

\n

独立したVertical Control Areaのentry point(@rdlabo/ionic-theme-ios27/vertical-bars)と dist/css/vertical-bars.css は、iOS 27テーマを読み込まずに動作します。この用途では enableVerticalControlArea() を呼び、縦の領域に置いた部品だけを描画します。既に enableNativeUIShell() を使うアプリは、両方を起動せず既存のruntimeを維持してください。ヒンジの状態やshellを使わないアプリのsplit pane配置を含む詳細はiPhone Duo対応を参照してください。

\n

対応するiOSでは .ios-theme-vertical-bars を追加すると、システムが物理的な側面の領域へ移した部品だけが変わります。対象のタブ、戻る操作、メニューボタン、toolbarの操作部品をSwiftUIの TabView とtoolbarで表示します。iOS 27.1以降とリンクしたiPhone DuoでOSが領域の端を通知する場合、適用した配置と一致する必要があり、不一致ならWeb描画を維持します。端を通知できない古いtoolchainではDOMの配置を使います。SwiftUIが適応配置とLiquid Glassの外観を管理し、ラベル、アイコン、選択・disabled状態、ルーティング、フォーム送信、click handlerは引き続きIonicが管理します。

\n

SwiftUIの面のclipとhit testはシステム操作領域内に制限します。その外のWebコンテンツは表示と操作を維持します。runtimeは元の ion-tab-button へ操作を渡す前にタブ選択を先行更新し、他のネイティブ部品と同じイベント・古いrevisionの保護を使います。メニュー、modal、popoverは独立した面のままで、メインページの操作領域へ移動しません。

\n

このモードはSwiftUIがコンパクトな表示を決めるため、標準の fill=\"default\" と fill=\"clear\" の固定toolbarボタンに対応します。solid、outline、独自色、スクロールする部品、明示的に対象外にした部品はWebに残ります。水平Web toolbarに残すには、ion-buttons グループまたは個別の ion-button に .ios-theme-horizontal-only を追加します。Web、Android、古いiOS、セットアップ中にネイティブ描画が利用できない場合は、Web描画へフォールバックします。

\n

既存テーマを維持する独立した導入手順は、既存テーマでiPhone Duoに対応するを参照してください。

\n

Native UI Shell API

\n

enableNativeUIShell() が返すhandleの操作です。型と起動オプションはAPIリファレンスも参照してください。

\n

getStatus()

\n
getStatus() => NativeUIShellStatus\n

現在のWeb・ネイティブ描画の状態を返します。

\n

suspend()

\n
suspend() => Promise<NativeUIShellSuspension>\n

対象部品を一時的にWeb描画へ戻し、再開用のleaseを返します。

\n

destroy()

\n
destroy() => Promise<void>\n

同期を止め、Web描画を復元してネイティブのリソースを解放します。

\n

ソース構成

\n

src/native/components の各TypeScript moduleがIonic tagとDOM readerを定義します。components/index.ts が探索selectorとcomponent型をまとめます。共有のDOM計測、項目データ、SVG描画は src/native/shared、同期・表示切り替え・lifecycleは runtime.ts が担当します。

\n

iOSのComponentsはUIKit部品の生成・更新・名前を管理します。ShellButton が通常・戻る・メニューボタンの実装を共有し、Shared がhost view、型付きsnapshot、形状、色、画像cacheを管理します。Capacitorは完全なsnapshotを Decodable で一度decodeし、描画側は型付きmodelと Equatable で内容を比較します。不正batchは表示変更前に拒否します。IonicNativeUIShellPlugin.swift がCapacitor呼び出し、revision、ネイティブviewの寿命を調整します。

\n

デモと検証

\n

デモには固定コントロールを確認する native-ui-shell ページがあります。リポジトリのルートからライブラリをビルドし、ブラウザテストを実行します。

\n
npm ci\nnpm run build\ncd demo\nnpm ci\nnpx --no-install playwright install chromium\nnpx --no-install playwright test e2e/native-ui-shell.spec.ts e2e/native-ui-shell-edge.spec.ts\n

ネイティブ操作と配置のテストには、Xcode 26以降、XcodeGen、起動済みのiOS 26以降のSimulatorを使用します。リポジトリのルートで sh scripts/verify-native-ui-shell.sh SIMULATOR_UDID を実行してください。npmパッケージから独立したSwift Package Manager consumerもビルドします。検索タブの統合テストには sh scripts/verify-native-search.sh SIMULATOR_UDID、配置、画面遷移、キーボードの境界条件には同じコマンドの末尾に edge を付けます。テスト成果物の保存先は各スクリプトに表示されます。

\n

検索controllerはUIKit管理のtransitionを維持し、通常部品の取得時crossfadeからは除外されます。

\n", + "html": "

Native UI Shellは実験的機能です。APIと対応するコントロールは変更される可能性があります。

\n

IonicアプリのWebコンテンツの周囲に、ネイティブのナビゲーションと操作部品を表示します。同梱の任意のCapacitor iOSプラグインが、対応する固定IonicコントロールをUIKitまたはSwiftUIとシステムのLiquid Glassで描画します。ページ内容、スクロール、アプリの状態、ルーティングはIonicのWebViewに残ります。

\n

背景

\n

Basecampは2014年5月8日のHybrid sweet spot: Native navigation, web contentで、Webコンテンツを中心に据え、体験が向上する部分にネイティブUIを使う構成を紹介しました。2018年2月27日のCapacitor 1.0.0 Alphaの発表も、ロードマップに Native UI Shell を明記し、この記事を参照しています。WebとネイティブUIの組み合わせは、Capacitorの初期からの方向性です。

\n

このパッケージは、その考え方をIonicとLiquid Glassへ適用します。既存のIonicマークアップがshellを定義し、対応するtoolbarの操作部品、タブ、固定FAB、検索付きタブがネイティブ表示になります。UIKitが外観と操作を担当し、bridgeがDOM状態を同期して元のIonicコンポーネントへ操作を返します。ナビゲーションスタック、画面遷移、アプリのロジックは引き続きIonicが管理します。対象は以下の固定コントロールです。

\n

有効化

\n

READMEに従ってテーマCSSを読み込んだ後、アプリの起動時に一度呼びます。

\n
import { enableNativeUIShell } from '@rdlabo/ionic-theme-ios27/native';\n\nvoid enableNativeUIShell();\n

enableNativeUIShell() はWebViewの左上の有効な角丸半径も読み取り、画面遷移へ適用します。ネイティブ部品を有効にせず、画面遷移だけを設定する場合は次を呼びます。

\n
import { configureNativeTransition } from '@rdlabo/ionic-theme-ios27/native';\n\nawait configureNativeTransition();\n

既存の navAnimation: iosTransitionAnimation 設定を維持してください。ページごとの登録、コンポーネント一覧、ネイティブcallback、Swift view controllerは不要です。インストール・更新後に npx cap sync ios を実行します。ネイティブプラグインはSwift Package Manager(SPM)を使います。既存のCocoaPodsアプリでは npx cap spm-migration-assistant を実行し、生成された CapApp-SPM パッケージをXcodeでアプリのtargetにリンクします。Xcode 26以降とCapacitor 8でビルドし、ネイティブglassにはiOS 26以降が必要です。Web、Android、SSR、古いiOSではWeb実装を維持します。

\n

明示的に有効化する機能です。通常のパッケージentry pointはCapacitorをimportせず、@capacitor/core は任意のpeer dependencyです。ただし、Capacitorプロジェクトへパッケージをインストールすると、enableNativeUIShell() を呼ばなくてもsyncがネイティブソースを検出してビルドします。

\n

ネイティブの外観は、適用したclass、system、always-darkのテーマCSSに従います。runtimeの稼働中はシステムのテーマ変更も同期します。

\n

対応マークアップ

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Ionicコンポーネント対応する外観と配置ネイティブ描画
ion-button固定header/footerのtoolbar内、fill=\"default\" の標準glassglassの UIButton
ion-buttons固定toolbar内、テーマのglassを共有する2個以上の直接のclear ion-button / ion-menu-button1つの UIGlassEffect 面と独立したネイティブボタン
ion-back-button固定header/footerのtoolbar内、標準のアイコンと色Ionicが解決したラベル・アイコンを使うglassの UIButton
ion-menu-button固定toolbar内のテーマglassの ion-buttonsglassの UIButton、元のIonicのメニュー切り替え
ion-tab-bar固定タブ、アイコンのみ・ラベルのみ、各項目1アイコン、点・文字バッジ、選択・disabled状態UITabBar と UITabBarItem
ion-segment固定toolbar内、スクロールなし、各項目は文字または1アイコンUISegmentedControl
ion-fab / ion-fab-button / ion-fab-listion-content のfixed slot内のglass FAB、主ボタン1つと任意の方向別リスト各ボタンの永続的なglass UIButton、FAB単位の同期
\n

通常のNative UI Shellでは、テーマ変数を読み込んだiOS modeのコンポーネントが対象です。明示的に有効化したVertical Barsは、後述のとおりmodeから独立しています。要素または祖先の ionic-theme-disabled、ios-theme-disabled、旧 ios26-disabled は常に対象外にします。タブ・segmentの1項目でテーマを無効化すると、そのグループ全体がWeb描画になります。

\n

Webテーマを維持したままNative UI Shellだけを無効にするには、ios-theme-shell-disabled を使います。要素とすべての子孫が対象外になります。実行時にclassを追加・削除すると、自動的にWeb描画を復元したりネイティブ描画の適格性を再評価したりします。

\n
<ion-toolbar class=\"ios-theme-shell-disabled\">\n  <ion-button>Web glass button</ion-button>\n</ion-toolbar>\n

共有面内の子が対象外になれば、ボタングループ、タブバー、segment、FABリストの面全体がWeb描画になります。検索FABや検索footerの一部を対象外にするとネイティブ検索連携が無効になりますが、タブバー自体は条件を満たせばネイティブ表示できます。

\n

glassの外観でも配置条件を満たす必要があります。通常のNative UI Shellでは、ボタン、戻るボタン、メニューボタングループ、segmentは ion-header または ion-footer 直下のtoolbarに置き、コントロールの祖先に ion-content がないことが必要です。header/footer直下のボタン、単独toolbar、スクロール内容内のtoolbarやheaderはWeb描画を維持します。slot=\"fixed\" のないFABも対象外です。ネイティブ表示中の部品を対象外の場所へ動かすとWebに戻り、元に戻すと再評価します。.ios-theme-vertical-bars を有効にすると、標準の ion-back-button はrouteのコンテンツ内や常設のapp shellなど固定toolbarの外からもVertical Control Areaへ描画できます。モードを有効にする場所とIonicのコンポーネントmodeはアプリが選び、Vertical Barsの描画に ios modeのクラスは不要です。overlay、縮小header、明示的に対象外にした部品、退出済みページは除外します。

\n

ネイティブタブは等幅項目とIonic既定の layout=\"icon-top\" に対応します。バー内部だけで横方向compact・縦方向regularのsize classを使い、iPadと横向きでもWebのアイコン・ラベルの縦積みを維持します。アプリのsize classは変更しません。ラベルのサイズと太さはWebのsnapshotに従います。icon-start、icon-end、icon-bottom、icon-hide、label-hide や不均等幅ではバー全体がWeb描画です。start・center・end配置はRTLを含め元の ion-tab-bar に従い、方向性のある ion-icon のRTL反転も維持します。

\n

単独のclear、solid、outlineボタンは対象外です。2個以上のclearボタンを持つglass ion-buttons は1つの面として描画し、各操作は独立します。メニューボタンも共有でき、単独のメニューボタンは親 ion-buttons をglass面として使うため、その下にWeb glassが残りません。テーマglass外のメニューボタンはWeb描画です。fillの混在、非対応の子、テーマ無効の子があればグループ全体がWebになります。単独clearボタンもWebのままです。独自のボタン色・戻るアイコンと色、縮小するheader、スクロール内toolbar、modal内、スクロール可能・expandedのsegment、segment-view連携もWeb描画です。複雑なslotや非対応SVGも同様です。任意のアプリCSSをUIKitへ変換するものではありません。

\n

ネイティブglassは実際に背後へ描画されたWeb内容を参照します。既存のtoolbar背景とheader blurも影響します。headerの背後を内容がスクロールするには、通常のtranslucent headerとfullscreen contentを使います。プラグインが内容を移動したり、アプリの不透明toolbar背景を上書きしたりはしません。

\n

Web入力がソフトウェアキーボードを使用中は通常の操作部品をWeb描画へ戻し、閉じると再評価します。キーボードがvisual viewportを動かさないiPadも対象です。ネイティブ検索欄は自身のキーボードと検索面を維持します。

\n

既存の部品が非対応になったときは、Webの元要素を描画してからネイティブの覆いを外します。空白を避けますが、WebとUIKitの更新は不可分ではなく短時間重なる可能性があります。通常の画面遷移で変更のない共有タブを維持する動作とは異なります。

\n

状態とイベント

\n

メニューボタンはIonicが解決した既定・設定済みアイコンか、対応するslotのアイコン・ラベルを使います。ネイティブ操作は元の ion-menu-button をクリックし、menu の対象指定を維持します。submit / reset はWebに残ります。disabled、autoHide、メニューの利用可否、split pane表示は実際のDOMに従います。メニューを開くとWeb表示を復元してネイティブ表示を取り除き、閉じると条件を満たす部品を再表示します。

\n

ラベル、SVG、配置、選択値、アプリ動作はDOMが管理します。配置基準は ion-tab-bar 自体です。ネイティブタブはその矩形をサイズの提案と配置基準として使い、UITabBarのより大きい外枠を考慮します。tab-bar-position-start、tab-bar-position-center、tab-bar-position-end がRTLを含む水平基準を決め、slot=\"bottom\" は下端、slot=\"top\" は上端を維持します。class変更も自動反映します。内部余白と背景面のサイズはUIKitが管理し、対応する縦積み配置と文字はWebに従います。幅いっぱいの指定でもUIKitが幅を制限することがあります。

\n

幅768px以上では、Webテーマの標準2・3・4・5項目バーの上限を、iPadの計測値に近い188・274・336・414ptとします。狭い画面では電話向けサイズを維持します。標準バーは高さ62pt、選択面54pt、内側余白4ptです。隣接ボタンはUIKit同様に重なり、アイコンとラベルはSimulator画像と照合しています。これは標準外観の目安で、独自フォント・アイコン・ラベルを保証する値ではありません。固有幅はUIKitが決め、ネイティブの幅・高さが異なるだけでは対象外になりません。バッジ・タイトル更新でも項目のidentityと配置基準を維持して再計測します。CSSの幅へ強制的に合わせるためにアイコンを伸ばしたりUIKit内部を変更したりしません。

\n

privateなclass名や固定のinset補正を使わず、ネイティブタブを含むview subtreeを計測します。認識できない配置はWebへ戻します。runtimeは構造、関係するshadow root、サイズ、ページのlifecycle、overlayを監視します。祖先の display: none、hidden、テーマclass、部品削除、disabled変更も自動反映し、現在のDOMとrevisionを確認してから元のIonic要素をクリックします。

\n

フォームは ion-button type=\"submit\" と既存のsubmit handlerを維持します。フォーム外からは引き続き [form]=\"formRef\" で渡します。プラグインは form.submit() を呼ばず、別の送信経路を追加せず、Angularのフォーム管理を変更しません。segmentも元の ion-segment-button をクリックするため値の型を維持し、プログラムによる値変更では合成 ionChange を発火しません。

\n

ラベルはネイティブ文字です。ローカルの静的SVGと解決済み ion-icon SVG(name とその変更も含む)は表示倍率でラスタライズし、色を維持してキャッシュします。文字色に従うタブSVGはtemplate描画で、bridgeからの画像更新を待たずラベルと選択色を揃えます。明示的な多色画像は元の色を維持します。外部参照、<use>、アニメーション、埋め込みHTML・画像、SVG文字、stylesheetは対象外です。Web fontや任意のslot配置は完全には再現しません。

\n

ネイティブhostは操作部品内だけで入力を受け、空白部分のtouchはWebViewへ通します。タブの操作とアクセシビリティは標準のUITabBarを使います。Ionic iOSは空バッジを通常隠しますが、空の可視 ion-badge は通知の点、空でなければ文字として表示します。背景・文字色はIonic color パレットを含むDOMの計算済みstyleから取得し、非表示・削除でネイティブバッジも消します。選択更新で項目のidentityを維持します。内部配置はUIKitが管理し、任意のCSS配置は再現しません。ネイティブ側は名前、disabled・selected特性、バッジをアクセシビリティへ公開し、描画中の元要素はWebアクセシビリティから隠します。WebとUIKitのVoiceOver巡回順序が同一になる保証はありません。

\n

検索付きタブ

\n

既存の attachTabBarSearchable(tabBar, fabButton, footer) 登録は、下部タブバーとglass検索部品が対応していれば自動的にネイティブ検索を使います。追加の設定・route・ネイティブ初期化・ページlistenerは不要です。通常タブは UITabBar、検索グループは永続的な UITabBarController、UITab / UISearchTab、UISearchController を使います。元のCapacitor WebViewが結果とナビゲーションを担当します。

\n

登録しても配置制限は変わりません。検索バーと閉じるボタンは固定footer toolbarへ置きます。トリガーは ion-content 直下の ion-fab[slot=\"fixed\"]、または既存の非スクロール .ion-page 直下の配置が必要です。スクロール内容内のwrapperはfixed slotではありません。

\n

登録が有効な間は、ページ遷移中や一時的に利用不可(available: false)でも検索コントローラーを維持し、別の通常タブとして作り直しません。検索前のAlbumを含む通常の表示は、一般のタブと同じ UITabBar と ShellTabBar.fit で配置します。利用可能な場合は検索トリガーをFABに固定し、検索中だけ UISearchTab を表示します。利用可否の変化や検索の開始・終了では両レイヤーをcrossfadeします。初回に通常タブを描画してから切り替わるのを防ぐため、遷移先ページの表示が終わる前(例: ionViewWillEnter)に登録してください。

\n

検索を開いても選択中のIonicタブを維持し、キーボードは自動表示しません(automaticallyActivatesSearch は無効のままです)。検索欄をタップするか ion-searchbar.setFocus() を呼びます。検索中はWebの配置投影を固定し、Capacitor Keyboardのresizeを none に保ちます。UIKitがタブと検索の操作部を管理するため、その間Ionic側の fit を繰り返しません。閉じると通常タブを ion-tab-bar、検索をFABに合わせて再計測します。通常タブの選択はWebの selected 状態が追いつくまで先行表示を維持します。検索が閉じるまで入力イベントとアプリからの value 更新をbridgeで同期します。トリガーSVGと検索アイコンも ion-icon name を含めIonicから取得します。

\n

ネイティブ編集はIonicの入力handlerを経由し、ionInput のdebounce、ionChange、ionFocus、ionBlur、ionClear を維持します。プログラムの value 変更では ionInput を発火せず、アプリによる同期的修正と古いネイティブ入力を区別します。変換中の文字とcaretはネイティブ編集が管理します。footerの閉じる操作は値を保持し、ionCancel や ionClear を発火しません。

\n

初期対応は、標準検索キーボード、既定clear、内部cancelなし、既定の自動修正・大文字化設定を持つiOS modeのglass検索バーです。独自input mode、return key hint、最小・最大文字数、autocomplete、自動修正、spellcheck、clear icon、classic検索バーはWebに残ります。対応グループの disabled、placeholder、value、setFocus() を同期します。一般の単独検索バーは対象外です。

\n

ページの表示終了、overlay、テーマ除外、ネイティブ制御の喪失時は検索を閉じ、最後に同期した値またはアプリの値を保持します。次の検索は閉じた状態から始まります。登録はキャッシュされたページ間の遷移でも維持され、戻るたびの再登録は不要です。bridgeの待機には期限があり、検索開始中にbridgeを失った場合は保留中のEnterを既存のWebアニメーションで完了できます。ただしJavaScriptへ届かなかったネイティブ文字は復元できません。

\n

登録済み ion-searchbar またはinputの置換で旧編集sessionを終了します。置換先は自身のアプリの値を維持し、再表示時に新sessionを開始します。

\n

有効な間はWebView上端のscroll-edge effectを抑えます。Ionicがheaderの縁を描画するため、OSとWebテーマが異なる際の二重の暗いgradientを防ぎます。destroy時に元の設定へ戻します。

\n

ネイティブ検索controllerは自身のキーボード上で表示を維持します。他のネイティブ部品はWeb入力のキーボード表示中は隠します。標準部品にはUIKitのアクセシビリティとReduce Motionが適用されますが、VoiceOverの完全な同等性は検証済みの保証ではありません。

\n

画面遷移と復旧

\n

FABはIonicの activated、子ごとの show、close()、元のclick handlerを維持します。複数リスト、初期展開、小さいボタン、edge は各ボタンの実測配置を使います。別のtimerや開閉controllerを追加せず、Ionicの順次表示を反映します。主ボタンのアイコン変更は解決済み closeIcon でcrossfadeし、Reduce Motionでは無効です。ネイティブFABのinstanceと元FABのネイティブ表示は通常の開閉中も維持します。

\n

標準の円形glass、文字、解決済み静的SVG・ion-icon、RTL反転に対応します。非対応の子が1つでもあれば、リストを閉じていてもFAB全体がWeb表示になります。色付きsolid、submit/reset・href FAB、独自背景・形・動き、fixed slot外、非対応画像もWebです。デモの赤背景 floating-action-button-fixed ページもWebの外観を維持します。fixed slotのFABは ion-content の直下が必要で、ページの兄弟要素に slot=\"fixed\" を付けてもcontent slotにはなりません。

\n

FAB、リスト、ボタンに独自host animationやtransitionがあれば、削除するまでWebです。transformは標準のidentityと隠れた子のscale(0)に対応し、独自scaleは非対応です。display:none 内の子では独自transformがあっても計算値が none になる場合があるため、配置可能になった時に検査し、必要ならFAB全体をWebへ戻します。stylesheetの解析や、非表示配置を予測するための一時的なリスト展開はしません。

\n

組み込みの iosTransitionAnimation はネイティブ表示の終了を待ってWebアニメーションを始めます。待機中のinteractive progressと完了・キャンセルはqueueに保持します。動かない共有タブは維持します。初回描画とanimation builderなしの遷移は、起動runtimeとIonic lifecycle eventで対応します。Ionic標準または独自の画面遷移builderには、パッケージrootと /vertical-bars からexportされる withNativeUIShellTransition() で同じ連携を追加できます。導入手順は画面遷移アニメーションを接続するを参照してください。

\n

タブ切り替えではWeb・ネイティブ間のcrossfadeを省き、終了するUIKitのsnapshotが次のタブへ重なるのを防ぎます。ionViewWillLeave でrouterのURLとまだ選択中のタブを比較し、vanilla環境では ionTabsWillChange / ionTabsDidChange のDOMイベントも使って検出します。スタックのpush・popでは通常の180msの引き継ぎを維持します。

\n

標準Ionic overlayはdismissまでネイティブ表示を一時停止します。非対応の検索付きタブは既存Webアニメーションを使い、Web glass gestureと制御を共有します。対応する親面のCSS motion中も一時的にWeb表示になります。

\n

ネイティブ表示を終了するときは元要素を復元して描画させてから覆いを外し、開始するときは有効な最新のネイティブ応答を確認してから元要素を隠します。遅延応答は部品ごとに再検証します。既存の対象部品は内容更新中もネイティブ表示を維持し、削除・対象外の部品だけWebへ戻します。新規取得には正確な応答確認が必要です。通常のページ変更では全体clearを呼びません。UIKitタブinstanceと項目を維持し、同一frame・選択を再適用せず、重複・古い操作は破棄します。意図的な空白frameは避けますが、WebKitとUIKitは別描画でOSレベルの不可分な合成を保証しません。独自遷移・overlayは対象Simulatorで検証してください。未知のoverlay systemは自動連携の対象外です。

\n

bridge更新の失敗・timeout時はruntimeを停止してWeb表示を復元し、自動再接続はしません。getStatus() が stopped と理由を返します。再試行するにはhandleの destroy() 後に enableNativeUIShell() を再度呼びます。

\n

診断やアプリの終了処理には次を使います。

\n
const shell = await enableNativeUIShell(); // 繰り返し呼んでもruntimeを共有します\nconsole.log(shell.getStatus()); // 状態、描画部品数、更新数、失敗理由\nawait shell.destroy(); // DOM復元、ネイティブ部品削除、listenerとcacheの解放\n

ネイティブ描画は既定ですべての対応部品に有効です。一部だけを使う場合は、対象を指定します。全体を無効にすると、すべての部品がWeb描画に戻ります。

\n
const shell = await enableNativeUIShell({\n  enabled: true,\n  controls: {\n    tabs: true,\n  },\n});\n\n// Native UI Shellを使わず、すべての部品をWebで描画します。\nconst disabledShell = await enableNativeUIShell({ enabled: false });\n

controls を省略するとすべての対応部品が対象です。指定した場合は true の部品だけがネイティブ描画の対象になります。指定できる項目は tabs、toolbar、segment、fab です。

\n

自動検出できない独自のmodalやoverlayを表示する前は、一時停止を取得します。取得が完了すると対象部品はWeb描画に戻っています。閉じた後は必ず再開してください。

\n
const suspension = await shell.suspend();\n\ntry {\n  await modal.present();\n  await modal.onDidDismiss();\n} finally {\n  await suspension.resume();\n}\n

一時停止は重ねて取得でき、resume() は繰り返し呼んでも安全です。すべての一時停止を解除すると、現在のDOMからネイティブ描画を再評価します。

\n

ネイティブの素材と外観は実行中のiOSに従います。このテーマを入れるだけでiOS 26端末がiOS 27の外観になるわけではありません。

\n

iPhone Duo対応(実験的機能)

\n

Native UI Shellを使わない独立した利用も含め、iPhone Duo対応は実験的機能です。Native UI Shellとともに 1.2.0-0 のプレリリースで利用でき、APIと対応する動作は変更される可能性があります。

\n

独立したVertical Control Areaのentry point(@rdlabo/ionic-theme-ios27/vertical-bars)と dist/css/vertical-bars.css は、iOS 27テーマを読み込まずに動作します。この用途では enableVerticalControlArea() を呼び、縦の領域に置いた部品だけを描画します。既に enableNativeUIShell() を使うアプリは、両方を起動せず既存のruntimeを維持してください。ヒンジの状態やshellを使わないアプリのsplit pane配置を含む詳細はiPhone Duo対応を参照してください。

\n

対応するiOSでは .ios-theme-vertical-bars を追加すると、システムが物理的な側面の領域へ移した部品だけが変わります。対象のタブ、戻る操作、メニューボタン、toolbarの操作部品をSwiftUIの TabView とtoolbarで表示します。iOS 27.1以降とリンクしたiPhone DuoでOSが領域の端を通知する場合、適用した配置と一致する必要があり、不一致ならWeb描画を維持します。端を通知できない古いtoolchainではDOMの配置を使います。SwiftUIが適応配置とLiquid Glassの外観を管理し、ラベル、アイコン、選択・disabled状態、ルーティング、フォーム送信、click handlerは引き続きIonicが管理します。

\n

SwiftUIの面のclipとhit testはシステム操作領域内に制限します。その外のWebコンテンツは表示と操作を維持します。runtimeは元の ion-tab-button へ操作を渡す前にタブ選択を先行更新し、他のネイティブ部品と同じイベント・古いrevisionの保護を使います。メニュー、modal、popoverは独立した面のままで、メインページの操作領域へ移動しません。

\n

このモードはSwiftUIがコンパクトな表示を決めるため、標準の fill=\"default\" と fill=\"clear\" の固定toolbarボタンに対応します。solid、outline、独自色、スクロールする部品、明示的に対象外にした部品はWebに残ります。水平Web toolbarに残すには、ion-buttons グループまたは個別の ion-button に .ios-theme-horizontal-only を追加します。Web、Android、古いiOS、セットアップ中にネイティブ描画が利用できない場合は、Web描画へフォールバックします。

\n

既存テーマを維持する独立した導入手順は、既存テーマでiPhone Duoに対応するを参照してください。

\n

Native UI Shell API

\n

enableNativeUIShell() が返すhandleの操作です。型と起動オプションはAPIリファレンスも参照してください。

\n

getStatus()

\n
getStatus() => NativeUIShellStatus\n

現在のWeb・ネイティブ描画の状態を返します。

\n

suspend()

\n
suspend() => Promise<NativeUIShellSuspension>\n

対象部品を一時的にWeb描画へ戻し、再開用のleaseを返します。

\n

destroy()

\n
destroy() => Promise<void>\n

同期を止め、Web描画を復元してネイティブのリソースを解放します。

\n

ソース構成

\n

src/native/components の各TypeScript moduleがIonic tagとDOM readerを定義します。components/index.ts が探索selectorとcomponent型をまとめます。共有のDOM計測、項目データ、SVG描画は src/native/shared、同期・表示切り替え・lifecycleは runtime.ts が担当します。

\n

iOSのComponentsはUIKit部品の生成・更新・名前を管理します。ShellButton が通常・戻る・メニューボタンの実装を共有し、Shared がhost view、型付きsnapshot、形状、色、画像cacheを管理します。Capacitorは完全なsnapshotを Decodable で一度decodeし、描画側は型付きmodelと Equatable で内容を比較します。不正batchは表示変更前に拒否します。IonicNativeUIShellPlugin.swift がCapacitor呼び出し、revision、ネイティブviewの寿命を調整します。

\n

デモと検証

\n

デモには固定コントロールを確認する native-ui-shell ページがあります。リポジトリのルートからライブラリをビルドし、ブラウザテストを実行します。

\n
npm ci\nnpm run build\ncd demo\nnpm ci\nnpx --no-install playwright install chromium\nnpx --no-install playwright test e2e/native-ui-shell.spec.ts e2e/native-ui-shell-edge.spec.ts\n

ネイティブ操作と配置のテストには、Xcode 26以降、XcodeGen、起動済みのiOS 26以降のSimulatorを使用します。リポジトリのルートで sh scripts/verify-native-ui-shell.sh SIMULATOR_UDID を実行してください。npmパッケージから独立したSwift Package Manager consumerもビルドします。検索タブの統合テストには sh scripts/verify-native-search.sh SIMULATOR_UDID、配置、画面遷移、キーボードの境界条件には同じコマンドの末尾に edge を付けます。テスト成果物の保存先は各スクリプトに表示されます。

\n

検索controllerはUIKit管理のtransitionを維持し、通常部品の取得時crossfadeからは除外されます。

\n", "headings": [ { "id": "%E8%83%8C%E6%99%AF", @@ -425,7 +425,7 @@ export const PROJECT = { "file": "iphone-duo-with-original-theme.md", "section": "ガイド", "path": "/projects/ionic-theme-ios27/docs/iphone-duo-with-original-theme", - "html": "

既存のテーマを維持したまま、Ionicアプリに縦のナビゲーション領域を追加します。タブと対応するtoolbarの操作部品は画面の側面へ移り、コンテンツと水平の操作部品は現在の外観を保ちます。Ionicの ios と md の両modeに対応します。

\n

まずChromeで試せます。 iPhone DuoやiOSビルドを準備する前に、Webの操作部品でレイアウトを確認できます。対応するCapacitor iOSでは、同じIonicマークアップからシステム操作領域にSwiftUIのネイティブ部品を表示します。

\n

1.2.0-0 で利用できる 実験的機能 です。APIと対応する動作は変更される可能性があります。

\n

既存のIonicアプリで試す

\n

1. インストールして専用スタイルシートを読み込む

\n

Ionic >=8.8.1 <10 とCapacitor Core >=8 <9 を使う既存アプリを前提とします。既存のCapacitor 8は再インストール不要です。別majorを使う場合は、Core・CLI・platformパッケージをまとめて移行してから進めてください。Capacitorを使わないWeb専用アプリでは @capacitor/core@^8 もインストールします。JavaScriptのentry pointはChromeでもこの依存を必要とします。

\n
npm install @rdlabo/ionic-theme-ios27@1.2.0-0\n

既存テーマのimportを維持し、グローバルSassファイルに次を追加します。

\n
@use '@rdlabo/ionic-theme-ios27/dist/css/vertical-bars.css';\n

独立したJavaScriptのentry pointはChromeでも @capacitor/core を必要とします。iOS 27テーマのスタイルシートは不要です。

\n

2. アプリを側面のレイアウトへ切り替える

\n

既存のapp rootにクラスを追加し、その中のコンテンツを維持します。

\n
<ion-app class=\"ios-theme-vertical-bars\">\n  <!-- 既存のページ、タブ、toolbarの操作部品をここに維持します。 -->\n</ion-app>\n

プレビューは物理的な右側に 80px を確保します。左側を試すには ios-theme-vertical-bars-left も追加します。

\n

3. Ionicの初期化前に画面遷移を設定する

\n

画面遷移をimportし、Ionicの navAnimation に登録します。操作領域のruntimeを起動するだけでは、この設定は登録されません。このアニメーションは画面遷移前にネイティブ部品の退避を待ち、縦レイアウトでは水平の戻るボタンをアニメーション対象から外します。既存テーマのスタイルは維持しますが、iOSの画面遷移には本パッケージのアニメーションを使います。

\n
import { iosTransitionAnimation } from '@rdlabo/ionic-theme-ios27';\n\nconst ionicConfig = {\n  navAnimation: iosTransitionAnimation,\n};\n

初期化前に、既存のIonic設定の ios mode用設定へこのオプションを統合します。Angularでは provideIonicAngular()、Reactでは setupIonicReact()、Vueでは IonicVue pluginのオプションへ渡してください。既存の md 用アニメーション設定は維持します。JavaScriptのentry pointをimportしても、iOS 27テーマのスタイルシートは読み込まれません。

\n

4. App rootのマウント後に操作部品を起動する

\n

ion-app がDOMに存在してから、アプリの起動処理で一度呼びます。

\n
import { enableVerticalControlArea } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\nconst rail = await enableVerticalControlArea();\n

確認できる結果: 既存のタブバーが側面へ移り、対応するアイコン付きの固定toolbarの操作部品も表示されます。コンテンツは既存テーマを維持し、操作部品の領域を空けます。Webのタブはアイコンを表示し、押したままドラッグするとラベルが現れます。

\n

既存のIonic click handlerとルーティングを使います。文字だけのtoolbar操作は水平に残ります。ion-buttons グループまたは個別の ion-button に .ios-theme-horizontal-only を追加すると、水平toolbarに残せます。

\n

アプリ内の利用元を破棄するときは await rail.destroy() を呼び、元の部品を復元してruntimeを解放します。既に enableNativeUIShell() を使う場合はそのruntimeを維持し、共通の配置ガイドに従ってください。

\n

プレビューが表示されない場合

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
表示の状態確認すること
側面に空きができないvertical-bars.css を読み込み、ion-app にクラスを追加します。
空きはできるが部品が水平に残るApp rootのマウント後に enableVerticalControlArea() を起動します。既存のタブや、固定toolbarの対応するアイコン付き操作を使います。
一部の操作だけ水平に残る文字だけの操作、独自のfill、明示的に除外した部品は元の表示を維持します。操作部品の対応条件を参照してください。
\n

iPhone Duoと接続する

\n

Capacitor iOSでは npx cap sync ios を実行します。実際の操作領域の端、safe areaのinset、ヒンジの状態を取得するには、Xcode 27.1以降でビルドし、iOS 27.1以降のSDKとリンクしてください。プラグインはSwift Package Managerを使います。既存のCocoaPodsアプリはNative UI Shellの導入を参照してください。

\n

ブラウザ用の起動コードを、ion-app のマウント後に実行する次のコードへ置き換えます。

\n
import { Capacitor, type PluginListenerHandle } from '@capacitor/core';\nimport { enableVerticalControlArea, IonicNativeUIShell } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\nconst rail = await enableVerticalControlArea();\nlet layoutListener: PluginListenerHandle | undefined;\n\nif (Capacitor.getPlatform() === 'ios') {\n  // Runtimeが既にレイアウトを監視しているため、購読だけを行います。\n  layoutListener = await IonicNativeUIShell.addListener('deviceLayoutChange', ({ placement }) =>\n    rail.setPlacement(placement),\n  );\n  rail.setPlacement((await IonicNativeUIShell.getDeviceLayout()).placement);\n}\n\n// アプリ内の利用元を破棄するときに呼びます。\nconst stopVerticalArea = async () => {\n  await layoutListener?.remove();\n  await rail.destroy();\n};\n

setPlacement() は実測insetを適用し、論理方向をdocumentの文字方向で解決します。null の端は通常のレイアウトを復元します。操作領域のない端末や古いSDKでビルドしたアプリは null を返すため、この例では通常のレイアウトへ戻ります。そのようなiOSビルドで意図的にDOMの操作領域を試す場合は、nullの配置を適用する代わりにアプリが rail.setPlacement('trailing') で固定の端を選んでください。レイアウトのシミュレーションであり、実際のシステム操作領域やヒンジの計測値は得られません。

\n

対応するiOSでは操作領域の部品はシステムのSwiftUIの外観を使い、通常のコンテンツと水平の操作部品は独自のWebスタイルを維持します。WebとAndroidはWebクローンを使います。

\n

操作部品を描画せずヒンジの状態だけを使う

\n

開閉状態に応じたsplit paneやレイアウト切り替えだけが必要なら、描画runtimeを起動せず、.ios-theme-vertical-bars も追加しません。getDeviceLayout() と deviceLayoutChange を直接使い、startDeviceLayoutMonitoring() と stopDeviceLayoutMonitoring() を対応させ、終了時にlistenerを削除します。

\n

購読例、null値、監視の寿命はデバイスのレイアウトを取得する、opt-inの幅指定と半開き状態はSplit paneを開閉状態に合わせるを参照してください。

\n

共通のレイアウト規則とAPI

\n

safe area、overlay、RTL、操作部品の対応条件、Webでのシミュレーション、handleのAPIはiPhone Duo対応に記載しています。独立した構成にも同じ規則が適用されます。

\n", + "html": "

既存のテーマを維持したまま、Ionicアプリに縦のナビゲーション領域を追加します。タブと対応するtoolbarの操作部品は画面の側面へ移り、コンテンツと水平の操作部品は現在の外観を保ちます。Ionicの ios と md の両modeに対応します。

\n

まずChromeで試せます。 iPhone DuoやiOSビルドを準備する前に、Webの操作部品でレイアウトを確認できます。対応するCapacitor iOSでは、同じIonicマークアップからシステム操作領域にSwiftUIのネイティブ部品を表示します。

\n

1.2.0-0 で利用できる 実験的機能 です。APIと対応する動作は変更される可能性があります。

\n

既存のIonicアプリで試す

\n

1. インストールして専用スタイルシートを読み込む

\n

Ionic >=8.8.1 <10 とCapacitor Core >=8 <9 を使う既存アプリを前提とします。既存のCapacitor 8は再インストール不要です。別majorを使う場合は、Core・CLI・platformパッケージをまとめて移行してから進めてください。Capacitorを使わないWeb専用アプリでは @capacitor/core@^8 もインストールします。JavaScriptのentry pointはChromeでもこの依存を必要とします。

\n
npm install @rdlabo/ionic-theme-ios27@1.2.0-1\n

既存テーマのimportを維持し、グローバルSassファイルに次を追加します。

\n
@use '@rdlabo/ionic-theme-ios27/dist/css/vertical-bars.css';\n

独立したJavaScriptのentry pointはChromeでも @capacitor/core を必要とします。iOS 27テーマのスタイルシートは不要です。

\n

2. アプリを側面のレイアウトへ切り替える

\n

既存のapp rootにクラスを追加し、その中のコンテンツを維持します。

\n
<ion-app class=\"ios-theme-vertical-bars\">\n  <!-- 既存のページ、タブ、toolbarの操作部品をここに維持します。 -->\n</ion-app>\n

プレビューは物理的な右側に 80px を確保します。左側を試すには ios-theme-vertical-bars-left も追加します。

\n

3. 画面遷移アニメーションを接続する

\n

Ionicの初期化前に navAnimation を設定します。操作領域のruntimeを起動するだけでは、この設定は登録されません。アダプターは既存アニメーションを維持しながらネイティブ部品の退避を待ち、スワイプの進捗とキャンセルを連携します。

\n

Ionicの標準アニメーションを維持する

\n

navAnimation を設定していない場合は、Ionic標準のbuilderをラップします。Ionicが遷移時に渡す mode でbuilderを選び、ios と md のどちらも通常のアニメーションを維持します。

\n
import { iosTransitionAnimation, mdTransitionAnimation, type AnimationBuilder } from '@ionic/core';\nimport { withNativeUIShellTransition } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\nconst defaultTransition: AnimationBuilder = (baseEl, opts) =>\n  (opts.mode === 'ios' ? iosTransitionAnimation : mdTransitionAnimation)(baseEl, opts);\n\nconst ionicConfig = {\n  navAnimation: withNativeUIShellTransition(defaultTransition),\n};\n

初期化前に、既存のIonic設定へこのオプションを統合します。Angularでは provideIonicAngular()、Reactでは setupIonicReact()、Vueでは IonicVue pluginのオプションへ渡してください。既存テーマのスタイルシートのimportは維持します。iOS 27テーマのスタイルシートは不要です。

\n

独自アニメーションを維持する

\n

既に navAnimation を設定している場合は、そのbuilderをラップします。

\n
import type { AnimationBuilder } from '@ionic/core';\nimport { withNativeUIShellTransition } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\n// アプリで既に使っているanimation builderを渡します。\nconst configureNavigation = (existingTransition: AnimationBuilder) => ({\n  navAnimation: withNativeUIShellTransition(existingTransition),\n});\n

アダプターは元の Animation を返し、効果、duration、easingを維持します。画面遷移だけに使い、modalやpopoverのアニメーションには使いません。Ionicが遷移後に破棄するため、builderは遷移ごとに新しい Animation を返してください。部品登録とアニメーションなしの遷移には、引き続きlifecycle eventを使います。

\n

アダプターは、水平の戻るボタンへの効果を含むbuilderのアニメーション対象を維持します。縦レイアウトでその効果を除外するiOS 27の画面遷移が必要なら、@rdlabo/ionic-theme-ios27 の iosTransitionAnimation を navAnimation に使ってください。こちらにはアダプターが組み込まれているため、ラップは不要です。

\n

withNativeUIShellTransition() は 1.2.0-1 以降で利用できます。

\n

4. App rootのマウント後に操作部品を起動する

\n

ion-app がDOMに存在してから、アプリの起動処理で一度呼びます。

\n
import { enableVerticalControlArea } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\nconst rail = await enableVerticalControlArea();\n

確認できる結果: 既存のタブバーが側面へ移り、対応するアイコン付きの固定toolbarの操作部品も表示されます。コンテンツは既存テーマを維持し、操作部品の領域を空けます。Webのタブはアイコンを表示し、押したままドラッグするとラベルが現れます。

\n

既存のIonic click handlerとルーティングを使います。文字だけのtoolbar操作は水平に残ります。ion-buttons グループまたは個別の ion-button に .ios-theme-horizontal-only を追加すると、水平toolbarに残せます。

\n

アプリ内の利用元を破棄するときは await rail.destroy() を呼び、元の部品を復元してruntimeを解放します。既に enableNativeUIShell() を使う場合はそのruntimeを維持し、共通の配置ガイドに従ってください。

\n

プレビューが表示されない場合

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
表示の状態確認すること
側面に空きができないvertical-bars.css を読み込み、ion-app にクラスを追加します。
空きはできるが部品が水平に残るApp rootのマウント後に enableVerticalControlArea() を起動します。既存のタブや、固定toolbarの対応するアイコン付き操作を使います。
一部の操作だけ水平に残る文字だけの操作、独自のfill、明示的に除外した部品は元の表示を維持します。操作部品の対応条件を参照してください。
\n

iPhone Duoと接続する

\n

Capacitor iOSでは npx cap sync ios を実行します。実際の操作領域の端、safe areaのinset、ヒンジの状態を取得するには、Xcode 27.1以降でビルドし、iOS 27.1以降のSDKとリンクしてください。プラグインはSwift Package Managerを使います。既存のCocoaPodsアプリはNative UI Shellの導入を参照してください。

\n

ブラウザ用の起動コードを、ion-app のマウント後に実行する次のコードへ置き換えます。

\n
import { Capacitor, type PluginListenerHandle } from '@capacitor/core';\nimport { enableVerticalControlArea, IonicNativeUIShell } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\nconst rail = await enableVerticalControlArea();\nlet layoutListener: PluginListenerHandle | undefined;\n\nif (Capacitor.getPlatform() === 'ios') {\n  // Runtimeが既にレイアウトを監視しているため、購読だけを行います。\n  layoutListener = await IonicNativeUIShell.addListener('deviceLayoutChange', ({ placement }) =>\n    rail.setPlacement(placement),\n  );\n  rail.setPlacement((await IonicNativeUIShell.getDeviceLayout()).placement);\n}\n\n// アプリ内の利用元を破棄するときに呼びます。\nconst stopVerticalArea = async () => {\n  await layoutListener?.remove();\n  await rail.destroy();\n};\n

setPlacement() は実測insetを適用し、論理方向をdocumentの文字方向で解決します。null の端は通常のレイアウトを復元します。操作領域のない端末や古いSDKでビルドしたアプリは null を返すため、この例では通常のレイアウトへ戻ります。そのようなiOSビルドで意図的にDOMの操作領域を試す場合は、nullの配置を適用する代わりにアプリが rail.setPlacement('trailing') で固定の端を選んでください。レイアウトのシミュレーションであり、実際のシステム操作領域やヒンジの計測値は得られません。

\n

対応するiOSでは操作領域の部品はシステムのSwiftUIの外観を使い、通常のコンテンツと水平の操作部品は独自のWebスタイルを維持します。WebとAndroidはWebクローンを使います。

\n

操作部品を描画せずヒンジの状態だけを使う

\n

開閉状態に応じたsplit paneやレイアウト切り替えだけが必要なら、描画runtimeを起動せず、.ios-theme-vertical-bars も追加しません。getDeviceLayout() と deviceLayoutChange を直接使い、startDeviceLayoutMonitoring() と stopDeviceLayoutMonitoring() を対応させ、終了時にlistenerを削除します。

\n

購読例、null値、監視の寿命はデバイスのレイアウトを取得する、opt-inの幅指定と半開き状態はSplit paneを開閉状態に合わせるを参照してください。

\n

共通のレイアウト規則とAPI

\n

safe area、overlay、RTL、操作部品の対応条件、Webでのシミュレーション、handleのAPIはiPhone Duo対応に記載しています。独立した構成にも同じ規則が適用されます。

\n", "headings": [ { "id": "%E6%97%A2%E5%AD%98%E3%81%AEionic%E3%82%A2%E3%83%97%E3%83%AA%E3%81%A7%E8%A9%A6%E3%81%99", @@ -443,10 +443,20 @@ export const PROJECT = { "level": 3 }, { - "id": "3.-ionic%E3%81%AE%E5%88%9D%E6%9C%9F%E5%8C%96%E5%89%8D%E3%81%AB%E7%94%BB%E9%9D%A2%E9%81%B7%E7%A7%BB%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B", - "text": "3. Ionicの初期化前に画面遷移を設定する", + "id": "3.-%E7%94%BB%E9%9D%A2%E9%81%B7%E7%A7%BB%E3%82%A2%E3%83%8B%E3%83%A1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E6%8E%A5%E7%B6%9A%E3%81%99%E3%82%8B", + "text": "3. 画面遷移アニメーションを接続する", "level": 3 }, + { + "id": "ionic%E3%81%AE%E6%A8%99%E6%BA%96%E3%82%A2%E3%83%8B%E3%83%A1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E7%B6%AD%E6%8C%81%E3%81%99%E3%82%8B", + "text": "Ionicの標準アニメーションを維持する", + "level": 4 + }, + { + "id": "%E7%8B%AC%E8%87%AA%E3%82%A2%E3%83%8B%E3%83%A1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E7%B6%AD%E6%8C%81%E3%81%99%E3%82%8B", + "text": "独自アニメーションを維持する", + "level": 4 + }, { "id": "4.-app-root%E3%81%AE%E3%83%9E%E3%82%A6%E3%83%B3%E3%83%88%E5%BE%8C%E3%81%AB%E6%93%8D%E4%BD%9C%E9%83%A8%E5%93%81%E3%82%92%E8%B5%B7%E5%8B%95%E3%81%99%E3%82%8B", "text": "4. App rootのマウント後に操作部品を起動する", @@ -485,7 +495,7 @@ export const PROJECT = { "file": "features.md", "section": "ガイド", "path": "/projects/ionic-theme-ios27/docs/features", - "html": "

CSS変数とSass mixinでテーマをカスタマイズしたり、コンポーネント単位で段階的に導入したりできます。マークアップ固有のopt-inは 特別なマークアップとクラス を参照してください。

\n

CSS変数

\n

ライブラリのデフォルトスタイルをデザインに合わせてカスタマイズできるよう、複数のCSS変数を提供しています。詳細は次のファイルを参照してください。
\nhttps://github.com/rdlabo-dev/ionic-theme-ios27/blob/ios27-v1.2.0-0/src/styles/default-variables.scsshttps://github.com/rdlabo-dev/ionic-theme-ios27/blob/ios27-v1.2.0-0/src/styles/default-variables.scss

\n

メニューの背景RGBは --ios-theme-menu-background-rgb(ライト: 225, 230, 240、ダーク: 26, 31, 34)、不透明度は --ios-theme-menu-background-opacity(既定値: 0.96)で指定します。どちらも ion-menu に設定できる公開CSS変数です。

\n

ボタン押下時の拡縮は :active に従い、少しovershootします。継続時間は共通のactivated transition変数ではなく、--ios-theme-button-press-duration(既定値: 380ms)で変更します。

\n

Liquid Glass mixin

\n

Liquid Glass mixinを使うには、メインパッケージからSCSSファイルをimportします。

\n
@use '@rdlabo/ionic-theme-ios27/src/styles/utils/api.scss';\n\nion-textarea label.textarea-wrapper {\n  @include api.glass-background;\n}\n

Native UI Shell(実験的機能)

\n

Capacitor iOSアプリでは、任意の実験的機能Native UI Shellで、対応する固定コントロールをUIKitとシステムのLiquid Glassで描画できます。内容とロジックはWebViewに残り、Ionicがルーティングと画面遷移を管理します。ガイドではBasecampとCapacitorに由来する考え方、導入、対応部品、Web描画へのフォールバックを説明します。

\n

コンポーネント単位のimport

\n

段階的に導入する場合は、テーマ全体ではなく個別のコンポーネントをimportできます。

\n
@import '@rdlabo/ionic-theme-ios27/dist/css/utils/translucent';\n@import '@rdlabo/ionic-theme-ios27/dist/css/components/ion-action-sheet';\n@import '@rdlabo/ionic-theme-ios27/dist/css/components/ion-alert';\n@import '@rdlabo/ionic-theme-ios27/dist/css/components/ion-button';\n/* Import the remaining components your application uses. */\n

コンポーネント単位でのDark Mode

\n

Dark Mode対応のコンポーネントを個別にimportする場合は、Always、System、Class modeでselectorが異なるためSCSSを使います。

\n

Always:

\n
@use '@rdlabo/ionic-theme-ios27/src/styles/utils/theme-dark';\n\n:root {\n  @include theme-dark.default-variables;\n}\n@include theme-dark.ion-list;\n@include theme-dark.ion-button;\n@include theme-dark.ion-fab;\n@include theme-dark.ion-tabs;\n@include theme-dark.ion-segment;\n

System:

\n
@use '@rdlabo/ionic-theme-ios27/src/styles/utils/theme-dark';\n\n@media (prefers-color-scheme: dark) {\n  :root {\n    @include theme-dark.default-variables;\n  }\n  @include theme-dark.ion-list;\n  @include theme-dark.ion-button;\n  @include theme-dark.ion-fab;\n  @include theme-dark.ion-tabs;\n  @include theme-dark.ion-segment;\n}\n

Class:

\n
@use '@rdlabo/ionic-theme-ios27/src/styles/utils/theme-dark';\n\n.ion-palette-dark {\n  @include theme-dark.default-variables;\n  @include theme-dark.ion-list;\n  @include theme-dark.ion-button;\n  @include theme-dark.ion-fab;\n  @include theme-dark.ion-tabs;\n  @include theme-dark.ion-segment;\n}\n

インタラクティブな例

\n

デモでrender済みの例を見る。

\n", + "html": "

CSS変数とSass mixinでテーマをカスタマイズしたり、コンポーネント単位で段階的に導入したりできます。マークアップ固有のopt-inは 特別なマークアップとクラス を参照してください。

\n

CSS変数

\n

ライブラリのデフォルトスタイルをデザインに合わせてカスタマイズできるよう、複数のCSS変数を提供しています。詳細は次のファイルを参照してください。
\nhttps://github.com/rdlabo-dev/ionic-theme-ios27/blob/ios27-v1.2.0-1/src/styles/default-variables.scsshttps://github.com/rdlabo-dev/ionic-theme-ios27/blob/ios27-v1.2.0-1/src/styles/default-variables.scss

\n

メニューの背景RGBは --ios-theme-menu-background-rgb(ライト: 225, 230, 240、ダーク: 26, 31, 34)、不透明度は --ios-theme-menu-background-opacity(既定値: 0.96)で指定します。どちらも ion-menu に設定できる公開CSS変数です。

\n

ボタン押下時の拡縮は :active に従い、少しovershootします。継続時間は共通のactivated transition変数ではなく、--ios-theme-button-press-duration(既定値: 380ms)で変更します。

\n

Liquid Glass mixin

\n

Liquid Glass mixinを使うには、メインパッケージからSCSSファイルをimportします。

\n
@use '@rdlabo/ionic-theme-ios27/src/styles/utils/api.scss';\n\nion-textarea label.textarea-wrapper {\n  @include api.glass-background;\n}\n

Native UI Shell(実験的機能)

\n

Capacitor iOSアプリでは、任意の実験的機能Native UI Shellで、対応する固定コントロールをUIKitとシステムのLiquid Glassで描画できます。内容とロジックはWebViewに残り、Ionicがルーティングと画面遷移を管理します。ガイドではBasecampとCapacitorに由来する考え方、導入、対応部品、Web描画へのフォールバックを説明します。

\n

コンポーネント単位のimport

\n

段階的に導入する場合は、テーマ全体ではなく個別のコンポーネントをimportできます。

\n
@import '@rdlabo/ionic-theme-ios27/dist/css/utils/translucent';\n@import '@rdlabo/ionic-theme-ios27/dist/css/components/ion-action-sheet';\n@import '@rdlabo/ionic-theme-ios27/dist/css/components/ion-alert';\n@import '@rdlabo/ionic-theme-ios27/dist/css/components/ion-button';\n/* Import the remaining components your application uses. */\n

コンポーネント単位でのDark Mode

\n

Dark Mode対応のコンポーネントを個別にimportする場合は、Always、System、Class modeでselectorが異なるためSCSSを使います。

\n

Always:

\n
@use '@rdlabo/ionic-theme-ios27/src/styles/utils/theme-dark';\n\n:root {\n  @include theme-dark.default-variables;\n}\n@include theme-dark.ion-list;\n@include theme-dark.ion-button;\n@include theme-dark.ion-fab;\n@include theme-dark.ion-tabs;\n@include theme-dark.ion-segment;\n

System:

\n
@use '@rdlabo/ionic-theme-ios27/src/styles/utils/theme-dark';\n\n@media (prefers-color-scheme: dark) {\n  :root {\n    @include theme-dark.default-variables;\n  }\n  @include theme-dark.ion-list;\n  @include theme-dark.ion-button;\n  @include theme-dark.ion-fab;\n  @include theme-dark.ion-tabs;\n  @include theme-dark.ion-segment;\n}\n

Class:

\n
@use '@rdlabo/ionic-theme-ios27/src/styles/utils/theme-dark';\n\n.ion-palette-dark {\n  @include theme-dark.default-variables;\n  @include theme-dark.ion-list;\n  @include theme-dark.ion-button;\n  @include theme-dark.ion-fab;\n  @include theme-dark.ion-tabs;\n  @include theme-dark.ion-segment;\n}\n

インタラクティブな例

\n

デモでrender済みの例を見る。

\n", "headings": [ { "id": "css%E5%A4%89%E6%95%B0", @@ -560,8 +570,13 @@ export const PROJECT = { "file": "migration.md", "section": "ガイド", "path": "/projects/ionic-theme-ios27/docs/migration", - "html": "

iOS 26テーマからの移行

\n

既存アプリが @rdlabo/ionic-theme-ios26 を使っている場合は、iOS 26を残して @rdlabo/ionic-theme-ios27 を追加する方法を推奨します。READMEの導入手順では、ブラウザの機能に応じてiOS 26/27のスタイルを切り替え、どちらにも対応しないブラウザではIonic標準のiOS外観を維持します。

\n

1. 新しいパッケージを追加する

\n

iOS 26パッケージを残したままiOS 27を追加します。新テーマには @ionic/core 8.8.1以降(Ionic 8/9)が必要です。

\n
npm install @rdlabo/ionic-theme-ios27\n

2. スタイルを切り替える

\n

グローバルSassで無条件に読み込んでいたiOS 26のスタイルを、排他的な2つの条件に置き換えます。classベースのダークモードの例です。

\n
+ @use 'sass:meta';\n+\n- @use '@rdlabo/ionic-theme-ios26/src/styles/default-variables.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect.scss';\n+ @supports (overflow-anchor: auto) {\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/default-variables');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27-dark-class');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/md-remove-ios-class-effect');\n+ }\n+\n+ @supports (text-wrap: pretty) and (not (overflow-anchor: auto)) {\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/default-variables');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect');\n+ }\n

Ionic側の対応するダークパレットも維持してください。system/alwaysの場合は両方の -dark-class を対応するvariantに変えます。md-ion-list-inset を使う場合は各条件内で対応するパッケージから読み込みます。どちらの条件も満たさないブラウザはIonic標準のスタイルを維持します。

\n

3. アニメーションを切り替える

\n

iOS 26からのimportをiOS 27に変更し、スタイルと同じブラウザ機能で有効化します。Ionicの初期化前に設定してください。

\n
- import { iosTransitionAnimation, popoverEnterAnimation, popoverLeaveAnimation } from '@rdlabo/ionic-theme-ios26';\n+ import { iosTransitionAnimation, popoverEnterAnimation, popoverLeaveAnimation } from '@rdlabo/ionic-theme-ios27';\n+\n+ function loadIOSAnimations() {\n+  if (typeof CSS === 'undefined') return {};\n+  if (!CSS.supports('overflow-anchor: auto') && !CSS.supports('text-wrap: pretty')) return {};\n+\n+  return {\n+    navAnimation: iosTransitionAnimation,\n+    popoverEnter: popoverEnterAnimation,\n+    popoverLeave: popoverLeaveAnimation,\n+  };\n+ }\n\n  provideIonicAngular({\n-  navAnimation: isPlatform('ios') ? iosTransitionAnimation : undefined,\n-  popoverEnter: isPlatform('ios') ? popoverEnterAnimation : undefined,\n-  popoverLeave: isPlatform('ios') ? popoverLeaveAnimation : undefined,\n+  ...(isPlatform('ios') ? loadIOSAnimations() : {}),\n  });\n

両方の世代でiOS 27の画面遷移とpopoverアニメーションを使います。古いブラウザはIonic標準のアニメーションを維持します。例はAngularです。Reactでは setupIonicReact、Vueでは IonicVue に同じオプションを渡します。

\n

4. カスタマイズ名を更新する

\n

アプリで使っているテーマ変数と除外クラスを更新します。

\n
  ion-content {\n-  --ios26-content-box-shadow-rgb: 0, 0, 0;\n+  --ios-theme-content-box-shadow-rgb: 0, 0, 0;\n  }\n\n- <ion-button class=\"ios26-disabled\">Standard Ionic button</ion-button>\n+ <ion-button class=\"ios-theme-disabled\">Standard Ionic button</ion-button>\n

旧名はdeprecatedのフォールバックとして残ります。対応ブラウザでライト・ダーク両モードの画面を確認してください。

\n

iOS 27だけに切り替える場合

\n

iOS 26パッケージを削除し、スタイルシートとアニメーションのimportをiOS 27に変更します。スタイルの差分は次のとおりです。

\n
- @use '@rdlabo/ionic-theme-ios26/src/styles/default-variables.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/default-variables.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27-dark-class.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/md-remove-ios-class-effect.scss';\n

アニメーションのimport元も @rdlabo/ionic-theme-ios26 から @rdlabo/ionic-theme-ios27 に変更します。既存の isPlatform('ios') 設定はそのまま使えます。iOS 27単体の導入手順も参照してください。無条件のimportではIonic iOSモードを使う全ブラウザに新しいスタイルが適用されます。

\n

iOS 27の命名

\n

iOS 27の条件内、またはiOS 27だけを使うアプリでは @rdlabo/ionic-theme-ios27 を読み込みます。スタイルシート名は ionic-theme-ios27.scss または ionic-theme-ios27.css です。dark-always、dark-system、dark-class のvariantも同様です。併用構成のiOS 26側では旧パッケージのスタイルシート名を維持します。

\n

CSS変数にはバージョンに依存しない --ios-theme-* を使います。--ios26-* はdeprecatedのフォールバックとして使え、両方ある場合は新名が優先されます。例えば --ios26-content-box-shadow-rgb を --ios-theme-content-box-shadow-rgb に変更します。

\n

テーマを除外するときは ios-theme-disabled を使います。ios26-disabled はdeprecatedの互換名として残ります。

\n

現在の名称は特別なマークアップとクラスとデフォルト変数を参照してください。以前の移行案内はiOS 26の移行ガイドにあります。

\n

送信ボタンの外観

\n

送信ボタンはIonicの各色の標準コントラスト値と、iOS 27の方向性を持つ縁取りを使います。テーマ専用のbrightness変数を削除してください。

\n
  :root {\n-  --ion-color-primary-brightness-rgb: 130, 255, 255;\n-  --ion-color-primary-brightness: #96feff;\n  }\n
", + "html": "

Native UI Shellの画面遷移アダプター

\n

withNativeUIShellTransition() を使うと、既存のIonic画面遷移アニメーションを維持しながらNative UI Shellの部品を連携できます。

\n\n
+ import { withNativeUIShellTransition } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\n  const ionicConfig = {\n-   navAnimation: existingTransition,\n+   navAnimation: withNativeUIShellTransition(existingTransition),\n  };\n

Ionicの初期化前に、既存設定へこのオプションを統合します。アダプターはアニメーションの効果、duration、easingを維持しながら、ネイティブ部品の退避、スワイプの進捗、キャンセルを連携します。既存テーマのスタイルシートのimportと、Native UI ShellまたはVertical Control Areaの起動処理は維持します。

\n

アダプターは画面遷移だけに使い、modalやpopoverのアニメーションは維持します。Ionicが遷移後に破棄するため、builderは遷移ごとに新しい Animation を作成してください。部品登録とアニメーションなしの遷移には、引き続きlifecycle eventを使います。独自builderが水平の戻るボタンを別途アニメーションする場合、.ios-theme-vertical-bars が有効な間はその効果を対象から外してください。

\n

導入手順と対応範囲は画面遷移アニメーションを接続するを参照してください。

\n

iOS 26テーマからの移行

\n

既存アプリが @rdlabo/ionic-theme-ios26 を使っている場合は、iOS 26を残して @rdlabo/ionic-theme-ios27 を追加する方法を推奨します。READMEの導入手順では、ブラウザの機能に応じてiOS 26/27のスタイルを切り替え、どちらにも対応しないブラウザではIonic標準のiOS外観を維持します。

\n

1. 新しいパッケージを追加する

\n

iOS 26パッケージを残したままiOS 27を追加します。新テーマには @ionic/core 8.8.1以降(Ionic 8/9)が必要です。

\n
npm install @rdlabo/ionic-theme-ios27\n

2. スタイルを切り替える

\n

グローバルSassで無条件に読み込んでいたiOS 26のスタイルを、排他的な2つの条件に置き換えます。classベースのダークモードの例です。

\n
+ @use 'sass:meta';\n+\n- @use '@rdlabo/ionic-theme-ios26/src/styles/default-variables.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect.scss';\n+ @supports (overflow-anchor: auto) {\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/default-variables');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27-dark-class');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/md-remove-ios-class-effect');\n+ }\n+\n+ @supports (text-wrap: pretty) and (not (overflow-anchor: auto)) {\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/default-variables');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect');\n+ }\n

Ionic側の対応するダークパレットも維持してください。system/alwaysの場合は両方の -dark-class を対応するvariantに変えます。md-ion-list-inset を使う場合は各条件内で対応するパッケージから読み込みます。どちらの条件も満たさないブラウザはIonic標準のスタイルを維持します。

\n

3. アニメーションを切り替える

\n

iOS 26からのimportをiOS 27に変更し、スタイルと同じブラウザ機能で有効化します。Ionicの初期化前に設定してください。

\n
- import { iosTransitionAnimation, popoverEnterAnimation, popoverLeaveAnimation } from '@rdlabo/ionic-theme-ios26';\n+ import { iosTransitionAnimation, popoverEnterAnimation, popoverLeaveAnimation } from '@rdlabo/ionic-theme-ios27';\n+\n+ function loadIOSAnimations() {\n+  if (typeof CSS === 'undefined') return {};\n+  if (!CSS.supports('overflow-anchor: auto') && !CSS.supports('text-wrap: pretty')) return {};\n+\n+  return {\n+    navAnimation: iosTransitionAnimation,\n+    popoverEnter: popoverEnterAnimation,\n+    popoverLeave: popoverLeaveAnimation,\n+  };\n+ }\n\n  provideIonicAngular({\n-  navAnimation: isPlatform('ios') ? iosTransitionAnimation : undefined,\n-  popoverEnter: isPlatform('ios') ? popoverEnterAnimation : undefined,\n-  popoverLeave: isPlatform('ios') ? popoverLeaveAnimation : undefined,\n+  ...(isPlatform('ios') ? loadIOSAnimations() : {}),\n  });\n

両方の世代でiOS 27の画面遷移とpopoverアニメーションを使います。古いブラウザはIonic標準のアニメーションを維持します。例はAngularです。Reactでは setupIonicReact、Vueでは IonicVue に同じオプションを渡します。

\n

4. カスタマイズ名を更新する

\n

アプリで使っているテーマ変数と除外クラスを更新します。

\n
  ion-content {\n-  --ios26-content-box-shadow-rgb: 0, 0, 0;\n+  --ios-theme-content-box-shadow-rgb: 0, 0, 0;\n  }\n\n- <ion-button class=\"ios26-disabled\">Standard Ionic button</ion-button>\n+ <ion-button class=\"ios-theme-disabled\">Standard Ionic button</ion-button>\n

旧名はdeprecatedのフォールバックとして残ります。対応ブラウザでライト・ダーク両モードの画面を確認してください。

\n

iOS 27だけに切り替える場合

\n

iOS 26パッケージを削除し、スタイルシートとアニメーションのimportをiOS 27に変更します。スタイルの差分は次のとおりです。

\n
- @use '@rdlabo/ionic-theme-ios26/src/styles/default-variables.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/default-variables.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27-dark-class.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/md-remove-ios-class-effect.scss';\n

アニメーションのimport元も @rdlabo/ionic-theme-ios26 から @rdlabo/ionic-theme-ios27 に変更します。既存の isPlatform('ios') 設定はそのまま使えます。iOS 27単体の導入手順も参照してください。無条件のimportではIonic iOSモードを使う全ブラウザに新しいスタイルが適用されます。

\n

iOS 27の命名

\n

iOS 27の条件内、またはiOS 27だけを使うアプリでは @rdlabo/ionic-theme-ios27 を読み込みます。スタイルシート名は ionic-theme-ios27.scss または ionic-theme-ios27.css です。dark-always、dark-system、dark-class のvariantも同様です。併用構成のiOS 26側では旧パッケージのスタイルシート名を維持します。

\n

CSS変数にはバージョンに依存しない --ios-theme-* を使います。--ios26-* はdeprecatedのフォールバックとして使え、両方ある場合は新名が優先されます。例えば --ios26-content-box-shadow-rgb を --ios-theme-content-box-shadow-rgb に変更します。

\n

テーマを除外するときは ios-theme-disabled を使います。ios26-disabled はdeprecatedの互換名として残ります。

\n

現在の名称は特別なマークアップとクラスとデフォルト変数を参照してください。以前の移行案内はiOS 26の移行ガイドにあります。

\n

送信ボタンの外観

\n

送信ボタンはIonicの各色の標準コントラスト値と、iOS 27の方向性を持つ縁取りを使います。テーマ専用のbrightness変数を削除してください。

\n
  :root {\n-  --ion-color-primary-brightness-rgb: 130, 255, 255;\n-  --ion-color-primary-brightness: #96feff;\n  }\n
", "headings": [ + { + "id": "native-ui-shell%E3%81%AE%E7%94%BB%E9%9D%A2%E9%81%B7%E7%A7%BB%E3%82%A2%E3%83%80%E3%83%97%E3%82%BF%E3%83%BC", + "text": "Native UI Shellの画面遷移アダプター", + "level": 2 + }, { "id": "ios-26%E3%83%86%E3%83%BC%E3%83%9E%E3%81%8B%E3%82%89%E3%81%AE%E7%A7%BB%E8%A1%8C", "text": "iOS 26テーマからの移行", @@ -615,7 +630,7 @@ export const PROJECT = { "file": "api.md", "section": "リファレンス", "path": "/projects/ionic-theme-ios27/docs/api", - "html": "

@rdlabo/ionic-theme-ios27 v1.2.0-0 が公開するJavaScript APIのリファレンスです。CSSとSassのentry pointはREADMEで説明します。

\n

Effect

\n

function registerTabBarEffect

(targetElement: HTMLElement) => registeredEffect | undefined

Ionic Tab BarにLiquid Glassの選択effectを登録します。

\n\n\n

function registerSegmentEffect

(targetElement: HTMLElement) => registeredEffect | undefined

Ionic SegmentにLiquid Glassの選択effectを登録します。

\n\n\n

interface registeredEffect

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberTypeDescription
destroy() => void登録時に作成したlistenerとeffect elementを削除します。
\n\n

interface EffectScales

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropTypeDescription
smallstringSmall effectのscaleです。
mediumstringMedium effectのscaleです。
largestringLarge effectのscaleです。
xlargestringExtra Large effectのscaleです。
\n\n

Searchable Tab Bar

\n

function attachTabBarSearchable

(ionTabBar: HTMLElement, ionFabButton: HTMLElement, ionFooter: HTMLElement) => TabBarSearchableFunction

Searchable Tab Barのtransitionを設定し、event handlerを返します。

\n\n\n

enum TabBarSearchableType

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberValueDescription
Enter\"enter\"Searchable modeへ入ります。
Leave\"leave\"Searchable modeから戻ります。
\n\n

type alias TabBarSearchableFunction

(event: Event, type: TabBarSearchableType) => Promise<void>

\n\n

Animation

\n

function iosTransitionAnimation

(navEl: HTMLElement, opts: TransitionOptions) => Animation

PackageのiOS navigation transitionを生成します。

\n\n\n

function setConfig

(config: Partial<IosTransitionConfig>) => void

画面遷移の角丸半径を設定します。既定値は 0 で、ネイティブアプリでは計測したWebViewの半径を渡せます。

\n\n\n

interface IosTransitionConfig

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropTypeDescription
radiusnumber画面遷移の角丸半径。
\n\n

function popoverEnterAnimation

(baseEl: HTMLElement, opts?: any) => Animation

iOS Popoverのenter animationを生成します。

\n\n\n

function popoverLeaveAnimation

(baseEl: HTMLElement) => Animation

iOS Popoverのleave animationを生成します。

\n\n\n

Searchbar

\n

function supportSeachbarCancelButtonIcon

(searchbar: HTMLIonSearchbarElement) => SearchbarCancelButtonIconSupport

iOS modeでIonicの cancelButtonIcon を描画する一時的な補助です。公開API名の Seachbar はこの綴りでimportします。初期化済みの要素を渡してください。

\n\n\n

interface SearchbarCancelButtonIconSupport

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberTypeDescription
refresh() => voidJavaScriptプロパティで変更した cancelButtonIcon を再読込します。
destroy() => voidobserverと挿入アイコンを削除して文字表示を復元します。
\n\n

Native UI Shell (Experimental)

\n

次のAPIと型は @rdlabo/ionic-theme-ios27/native からimportします。Native UI Shellガイドに導入条件とフォールバックを記載しています。

\n

function enableNativeUIShell

(options?: NativeUIShellOptions) => Promise<NativeUIShellHandle>

起動時に一度呼びます。同じ設定で繰り返すと稼働中のruntimeを共有し、稼働中に異なる設定を指定するとエラーになります。非対応環境ではWeb状態のhandleを返します。enabled: false で稼働中のネイティブ描画を停止し、Web描画に戻します。

\n\n\n

function configureNativeTransition

() => Promise<WebViewMetrics>

ネイティブWebViewの角丸半径を取得し、ネイティブ部品を有効にせず画面遷移へ適用します。他のプラットフォームでは半径は 0 です。

\n\n\n

interface NativeUIShellOptions

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropTypeDescription
enabledbooleanネイティブ描画を全体で有効にします。既定値は true。
controlsNativeUIShellControls指定時は true の部品だけをネイティブ描画の対象にします。
\n\n

interface NativeUIShellControls

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropTypeDescription
tabsbooleanタブバーとネイティブ検索。
toolbarbooleantoolbar、戻る・メニュー。
segmentbooleansegment。
fabbooleanFAB。
\n\n

interface NativeUIShellHandle

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberTypeDescription
getStatus() => NativeUIShellStatus現在の状態を取得します。
suspend() => Promise<NativeUIShellSuspension>resume() まで対象部品をWeb描画に戻します。
destroy() => Promise<void>Web描画を復元し、ネイティブ部品とruntimeを破棄します。
\n\n

interface NativeUIShellSuspension

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberTypeDescription
resume() => Promise<void>一時停止を解除します。すべて解除すると再描画を評価します。
\n\n

interface WebViewMetrics

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropTypeDescription
radiusnumberWebViewの角丸半径。
\n\n

interface NativeUIShellStatus

interface NativeUIShellStatus {\n  state: 'web' | 'native' | 'stopped';\n  projected: number;\n  updates: number;\n  reason?: string;\n}\n

projected はネイティブ描画中の部品数、updates は更新数、reason はWebフォールバック・停止理由です。bridge障害による stopped から自動再接続はしません。handleを destroy() してから再度有効化します。

\n\n

type alias NativeUIShellComponent

'ion-button' | 'ion-buttons' | 'ion-back-button' | 'ion-menu-button' | 'ion-tab-bar' | 'ion-segment' | 'ion-fab'

runtimeが扱うコンポーネントtagのunionです。個別の対応条件はガイドを参照してください。

\n\n\n

iPhone Duo / Vertical Control Area(実験的機能)

\n

次のAPIは @rdlabo/ionic-theme-ios27/vertical-bars または @rdlabo/ionic-theme-ios27/native からimportします。独立したentry pointはiOS 27テーマや完全なNative UI Shellなしで動作します。導入方法、toolchainの要件、WebフォールバックはiPhone Duo対応を参照してください。

\n

function enableVerticalControlArea

() => Promise<VerticalControlAreaHandle>

縦の領域の操作部品だけにruntimeを起動します。これと enableNativeUIShell() の一方だけを起動してください。同じ設定で繰り返すと共有し、稼働中に異なる設定を指定するとエラーになります。

\n\n\n

function setVerticalControlAreaPlacement

(placement: VerticalBarEdge | VerticalBarPlacement, rtl?: boolean) => void

ion-app のマウント後、アプリが選んだ配置をCSSとWeb・ネイティブの操作部品へ適用します。論理方向は最も近い dir 属性か明示的な rtl で解決します。null を渡すと通常のレイアウトへ戻ります。

\n\n\n

interface VerticalControlAreaHandle

NativeUIShellHandle に setPlacement を追加した型です。setVerticalControlAreaPlacement と同じ関数です。

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberType説明
setPlacement(placement: VerticalBarEdge | VerticalBarPlacement, rtl?: boolean) => voidWebとネイティブの部品に配置を適用します。
\n\n\n

type alias VerticalBarEdge

'leading' | 'trailing' | null

読む方向に対する論理的な端です。null は縦の操作領域がない状態を表します。

\n\n\n

interface VerticalBarPlacement

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropType説明
edgeVerticalBarEdge操作領域の論理的な端。
insetnumberUIKitのsafe area inset(ポイント)。
\n\n

enum HingeStatus

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberValue
Closed\"closed\"
PartiallyOpen\"partiallyOpen\"
FullyOpen\"fullyOpen\"
\n\n

interface DeviceLayout

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropType説明
placementVerticalBarPlacement操作領域の端と実測したsafe area inset。
hingeStatusHingeStatus | nullヒンジの状態。取得できない場合は null。
webViewMetricsWebViewMetricsWebViewの有効な角丸半径。
\n\n

module IonicNativeUIShell

同梱のCapacitorプラグインは描画runtimeを起動せずレイアウト情報を取得できます。Web実装はないため、呼び出し前に Capacitor.getPlatform() === 'ios' を確認してください。

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberType説明
getDeviceLayout() => Promise<DeviceLayout>初期値や一度だけの計測値を取得します。
startDeviceLayoutMonitoring() => Promise<void>監視の参照を取得します。
stopDeviceLayoutMonitoring() => Promise<void>参照を解放し、最後の解放でイベントが止まります。
addListener(name: 'deviceLayoutChange', listener: (event: DeviceLayout) => void) => Promise<PluginListenerHandle>レイアウト変更を購読します。終了時に返されたlistenerを削除します。

ネイティブ描画runtimeが稼働中なら既に監視の参照を保持するため、利用元は購読と初期値の取得だけを行います。bridgeのsnapshotや操作イベントのメソッドは内部の実装詳細です。

\n\n\n\n
", + "html": "

@rdlabo/ionic-theme-ios27 v1.2.0-1 が公開するJavaScript APIのリファレンスです。CSSとSassのentry pointはREADMEで説明します。

\n

Effect

\n

function registerTabBarEffect

(targetElement: HTMLElement) => registeredEffect | undefined

Ionic Tab BarにLiquid Glassの選択effectを登録します。

\n\n\n

function registerSegmentEffect

(targetElement: HTMLElement) => registeredEffect | undefined

Ionic SegmentにLiquid Glassの選択effectを登録します。

\n\n\n

interface registeredEffect

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberTypeDescription
destroy() => void登録時に作成したlistenerとeffect elementを削除します。
\n\n

interface EffectScales

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropTypeDescription
smallstringSmall effectのscaleです。
mediumstringMedium effectのscaleです。
largestringLarge effectのscaleです。
xlargestringExtra Large effectのscaleです。
\n\n

Searchable Tab Bar

\n

function attachTabBarSearchable

(ionTabBar: HTMLElement, ionFabButton: HTMLElement, ionFooter: HTMLElement) => TabBarSearchableFunction

Searchable Tab Barのtransitionを設定し、event handlerを返します。

\n\n\n

enum TabBarSearchableType

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberValueDescription
Enter\"enter\"Searchable modeへ入ります。
Leave\"leave\"Searchable modeから戻ります。
\n\n

type alias TabBarSearchableFunction

(event: Event, type: TabBarSearchableType) => Promise<void>

\n\n

Animation

\n

function withNativeUIShellTransition

(builder: AnimationBuilder) => AnimationBuilder

Ionicの画面遷移builderをラップし、返されるアニメーションを維持しながら、ネイティブ部品の退避、スワイプの進捗、キャンセルを連携します。パッケージrootと /vertical-bars からexportされます。navAnimation に登録し、遷移ごとに新しい Animation を使ってください。本パッケージの iosTransitionAnimation には既に組み込まれています。Ionic標準と独自builderの設定は既存テーマでの導入を参照してください。

\n\n\n

function iosTransitionAnimation

(navEl: HTMLElement, opts: TransitionOptions) => Animation

PackageのiOS navigation transitionを生成します。

\n\n\n

function setConfig

(config: Partial<IosTransitionConfig>) => void

画面遷移の角丸半径を設定します。既定値は 0 で、ネイティブアプリでは計測したWebViewの半径を渡せます。

\n\n\n

interface IosTransitionConfig

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropTypeDescription
radiusnumber画面遷移の角丸半径。
\n\n

function popoverEnterAnimation

(baseEl: HTMLElement, opts?: any) => Animation

iOS Popoverのenter animationを生成します。

\n\n\n

function popoverLeaveAnimation

(baseEl: HTMLElement) => Animation

iOS Popoverのleave animationを生成します。

\n\n\n

Searchbar

\n

function supportSeachbarCancelButtonIcon

(searchbar: HTMLIonSearchbarElement) => SearchbarCancelButtonIconSupport

iOS modeでIonicの cancelButtonIcon を描画する一時的な補助です。公開API名の Seachbar はこの綴りでimportします。初期化済みの要素を渡してください。

\n\n\n

interface SearchbarCancelButtonIconSupport

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberTypeDescription
refresh() => voidJavaScriptプロパティで変更した cancelButtonIcon を再読込します。
destroy() => voidobserverと挿入アイコンを削除して文字表示を復元します。
\n\n

Native UI Shell (Experimental)

\n

次のAPIと型は @rdlabo/ionic-theme-ios27/native からimportします。Native UI Shellガイドに導入条件とフォールバックを記載しています。

\n

function enableNativeUIShell

(options?: NativeUIShellOptions) => Promise<NativeUIShellHandle>

起動時に一度呼びます。同じ設定で繰り返すと稼働中のruntimeを共有し、稼働中に異なる設定を指定するとエラーになります。非対応環境ではWeb状態のhandleを返します。enabled: false で稼働中のネイティブ描画を停止し、Web描画に戻します。

\n\n\n

function configureNativeTransition

() => Promise<WebViewMetrics>

ネイティブWebViewの角丸半径を取得し、ネイティブ部品を有効にせず画面遷移へ適用します。他のプラットフォームでは半径は 0 です。

\n\n\n

interface NativeUIShellOptions

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropTypeDescription
enabledbooleanネイティブ描画を全体で有効にします。既定値は true。
controlsNativeUIShellControls指定時は true の部品だけをネイティブ描画の対象にします。
\n\n

interface NativeUIShellControls

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropTypeDescription
tabsbooleanタブバーとネイティブ検索。
toolbarbooleantoolbar、戻る・メニュー。
segmentbooleansegment。
fabbooleanFAB。
\n\n

interface NativeUIShellHandle

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberTypeDescription
getStatus() => NativeUIShellStatus現在の状態を取得します。
suspend() => Promise<NativeUIShellSuspension>resume() まで対象部品をWeb描画に戻します。
destroy() => Promise<void>Web描画を復元し、ネイティブ部品とruntimeを破棄します。
\n\n

interface NativeUIShellSuspension

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberTypeDescription
resume() => Promise<void>一時停止を解除します。すべて解除すると再描画を評価します。
\n\n

interface WebViewMetrics

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropTypeDescription
radiusnumberWebViewの角丸半径。
\n\n

interface NativeUIShellStatus

interface NativeUIShellStatus {\n  state: 'web' | 'native' | 'stopped';\n  projected: number;\n  updates: number;\n  reason?: string;\n}\n

projected はネイティブ描画中の部品数、updates は更新数、reason はWebフォールバック・停止理由です。bridge障害による stopped から自動再接続はしません。handleを destroy() してから再度有効化します。

\n\n

type alias NativeUIShellComponent

'ion-button' | 'ion-buttons' | 'ion-back-button' | 'ion-menu-button' | 'ion-tab-bar' | 'ion-segment' | 'ion-fab'

runtimeが扱うコンポーネントtagのunionです。個別の対応条件はガイドを参照してください。

\n\n\n

iPhone Duo / Vertical Control Area(実験的機能)

\n

次のAPIは @rdlabo/ionic-theme-ios27/vertical-bars または @rdlabo/ionic-theme-ios27/native からimportします。独立したentry pointはiOS 27テーマや完全なNative UI Shellなしで動作します。導入方法、toolchainの要件、WebフォールバックはiPhone Duo対応を参照してください。

\n

function enableVerticalControlArea

() => Promise<VerticalControlAreaHandle>

縦の領域の操作部品だけにruntimeを起動します。これと enableNativeUIShell() の一方だけを起動してください。同じ設定で繰り返すと共有し、稼働中に異なる設定を指定するとエラーになります。

\n\n\n

function setVerticalControlAreaPlacement

(placement: VerticalBarEdge | VerticalBarPlacement, rtl?: boolean) => void

ion-app のマウント後、アプリが選んだ配置をCSSとWeb・ネイティブの操作部品へ適用します。論理方向は最も近い dir 属性か明示的な rtl で解決します。null を渡すと通常のレイアウトへ戻ります。

\n\n\n

interface VerticalControlAreaHandle

NativeUIShellHandle に setPlacement を追加した型です。setVerticalControlAreaPlacement と同じ関数です。

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberType説明
setPlacement(placement: VerticalBarEdge | VerticalBarPlacement, rtl?: boolean) => voidWebとネイティブの部品に配置を適用します。
\n\n\n

type alias VerticalBarEdge

'leading' | 'trailing' | null

読む方向に対する論理的な端です。null は縦の操作領域がない状態を表します。

\n\n\n

interface VerticalBarPlacement

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropType説明
edgeVerticalBarEdge操作領域の論理的な端。
insetnumberUIKitのsafe area inset(ポイント)。
\n\n

enum HingeStatus

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberValue
Closed\"closed\"
PartiallyOpen\"partiallyOpen\"
FullyOpen\"fullyOpen\"
\n\n

interface DeviceLayout

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PropType説明
placementVerticalBarPlacement操作領域の端と実測したsafe area inset。
hingeStatusHingeStatus | nullヒンジの状態。取得できない場合は null。
webViewMetricsWebViewMetricsWebViewの有効な角丸半径。
\n\n

module IonicNativeUIShell

同梱のCapacitorプラグインは描画runtimeを起動せずレイアウト情報を取得できます。Web実装はないため、呼び出し前に Capacitor.getPlatform() === 'ios' を確認してください。

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
MemberType説明
getDeviceLayout() => Promise<DeviceLayout>初期値や一度だけの計測値を取得します。
startDeviceLayoutMonitoring() => Promise<void>監視の参照を取得します。
stopDeviceLayoutMonitoring() => Promise<void>参照を解放し、最後の解放でイベントが止まります。
addListener(name: 'deviceLayoutChange', listener: (event: DeviceLayout) => void) => Promise<PluginListenerHandle>レイアウト変更を購読します。終了時に返されたlistenerを削除します。

ネイティブ描画runtimeが稼働中なら既に監視の参照を保持するため、利用元は購読と初期値の取得だけを行います。bridgeのsnapshotや操作イベントのメソッドは内部の実装詳細です。

\n\n\n\n
", "headings": [ { "id": "effect", @@ -667,6 +682,11 @@ export const PROJECT = { "text": "Animation", "level": 2 }, + { + "id": "function-withnativeuishelltransition", + "text": "function withNativeUIShellTransition", + "level": 4 + }, { "id": "function-iostransitionanimation", "text": "function iosTransitionAnimation", diff --git a/projects/docs/src/ionic-theme-ios27/docs/api.md b/projects/docs/src/ionic-theme-ios27/docs/api.md index b6c3670..facc310 100644 --- a/projects/docs/src/ionic-theme-ios27/docs/api.md +++ b/projects/docs/src/ionic-theme-ios27/docs/api.md @@ -2,7 +2,7 @@ title: API --- -Reference for the JavaScript API exported by `@rdlabo/ionic-theme-ios27` v1.2.0-0. CSS and Sass entry points remain documented in the README. +Reference for the JavaScript API exported by `@rdlabo/ionic-theme-ios27` v1.2.0-1. CSS and Sass entry points remain documented in the README. ## Effects @@ -54,6 +54,12 @@ Attaches the searchable tab-bar transition and returns its event handler. ## Animations +#### `function` withNativeUIShellTransition + +`(builder: AnimationBuilder) => AnimationBuilder` + +Wraps an Ionic navigation animation builder to coordinate native control retirement, swipe progress, and cancellation while preserving the returned animation. Exported from the package root and `/vertical-bars`. Register it as `navAnimation`; use a fresh `Animation` for each navigation. The package's `iosTransitionAnimation` already includes this adapter. See [existing-theme setup](/docs/iphone-duo-with-original-theme) for Ionic's default and custom builders. + #### `function` iosTransitionAnimation `(navEl: HTMLElement, opts: TransitionOptions) => Animation` diff --git a/projects/docs/src/ionic-theme-ios27/docs/ja/api.md b/projects/docs/src/ionic-theme-ios27/docs/ja/api.md index fff5ce6..1cf2d94 100644 --- a/projects/docs/src/ionic-theme-ios27/docs/ja/api.md +++ b/projects/docs/src/ionic-theme-ios27/docs/ja/api.md @@ -2,7 +2,7 @@ title: API --- -`@rdlabo/ionic-theme-ios27` v1.2.0-0 が公開するJavaScript APIのリファレンスです。CSSとSassのentry pointはREADMEで説明します。 +`@rdlabo/ionic-theme-ios27` v1.2.0-1 が公開するJavaScript APIのリファレンスです。CSSとSassのentry pointはREADMEで説明します。 ## Effect @@ -54,6 +54,12 @@ Searchable Tab Barのtransitionを設定し、event handlerを返します。 ## Animation +#### `function` withNativeUIShellTransition + +`(builder: AnimationBuilder) => AnimationBuilder` + +Ionicの画面遷移builderをラップし、返されるアニメーションを維持しながら、ネイティブ部品の退避、スワイプの進捗、キャンセルを連携します。パッケージrootと `/vertical-bars` からexportされます。`navAnimation` に登録し、遷移ごとに新しい `Animation` を使ってください。本パッケージの `iosTransitionAnimation` には既に組み込まれています。Ionic標準と独自builderの設定は[既存テーマでの導入](/docs/iphone-duo-with-original-theme)を参照してください。 + #### `function` iosTransitionAnimation `(navEl: HTMLElement, opts: TransitionOptions) => Animation` diff --git a/projects/docs/src/ionic-theme-ios27/docs/ja/features.md b/projects/docs/src/ionic-theme-ios27/docs/ja/features.md index 382666a..e837aee 100644 --- a/projects/docs/src/ionic-theme-ios27/docs/ja/features.md +++ b/projects/docs/src/ionic-theme-ios27/docs/ja/features.md @@ -7,7 +7,7 @@ CSS変数とSass mixinでテーマをカスタマイズしたり、コンポー ## CSS変数 ライブラリのデフォルトスタイルをデザインに合わせてカスタマイズできるよう、複数のCSS変数を提供しています。詳細は次のファイルを参照してください。 -https://github.com/rdlabo-dev/ionic-theme-ios27/blob/ios27-v1.2.0-0/src/styles/default-variables.scss +https://github.com/rdlabo-dev/ionic-theme-ios27/blob/ios27-v1.2.0-1/src/styles/default-variables.scss メニューの背景RGBは `--ios-theme-menu-background-rgb`(ライト: `225, 230, 240`、ダーク: `26, 31, 34`)、不透明度は `--ios-theme-menu-background-opacity`(既定値: `0.96`)で指定します。どちらも `ion-menu` に設定できる公開CSS変数です。 diff --git a/projects/docs/src/ionic-theme-ios27/docs/ja/iphone-duo-with-original-theme.md b/projects/docs/src/ionic-theme-ios27/docs/ja/iphone-duo-with-original-theme.md index be98680..7e81890 100644 --- a/projects/docs/src/ionic-theme-ios27/docs/ja/iphone-duo-with-original-theme.md +++ b/projects/docs/src/ionic-theme-ios27/docs/ja/iphone-duo-with-original-theme.md @@ -15,7 +15,7 @@ title: 既存テーマでiPhone Duoに対応する(実験的機能) Ionic `>=8.8.1 <10` とCapacitor Core `>=8 <9` を使う既存アプリを前提とします。既存のCapacitor 8は再インストール不要です。別majorを使う場合は、Core・CLI・platformパッケージをまとめて移行してから進めてください。Capacitorを使わないWeb専用アプリでは `@capacitor/core@^8` もインストールします。JavaScriptのentry pointはChromeでもこの依存を必要とします。 ```bash -npm install @rdlabo/ionic-theme-ios27@1.2.0-0 +npm install @rdlabo/ionic-theme-ios27@1.2.0-1 ``` 既存テーマのimportを維持し、グローバルSassファイルに次を追加します。 @@ -38,19 +38,47 @@ npm install @rdlabo/ionic-theme-ios27@1.2.0-0 プレビューは物理的な右側に `80px` を確保します。左側を試すには `ios-theme-vertical-bars-left` も追加します。 -### 3. Ionicの初期化前に画面遷移を設定する +### 3. 画面遷移アニメーションを接続する -画面遷移をimportし、Ionicの `navAnimation` に登録します。操作領域のruntimeを起動するだけでは、この設定は登録されません。このアニメーションは画面遷移前にネイティブ部品の退避を待ち、縦レイアウトでは水平の戻るボタンをアニメーション対象から外します。既存テーマのスタイルは維持しますが、iOSの画面遷移には本パッケージのアニメーションを使います。 +Ionicの初期化前に `navAnimation` を設定します。操作領域のruntimeを起動するだけでは、この設定は登録されません。アダプターは既存アニメーションを維持しながらネイティブ部品の退避を待ち、スワイプの進捗とキャンセルを連携します。 + +#### Ionicの標準アニメーションを維持する + +`navAnimation` を設定していない場合は、Ionic標準のbuilderをラップします。Ionicが遷移時に渡す `mode` でbuilderを選び、`ios` と `md` のどちらも通常のアニメーションを維持します。 ```ts -import { iosTransitionAnimation } from '@rdlabo/ionic-theme-ios27'; +import { iosTransitionAnimation, mdTransitionAnimation, type AnimationBuilder } from '@ionic/core'; +import { withNativeUIShellTransition } from '@rdlabo/ionic-theme-ios27/vertical-bars'; + +const defaultTransition: AnimationBuilder = (baseEl, opts) => + (opts.mode === 'ios' ? iosTransitionAnimation : mdTransitionAnimation)(baseEl, opts); const ionicConfig = { - navAnimation: iosTransitionAnimation, + navAnimation: withNativeUIShellTransition(defaultTransition), }; ``` -初期化前に、既存のIonic設定の `ios` mode用設定へこのオプションを統合します。Angularでは `provideIonicAngular()`、Reactでは `setupIonicReact()`、Vueでは `IonicVue` pluginのオプションへ渡してください。既存の `md` 用アニメーション設定は維持します。JavaScriptのentry pointをimportしても、iOS 27テーマのスタイルシートは読み込まれません。 +初期化前に、既存のIonic設定へこのオプションを統合します。Angularでは `provideIonicAngular()`、Reactでは `setupIonicReact()`、Vueでは `IonicVue` pluginのオプションへ渡してください。既存テーマのスタイルシートのimportは維持します。iOS 27テーマのスタイルシートは不要です。 + +#### 独自アニメーションを維持する + +既に `navAnimation` を設定している場合は、そのbuilderをラップします。 + +```ts +import type { AnimationBuilder } from '@ionic/core'; +import { withNativeUIShellTransition } from '@rdlabo/ionic-theme-ios27/vertical-bars'; + +// アプリで既に使っているanimation builderを渡します。 +const configureNavigation = (existingTransition: AnimationBuilder) => ({ + navAnimation: withNativeUIShellTransition(existingTransition), +}); +``` + +アダプターは元の `Animation` を返し、効果、duration、easingを維持します。画面遷移だけに使い、modalやpopoverのアニメーションには使いません。Ionicが遷移後に破棄するため、builderは遷移ごとに新しい `Animation` を返してください。部品登録とアニメーションなしの遷移には、引き続きlifecycle eventを使います。 + +アダプターは、水平の戻るボタンへの効果を含むbuilderのアニメーション対象を維持します。縦レイアウトでその効果を除外するiOS 27の画面遷移が必要なら、`@rdlabo/ionic-theme-ios27` の `iosTransitionAnimation` を `navAnimation` に使ってください。こちらにはアダプターが組み込まれているため、ラップは不要です。 + +`withNativeUIShellTransition()` は `1.2.0-1` 以降で利用できます。 ### 4. App rootのマウント後に操作部品を起動する diff --git a/projects/docs/src/ionic-theme-ios27/docs/ja/migration.md b/projects/docs/src/ionic-theme-ios27/docs/ja/migration.md index 25baccc..dee013d 100644 --- a/projects/docs/src/ionic-theme-ios27/docs/ja/migration.md +++ b/projects/docs/src/ionic-theme-ios27/docs/ja/migration.md @@ -2,6 +2,29 @@ title: 移行 --- +## Native UI Shellの画面遷移アダプター + +`withNativeUIShellTransition()` を使うと、既存のIonic画面遷移アニメーションを維持しながらNative UI Shellの部品を連携できます。 + +- 本パッケージの `iosTransitionAnimation` を使っている場合、設定変更は不要です。内部で共通アダプターを使うため、追加のラップは不要です。 +- `navAnimation` を設定せずIonic標準のアニメーションを使っている場合は、[Ionicの標準アニメーションを維持する](/docs/iphone-duo-with-original-theme#ionicの標準アニメーションを維持する)を参照してください。遷移modeに応じてIonic標準のiOSまたはMD builderを選ぶ例を掲載しています。 +- Native UI Shellまたは独立したVertical Control Areaで独自の画面遷移を使う場合、Ionic設定時に既存のbuilderをラップします。 + +```diff ++ import { withNativeUIShellTransition } from '@rdlabo/ionic-theme-ios27/vertical-bars'; + + const ionicConfig = { +- navAnimation: existingTransition, ++ navAnimation: withNativeUIShellTransition(existingTransition), + }; +``` + +Ionicの初期化前に、既存設定へこのオプションを統合します。アダプターはアニメーションの効果、duration、easingを維持しながら、ネイティブ部品の退避、スワイプの進捗、キャンセルを連携します。既存テーマのスタイルシートのimportと、Native UI ShellまたはVertical Control Areaの起動処理は維持します。 + +アダプターは画面遷移だけに使い、modalやpopoverのアニメーションは維持します。Ionicが遷移後に破棄するため、builderは遷移ごとに新しい `Animation` を作成してください。部品登録とアニメーションなしの遷移には、引き続きlifecycle eventを使います。独自builderが水平の戻るボタンを別途アニメーションする場合、`.ios-theme-vertical-bars` が有効な間はその効果を対象から外してください。 + +導入手順と対応範囲は[画面遷移アニメーションを接続する](/docs/iphone-duo-with-original-theme#3.-画面遷移アニメーションを接続する)を参照してください。 + ## iOS 26テーマからの移行 既存アプリが `@rdlabo/ionic-theme-ios26` を使っている場合は、iOS 26を残して `@rdlabo/ionic-theme-ios27` を追加する方法を推奨します。[READMEの導入手順](/docs/readme#get-started)では、ブラウザの機能に応じてiOS 26/27のスタイルを切り替え、どちらにも対応しないブラウザではIonic標準のiOS外観を維持します。 @@ -112,7 +135,7 @@ CSS変数にはバージョンに依存しない `--ios-theme-*` を使います テーマを除外するときは `ios-theme-disabled` を使います。`ios26-disabled` はdeprecatedの互換名として残ります。 -現在の名称は[特別なマークアップとクラス](/docs/special-markup)と[デフォルト変数](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/ios27-v1.2.0-0/src/styles/default-variables.scss)を参照してください。以前の移行案内は[iOS 26の移行ガイド](/ionic-theme-ios26/docs/migration)にあります。 +現在の名称は[特別なマークアップとクラス](/docs/special-markup)と[デフォルト変数](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/ios27-v1.2.0-1/src/styles/default-variables.scss)を参照してください。以前の移行案内は[iOS 26の移行ガイド](/ionic-theme-ios26/docs/migration)にあります。 ## 送信ボタンの外観 diff --git a/projects/docs/src/ionic-theme-ios27/docs/ja/native-ui-shell.md b/projects/docs/src/ionic-theme-ios27/docs/ja/native-ui-shell.md index edb6ce1..70edfd5 100644 --- a/projects/docs/src/ionic-theme-ios27/docs/ja/native-ui-shell.md +++ b/projects/docs/src/ionic-theme-ios27/docs/ja/native-ui-shell.md @@ -118,7 +118,7 @@ FABはIonicの `activated`、子ごとの `show`、`close()`、元のclick handl FAB、リスト、ボタンに独自host animationやtransitionがあれば、削除するまでWebです。transformは標準のidentityと隠れた子のscale(0)に対応し、独自scaleは非対応です。`display:none` 内の子では独自transformがあっても計算値が `none` になる場合があるため、配置可能になった時に検査し、必要ならFAB全体をWebへ戻します。stylesheetの解析や、非表示配置を予測するための一時的なリスト展開はしません。 -`src/transition/ios.transition.ts` はネイティブ表示の終了を待ってWebアニメーションを始めます。待機中のinteractive progressと完了・キャンセルはqueueに保持します。動かない共有タブは維持します。初回描画とanimation builderなしの遷移は、起動runtimeとIonic lifecycle eventで対応します。 +組み込みの `iosTransitionAnimation` はネイティブ表示の終了を待ってWebアニメーションを始めます。待機中のinteractive progressと完了・キャンセルはqueueに保持します。動かない共有タブは維持します。初回描画とanimation builderなしの遷移は、起動runtimeとIonic lifecycle eventで対応します。Ionic標準または独自の画面遷移builderには、パッケージrootと `/vertical-bars` からexportされる `withNativeUIShellTransition()` で同じ連携を追加できます。導入手順は[画面遷移アニメーションを接続する](/docs/iphone-duo-with-original-theme#3.-画面遷移アニメーションを接続する)を参照してください。 タブ切り替えではWeb・ネイティブ間のcrossfadeを省き、終了するUIKitのsnapshotが次のタブへ重なるのを防ぎます。`ionViewWillLeave` でrouterのURLとまだ選択中のタブを比較し、vanilla環境では `ionTabsWillChange` / `ionTabsDidChange` のDOMイベントも使って検出します。スタックのpush・popでは通常の180msの引き継ぎを維持します。 @@ -213,9 +213,9 @@ destroy() => Promise ## ソース構成 -[src/native/components](https://github.com/rdlabo-dev/ionic-theme-ios27/tree/ios27-v1.2.0-0/src/native/components) の各TypeScript moduleがIonic tagとDOM readerを定義します。`components/index.ts` が探索selectorとcomponent型をまとめます。共有のDOM計測、項目データ、SVG描画は `src/native/shared`、同期・表示切り替え・lifecycleは `runtime.ts` が担当します。 +[src/native/components](https://github.com/rdlabo-dev/ionic-theme-ios27/tree/ios27-v1.2.0-1/src/native/components) の各TypeScript moduleがIonic tagとDOM readerを定義します。`components/index.ts` が探索selectorとcomponent型をまとめます。共有のDOM計測、項目データ、SVG描画は `src/native/shared`、同期・表示切り替え・lifecycleは `runtime.ts` が担当します。 -iOSの[Components](https://github.com/rdlabo-dev/ionic-theme-ios27/tree/ios27-v1.2.0-0/ios/Sources/IonicNativeUIShellPlugin/Components)はUIKit部品の生成・更新・名前を管理します。`ShellButton` が通常・戻る・メニューボタンの実装を共有し、`Shared` がhost view、型付きsnapshot、形状、色、画像cacheを管理します。Capacitorは完全なsnapshotを `Decodable` で一度decodeし、描画側は型付きmodelと `Equatable` で内容を比較します。不正batchは表示変更前に拒否します。`IonicNativeUIShellPlugin.swift` がCapacitor呼び出し、revision、ネイティブviewの寿命を調整します。 +iOSの[Components](https://github.com/rdlabo-dev/ionic-theme-ios27/tree/ios27-v1.2.0-1/ios/Sources/IonicNativeUIShellPlugin/Components)はUIKit部品の生成・更新・名前を管理します。`ShellButton` が通常・戻る・メニューボタンの実装を共有し、`Shared` がhost view、型付きsnapshot、形状、色、画像cacheを管理します。Capacitorは完全なsnapshotを `Decodable` で一度decodeし、描画側は型付きmodelと `Equatable` で内容を比較します。不正batchは表示変更前に拒否します。`IonicNativeUIShellPlugin.swift` がCapacitor呼び出し、revision、ネイティブviewの寿命を調整します。 ## デモと検証 diff --git a/projects/docs/src/ionic-theme-ios27/docs/ja/readme.md b/projects/docs/src/ionic-theme-ios27/docs/ja/readme.md index d935be8..ef7a389 100644 --- a/projects/docs/src/ionic-theme-ios27/docs/ja/readme.md +++ b/projects/docs/src/ionic-theme-ios27/docs/ja/readme.md @@ -6,14 +6,14 @@ scrollActiveLine: [] IonicアプリにiOS 27のLiquid Glassとアニメーションを適用するテーマです。Capacitor iOSアプリでは、対応する操作部品に実験的なNative UI Shellを選択できます。 -**[Ionic 9デモ](https://ionic-theme-ios27.rdlabo.dev/) · [Ionic 8デモ](https://ionic8-theme-ios27.rdlabo.dev/) · [1.2.0-0リリースノート](https://github.com/rdlabo-dev/ionic-theme-ios27/releases/tag/ios27-v1.2.0-0)** +**[Ionic 9デモ](https://ionic-theme-ios27.rdlabo.dev/) · [Ionic 8デモ](https://ionic8-theme-ios27.rdlabo.dev/) · [1.2.0-0リリースノート](https://github.com/rdlabo-dev/ionic-theme-ios27/releases/tag/ios27-v1.2.0-1)**

- iOS 27テーマ: Liquid Glass検索バーを備えたライトモードの設定画面 - iOS 27テーマ: ダークモードの設定画面 - iOS 27テーマ: Liquid Glassボタンとタブバーを備えたライブラリ画面 + iOS 27テーマ: Liquid Glass検索バーを備えたライトモードの設定画面 + iOS 27テーマ: ダークモードの設定画面 + iOS 27テーマ: Liquid Glassボタンとタブバーを備えたライブラリ画面

@@ -30,7 +30,7 @@ Liquid Glass、ツールバー、タブ、リスト、ボタン、検索、オ **iOS 27のタブドラッグ:** 同じLibrary画面をNative UI Shellオフ(Web)とオン(UIKit)で比較しました。下段はタブバー周辺を拡大しています。 -[![Native UI Shellのオン・オフで同じタブをドラッグした比較](https://raw.githubusercontent.com/rdlabo-dev/ionic-theme-ios27/ios27-v1.2.0-0/screenshots/native-ui-shell-drag/comparison.png)](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/ios27-v1.2.0-0/screenshots/native-ui-shell-drag/comparison.png) +[![Native UI Shellのオン・オフで同じタブをドラッグした比較](https://raw.githubusercontent.com/rdlabo-dev/ionic-theme-ios27/ios27-v1.2.0-1/screenshots/native-ui-shell-drag/comparison.png)](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/ios27-v1.2.0-1/screenshots/native-ui-shell-drag/comparison.png) ### 利用者の端末に合わせる diff --git a/projects/docs/src/ionic-theme-ios27/docs/ja/using-ion-item-group.md b/projects/docs/src/ionic-theme-ios27/docs/ja/using-ion-item-group.md index e488865..06b4d33 100644 --- a/projects/docs/src/ionic-theme-ios27/docs/ja/using-ion-item-group.md +++ b/projects/docs/src/ionic-theme-ios27/docs/ja/using-ion-item-group.md @@ -24,7 +24,7 @@ title: ion-item-groupの使用方法 Ionicは通常、`ion-list` 自体に背景を設定するため、`ion-list-header` もitemと同じsurface内に表示されます。iOS 27のlayoutではheaderとitemのsurfaceを分離します。 -![ion-item-groupが必要な理由を示すinset listの背景比較](https://raw.githubusercontent.com/rdlabo-dev/ionic-theme-ios27/ios27-v1.2.0-0/screenshots/why-ion-list-inset.png) +![ion-item-groupが必要な理由を示すinset listの背景比較](https://raw.githubusercontent.com/rdlabo-dev/ionic-theme-ios27/ios27-v1.2.0-1/screenshots/why-ion-list-inset.png) そのため、このテーマは次のようにstyleを適用します。 diff --git a/scripts/build-output.test.ts b/scripts/build-output.test.ts index 732f4dd..bbf8eeb 100644 --- a/scripts/build-output.test.ts +++ b/scripts/build-output.test.ts @@ -9,6 +9,35 @@ import { PAST_SPONSORS, } from '../projects/docs/src/app/generated/sponsors.generated'; +test('transition guides link to existing localized onboarding headings', async () => { + for (const locale of ['', 'ja/']) { + const route = `${locale}projects/ionic-theme-ios27/docs/`; + const target = new JSDOM( + await readFile(`dist/docs/browser/${route}iphone-duo-with-original-theme/index.html`, 'utf8'), + ).window.document; + for (const page of ['migration', 'native-ui-shell']) { + const document = new JSDOM( + await readFile(`dist/docs/browser/${route}${page}/index.html`, 'utf8'), + ).window.document; + const links = Array.from(document.querySelectorAll('a[href]')) + .map( + (link) => new URL(link.getAttribute('href')!, `https://docs.rdlabo.dev/${route}${page}`), + ) + .filter((url) => url.pathname.endsWith('/iphone-duo-with-original-theme') && url.hash); + assert.ok(links.length, `${locale}${page} should link to transition setup`); + for (const url of links) { + assert.equal(url.pathname, `/${route}iphone-duo-with-original-theme`); + const fragment = url.hash.slice(1); + // Japanese headings use URI-encoded IDs; English punctuation may be encoded in links. + assert.ok( + target.getElementById(fragment) ?? target.getElementById(decodeURIComponent(fragment)), + url.href, + ); + } + } + } +}); + test('places locale-specific static 404 pages in the browser output', async () => { const [english, japanese] = await Promise.all([ readFile(new URL('../dist/docs/browser/404.html', import.meta.url), 'utf8'), diff --git a/scripts/package-repository.test.ts b/scripts/package-repository.test.ts index 590112c..f8530ef 100644 --- a/scripts/package-repository.test.ts +++ b/scripts/package-repository.test.ts @@ -113,7 +113,7 @@ test('resolves README screenshot paths against the pinned package release', asyn '[![Comparison](./screenshots/native-ui-shell-drag/comparison.png)](./screenshots/native-ui-shell-drag/comparison.png)', ].join('\n'); const base = - 'https://raw.githubusercontent.com/rdlabo-dev/ionic-theme-ios27/ios27-v1.2.0-0/screenshots/'; + 'https://raw.githubusercontent.com/rdlabo-dev/ionic-theme-ios27/ios27-v1.2.0-1/screenshots/'; assert.equal( await pinPackageSourceLinks(project, markdown), @@ -170,12 +170,12 @@ test('supports prefixed release tags for renamed theme packages', async () => { repositoryUrl: 'https://github.com/rdlabo-dev/ionic-theme-ios27', releaseTagPrefix: 'ios27-v', }; - assert.equal(await resolveEnglishSourceRef(project), 'ios27-v1.2.0-0'); + assert.equal(await resolveEnglishSourceRef(project), 'ios27-v1.2.0-1'); assert.equal( await pinPackageSourceLinks( project, '[source](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/main/src/index.ts)', ), - '[source](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/ios27-v1.2.0-0/src/index.ts)', + '[source](https://github.com/rdlabo-dev/ionic-theme-ios27/blob/ios27-v1.2.0-1/src/index.ts)', ); }); diff --git a/scripts/project-manifest.ts b/scripts/project-manifest.ts index 03d1d57..f542968 100644 --- a/scripts/project-manifest.ts +++ b/scripts/project-manifest.ts @@ -741,9 +741,9 @@ export const projectDefinitions: readonly ProjectDefinition[] = [ packageName: '@rdlabo/ionic-theme-ios27', repositoryUrl: 'https://github.com/rdlabo-dev/ionic-theme-ios27', demoUrl: 'https://ionic-theme-ios27.rdlabo.dev/', - releaseNotesUrl: 'https://github.com/rdlabo-dev/ionic-theme-ios27/releases/tag/ios27-v1.2.0-0', + releaseNotesUrl: 'https://github.com/rdlabo-dev/ionic-theme-ios27/releases/tag/ios27-v1.2.0-1', releaseTagPrefix: 'ios27-v', - englishDocsRef: '7cb3cfc28208c32b46977235106f306e95e8725c', + englishDocsRef: '7e8a1d50337226b36798bd123497611e627d45ed', category: 'frontend-tools', icon: 'theme', adapter: 'markdown', diff --git a/scripts/site-contract.test.ts b/scripts/site-contract.test.ts index 2e03f80..699b45f 100644 --- a/scripts/site-contract.test.ts +++ b/scripts/site-contract.test.ts @@ -541,7 +541,7 @@ test('lists every ionic-angular-library package and imports localized READMEs', test('lists ionic theme packages and pins localized README imports', async () => { const expectedProjects = new Map([ ['ionic-theme-ios26', { packageName: '@rdlabo/ionic-theme-ios26', version: '9.2.0' }], - ['ionic-theme-ios27', { packageName: '@rdlabo/ionic-theme-ios27', version: '1.2.0-0' }], + ['ionic-theme-ios27', { packageName: '@rdlabo/ionic-theme-ios27', version: '1.2.0-1' }], ['ionic-theme-md3', { packageName: '@rdlabo/ionic-theme-md3', version: '9.1.0' }], ]); const packageJson = JSON.parse( @@ -1621,15 +1621,15 @@ test('declares authorized Ionic and Capacitor documentation translations', async test('separates iOS 26 and iOS 27 documentation, source branches, and screenshots', () => { for (const project of [ionicThemeIos27En, ionicThemeIos27Ja]) { - assert.equal(project.version, '1.2.0-0'); + assert.equal(project.version, '1.2.0-1'); assert.equal(project.demoUrl, 'https://ionic-theme-ios27.rdlabo.dev/'); assert.equal( project.releaseNotesUrl, - 'https://github.com/rdlabo-dev/ionic-theme-ios27/releases/tag/ios27-v1.2.0-0', + 'https://github.com/rdlabo-dev/ionic-theme-ios27/releases/tag/ios27-v1.2.0-1', ); assert.equal((project.overviewHtml.match(/Add a vertical navigation area to your Ionic app while keeping its existing theme. Tabs and supported toolbar actions move to the side of the screen; your content and horizontal controls keep their current appearance. Both Ionic ios and md modes are supported.

\n

Try it in Chrome first. You can preview the layout with Web controls before setting up an iPhone Duo or an iOS build. On supported Capacitor iOS, the same Ionic markup supplies native SwiftUI controls in the system rail.

\n

Available in 1.2.0-0 as an experimental feature. APIs and supported behavior may change.

\n

Try it in your existing Ionic app

\n

1. Install and load the standalone stylesheet

\n

This guide assumes an existing Ionic app with Ionic >=8.8.1 <10 and Capacitor Core >=8 <9. Keep your existing Capacitor 8 installation. If your app uses another Capacitor major, migrate its Core, CLI, and platform packages together before following this guide. For a Web-only app without Capacitor, also install @capacitor/core@^8; the JavaScript entry point needs it even in Chrome.

\n
npm install @rdlabo/ionic-theme-ios27@1.2.0-1\n

Keep your existing theme imports. Add this to your global Sass file:

\n
@use '@rdlabo/ionic-theme-ios27/dist/css/vertical-bars.css';\n

The standalone JavaScript entry point needs @capacitor/core even in Chrome. The iOS 27 theme stylesheets are not required.

\n

2. Opt your app into the side layout

\n

Add the class to your existing app root and keep the content inside it:

\n
<ion-app class=\"ios-theme-vertical-bars\">\n  <!-- Keep your existing pages, tabs, and toolbar controls here. -->\n</ion-app>\n

The preview reserves 80px on the physical right. To preview the left side, also add ios-theme-vertical-bars-left.

\n

3. Connect your navigation animation

\n

Configure navAnimation before Ionic initializes. Starting the rail runtime does not register this option. The adapter waits for native control retirement and coordinates swipe progress and cancellation while keeping your existing animation.

\n

Keep Ionic's default animation

\n

If you have not configured navAnimation, wrap Ionic's standard builders. Select the builder from Ionic's transition mode so both ios and md keep their usual animation:

\n
import { iosTransitionAnimation, mdTransitionAnimation, type AnimationBuilder } from '@ionic/core';\nimport { withNativeUIShellTransition } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\nconst defaultTransition: AnimationBuilder = (baseEl, opts) =>\n  (opts.mode === 'ios' ? iosTransitionAnimation : mdTransitionAnimation)(baseEl, opts);\n\nconst ionicConfig = {\n  navAnimation: withNativeUIShellTransition(defaultTransition),\n};\n

Merge this option into your existing Ionic configuration before initialization: pass it to Angular's provideIonicAngular(), React's setupIonicReact(), or Vue's IonicVue plugin options. Keep your existing theme stylesheet imports. No iOS 27 theme stylesheet is required.

\n

Keep your custom animation

\n

If your app already configures navAnimation, wrap that builder instead:

\n
import type { AnimationBuilder } from '@ionic/core';\nimport { withNativeUIShellTransition } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\n// Pass the animation builder your app already uses.\nconst configureNavigation = (existingTransition: AnimationBuilder) => ({\n  navAnimation: withNativeUIShellTransition(existingTransition),\n});\n

The adapter returns the original Animation, preserving its effects, duration, and easing. Use it only for navigation, not modal or popover animations. The builder must return a fresh Animation for each navigation; Ionic destroys it after the transition. Keep lifecycle events for control registration and transitions without animation.

\n

The adapter keeps the builder's animation targets, including any horizontal back-button effect. If you need the iOS 27 transition with that effect excluded in vertical layouts, use iosTransitionAnimation from @rdlabo/ionic-theme-ios27 as your navAnimation instead. It already includes the adapter, so no wrapper is needed.

\n

withNativeUIShellTransition() is available in 1.2.0-1 and later.

\n

4. Start the controls after the app root is mounted

\n

Call this once from your application startup after ion-app exists in the DOM:

\n
import { enableVerticalControlArea } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\nconst rail = await enableVerticalControlArea();\n

What you should see: your existing tab bar moves to the side, and supported icon-based fixed-toolbar actions appear there too. Content keeps its existing theme and leaves room for the controls. The Web tab rail displays icons; pressing and dragging reveals tab labels.

\n

Use your existing Ionic click handlers and routing. Text-only toolbar actions remain horizontal. Add .ios-theme-horizontal-only to an ion-buttons group or individual ion-button to keep an action in the horizontal toolbar.

\n

When the application owner is disposed, call await rail.destroy() to restore the original controls and release the runtime. If you already use enableNativeUIShell(), keep that runtime and follow the shared placement guide.

\n

If the preview does not appear

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
What you seeWhat to check
No space at the sideLoad vertical-bars.css and put the class on ion-app.
Space appears, but controls stay horizontalStart enableVerticalControlArea() after mounting the app root. Use existing tabs or supported icon-based actions in a fixed toolbar.
One action stays horizontalText-only actions, custom fills, and explicitly excluded controls keep their original presentation. See control requirements.
\n

Connect an iPhone Duo

\n

For Capacitor iOS, run npx cap sync ios. Build with Xcode 27.1 or newer and link against the iOS 27.1 SDK or later to receive the actual rail edge, safe-area inset, and hinge posture. The plugin uses Swift Package Manager; existing CocoaPods apps can follow Native UI Shell setup.

\n

Replace the browser-only startup above with this after ion-app is mounted:

\n
import { Capacitor, type PluginListenerHandle } from '@capacitor/core';\nimport { enableVerticalControlArea, IonicNativeUIShell } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\nconst rail = await enableVerticalControlArea();\nlet layoutListener: PluginListenerHandle | undefined;\n\nif (Capacitor.getPlatform() === 'ios') {\n  // The runtime already monitors device layout; only subscribe.\n  layoutListener = await IonicNativeUIShell.addListener('deviceLayoutChange', ({ placement }) =>\n    rail.setPlacement(placement),\n  );\n  rail.setPlacement((await IonicNativeUIShell.getDeviceLayout()).placement);\n}\n\n// Call when the application owner is disposed.\nconst stopVerticalArea = async () => {\n  await layoutListener?.remove();\n  await rail.destroy();\n};\n

setPlacement() applies the measured inset and resolves the logical edge through the document direction. A null edge restores the ordinary layout. Devices without a rail and apps built with older SDKs report null, so this example restores the ordinary layout there. To deliberately preview a DOM rail on such an iOS build, have your application choose a fixed edge with rail.setPlacement('trailing') instead of applying that null placement. This simulates the layout; it does not provide a real system rail or hinge measurements.

\n

On supported iOS, controls in the rail use the system SwiftUI appearance; your custom Web styling still applies to ordinary content and horizontal controls. Web and Android use Web clones.

\n

Use hinge posture without projecting controls

\n

If your existing theme needs only a posture-driven split pane or a layout switch, do not start a projection runtime or add .ios-theme-vertical-bars. Use getDeviceLayout() and deviceLayoutChange directly, pairing startDeviceLayoutMonitoring() with stopDeviceLayoutMonitoring() and removing the listener when finished.

\n

See Read the device layout for the subscription example, null values, and monitoring lifetime. See Adapt the split pane for the opt-in width rules and half-open state.

\n

Shared layout rules and API

\n

Safe-area handling, overlays, RTL, control eligibility, Web simulation, and the handle API are documented in iPhone Duo support. Those rules apply to this standalone setup too.

\n", + "html": "

Add a vertical navigation area to your Ionic app while keeping its existing theme. Tabs and supported toolbar actions move to the side of the screen; your content and horizontal controls keep their current appearance. Both Ionic ios and md modes are supported.

\n

Try it in Chrome first. You can preview the layout with Web controls before setting up an iPhone Duo or an iOS build. On supported Capacitor iOS, the same Ionic markup supplies native SwiftUI controls in the system rail.

\n

Available in 1.2.0-0 as an experimental feature. APIs and supported behavior may change.

\n

Try it in your existing Ionic app

\n

1. Install and load the standalone stylesheet

\n

This guide assumes an existing Ionic app with Ionic >=8.8.1 <10 and Capacitor Core >=8 <9. Keep your existing Capacitor 8 installation. If your app uses another Capacitor major, migrate its Core, CLI, and platform packages together before following this guide. For a Web-only app without Capacitor, also install @capacitor/core@^8; the JavaScript entry point needs it even in Chrome.

\n
npm install @rdlabo/ionic-theme-ios27@1.2.0-1\n

Keep your existing theme imports. Add this to your global Sass file:

\n
@use '@rdlabo/ionic-theme-ios27/dist/css/vertical-bars.css';\n

The standalone JavaScript entry point needs @capacitor/core even in Chrome. The iOS 27 theme stylesheets are not required.

\n

2. Opt your app into the side layout

\n

Add the class to your existing app root and keep the content inside it:

\n
<ion-app class=\"ios-theme-vertical-bars\">\n  <!-- Keep your existing pages, tabs, and toolbar controls here. -->\n</ion-app>\n

The preview reserves 80px on the physical right. To preview the left side, also add ios-theme-vertical-bars-left.

\n

3. Connect your navigation animation

\n

Configure navAnimation before Ionic initializes. Starting the rail runtime does not register this option. The adapter waits for native control retirement and coordinates swipe progress and cancellation while keeping your existing animation.

\n

Keep Ionic's default animation

\n

If you have not configured navAnimation, wrap Ionic's standard builders. Select the builder from Ionic's transition mode so both ios and md keep their usual animation:

\n
import { iosTransitionAnimation, mdTransitionAnimation, type AnimationBuilder } from '@ionic/core';\nimport { withNativeUIShellTransition } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\nconst defaultTransition: AnimationBuilder = (baseEl, opts) =>\n  (opts.mode === 'ios' ? iosTransitionAnimation : mdTransitionAnimation)(baseEl, opts);\n\nconst ionicConfig = {\n  navAnimation: withNativeUIShellTransition(defaultTransition),\n};\n

Merge this option into your existing Ionic configuration before initialization: pass it to Angular's provideIonicAngular(), React's setupIonicReact(), or Vue's IonicVue plugin options. Keep your existing theme stylesheet imports. No iOS 27 theme stylesheet is required.

\n

Use this package's iOS animation

\n

If you already use the iOS 27 transition, keep this configuration. It includes the native adapter and excludes the horizontal back-button effect in vertical layouts; no additional wrapper is needed. Importing this JavaScript entry point does not load the theme stylesheets.

\n
import { iosTransitionAnimation } from '@rdlabo/ionic-theme-ios27';\n\nconst ionicConfig = {\n  navAnimation: iosTransitionAnimation,\n};\n

Apply this option to your existing iOS-mode configuration and keep your MD configuration.

\n

Keep your custom animation

\n

If your app uses another builder for navAnimation, wrap it:

\n
import type { AnimationBuilder } from '@ionic/core';\nimport { withNativeUIShellTransition } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\n// Pass the animation builder your app already uses.\nconst configureNavigation = (existingTransition: AnimationBuilder) => ({\n  navAnimation: withNativeUIShellTransition(existingTransition),\n});\n

The adapter returns the original Animation, preserving its effects, duration, and easing. Use it only for navigation, not modal or popover animations. The builder must return a fresh Animation for each navigation; Ionic destroys it after the transition. Keep lifecycle events for control registration and transitions without animation.

\n

The adapter keeps the builder's animation targets, including any horizontal back-button effect. If you need the iOS 27 transition with that effect excluded in vertical layouts, use iosTransitionAnimation from @rdlabo/ionic-theme-ios27 as your navAnimation instead. It already includes the adapter, so no wrapper is needed.

\n

withNativeUIShellTransition() is available in 1.2.0-1 and later.

\n

4. Start the controls after the app root is mounted

\n

Call this once from your application startup after ion-app exists in the DOM:

\n
import { enableVerticalControlArea } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\nconst rail = await enableVerticalControlArea();\n

What you should see: your existing tab bar moves to the side, and supported icon-based fixed-toolbar actions appear there too. Content keeps its existing theme and leaves room for the controls. The Web tab rail displays icons; pressing and dragging reveals tab labels.

\n

Use your existing Ionic click handlers and routing. Text-only toolbar actions remain horizontal. Add .ios-theme-horizontal-only to an ion-buttons group or individual ion-button to keep an action in the horizontal toolbar.

\n

When the application owner is disposed, call await rail.destroy() to restore the original controls and release the runtime. If you already use enableNativeUIShell(), keep that runtime and follow the shared placement guide.

\n

If the preview does not appear

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
What you seeWhat to check
No space at the sideLoad vertical-bars.css and put the class on ion-app.
Space appears, but controls stay horizontalStart enableVerticalControlArea() after mounting the app root. Use existing tabs or supported icon-based actions in a fixed toolbar.
One action stays horizontalText-only actions, custom fills, and explicitly excluded controls keep their original presentation. See control requirements.
\n

Connect an iPhone Duo

\n

For Capacitor iOS, run npx cap sync ios. Build with Xcode 27.1 or newer and link against the iOS 27.1 SDK or later to receive the actual rail edge, safe-area inset, and hinge posture. The plugin uses Swift Package Manager; existing CocoaPods apps can follow Native UI Shell setup.

\n

Replace the browser-only startup above with this after ion-app is mounted:

\n
import { Capacitor, type PluginListenerHandle } from '@capacitor/core';\nimport { enableVerticalControlArea, IonicNativeUIShell } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\nconst rail = await enableVerticalControlArea();\nlet layoutListener: PluginListenerHandle | undefined;\n\nif (Capacitor.getPlatform() === 'ios') {\n  // The runtime already monitors device layout; only subscribe.\n  layoutListener = await IonicNativeUIShell.addListener('deviceLayoutChange', ({ placement }) =>\n    rail.setPlacement(placement),\n  );\n  rail.setPlacement((await IonicNativeUIShell.getDeviceLayout()).placement);\n}\n\n// Call when the application owner is disposed.\nconst stopVerticalArea = async () => {\n  await layoutListener?.remove();\n  await rail.destroy();\n};\n

setPlacement() applies the measured inset and resolves the logical edge through the document direction. A null edge restores the ordinary layout. Devices without a rail and apps built with older SDKs report null, so this example restores the ordinary layout there. To deliberately preview a DOM rail on such an iOS build, have your application choose a fixed edge with rail.setPlacement('trailing') instead of applying that null placement. This simulates the layout; it does not provide a real system rail or hinge measurements.

\n

On supported iOS, controls in the rail use the system SwiftUI appearance; your custom Web styling still applies to ordinary content and horizontal controls. Web and Android use Web clones.

\n

Use hinge posture without projecting controls

\n

If your existing theme needs only a posture-driven split pane or a layout switch, do not start a projection runtime or add .ios-theme-vertical-bars. Use getDeviceLayout() and deviceLayoutChange directly, pairing startDeviceLayoutMonitoring() with stopDeviceLayoutMonitoring() and removing the listener when finished.

\n

See Read the device layout for the subscription example, null values, and monitoring lifetime. See Adapt the split pane for the opt-in width rules and half-open state.

\n

Shared layout rules and API

\n

Safe-area handling, overlays, RTL, control eligibility, Web simulation, and the handle API are documented in iPhone Duo support. Those rules apply to this standalone setup too.

\n", "headings": [ { "id": "try-it-in-your-existing-ionic-app", @@ -467,6 +467,11 @@ export const PROJECT = { "text": "Keep Ionic's default animation", "level": 4 }, + { + "id": "use-this-package's-ios-animation", + "text": "Use this package's iOS animation", + "level": 4 + }, { "id": "keep-your-custom-animation", "text": "Keep your custom animation", @@ -585,7 +590,7 @@ export const PROJECT = { "file": "migration.md", "section": "Guides", "path": "/projects/ionic-theme-ios27/docs/migration", - "html": "

Native UI Shell transition adapter

\n

Use withNativeUIShellTransition() to keep your existing Ionic navigation animation while coordinating Native UI Shell controls.

\n
    \n
  • If you already use this package's iosTransitionAnimation, no configuration change is needed. It now uses the shared adapter internally; do not add another wrapper.
  • \n
  • If you use Ionic's default animation without a navAnimation option, follow Keep Ionic's default animation. The example selects Ionic's standard iOS or MD builder from the transition mode.
  • \n
  • If you use a custom navigation animation with Native UI Shell or the standalone Vertical Control Area, wrap your existing builder when configuring Ionic:
  • \n
\n
+ import { withNativeUIShellTransition } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\n  const ionicConfig = {\n-   navAnimation: existingTransition,\n+   navAnimation: withNativeUIShellTransition(existingTransition),\n  };\n

Merge this option into your existing Ionic configuration before initialization. The adapter preserves the animation's effects, duration, and easing while coordinating native retirement, swipe progress, and cancellation. Keep your existing theme stylesheet imports and Native UI Shell or Vertical Control Area startup.

\n

Use the adapter only for navigation; leave modal and popover animations unchanged. Your builder must create a fresh Animation for each navigation because Ionic destroys it afterward. Keep lifecycle events for control registration and transitions without animation. If the custom builder animates a horizontal back button separately, exclude that effect while .ios-theme-vertical-bars is active.

\n

See Connect your navigation animation for the setup and supported scope.

\n

From the iOS 26 theme

\n

For an app using @rdlabo/ionic-theme-ios26, the recommended migration keeps that package and adds @rdlabo/ionic-theme-ios27. The README setup selects iOS 27 or iOS 26 styles by browser capability and leaves Ionic's default iOS appearance on older browsers.

\n

1. Add the new package

\n

Keep the iOS 26 package and add iOS 27. The new theme requires @ionic/core 8.8.1 or later (Ionic 8 or 9).

\n
npm install @rdlabo/ionic-theme-ios27\n

2. Make the styles adaptive

\n

Replace unconditional iOS 26 imports in your global Sass stylesheet with two mutually exclusive branches. This example uses class-based dark mode:

\n
+ @use 'sass:meta';\n+\n- @use '@rdlabo/ionic-theme-ios26/src/styles/default-variables.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect.scss';\n+ @supports (overflow-anchor: auto) {\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/default-variables');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27-dark-class');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/md-remove-ios-class-effect');\n+ }\n+\n+ @supports (text-wrap: pretty) and (not (overflow-anchor: auto)) {\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/default-variables');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect');\n+ }\n

Keep Ionic's matching dark palette. For system or always-dark mode, replace both -dark-class imports with the matching variant. If you use md-ion-list-inset, load the corresponding package's stylesheet inside each branch. Browsers without either feature retain Ionic's default styling.

\n

3. Switch the animations

\n

Replace the iOS 26 animation import and gate the iOS 27 animations on the same browser features. Resolve the options before Ionic initializes:

\n
- import { iosTransitionAnimation, popoverEnterAnimation, popoverLeaveAnimation } from '@rdlabo/ionic-theme-ios26';\n+ import { iosTransitionAnimation, popoverEnterAnimation, popoverLeaveAnimation } from '@rdlabo/ionic-theme-ios27';\n+\n+ function loadIOSAnimations() {\n+  if (typeof CSS === 'undefined') return {};\n+  if (!CSS.supports('overflow-anchor: auto') && !CSS.supports('text-wrap: pretty')) return {};\n+\n+  return {\n+    navAnimation: iosTransitionAnimation,\n+    popoverEnter: popoverEnterAnimation,\n+    popoverLeave: popoverLeaveAnimation,\n+  };\n+ }\n\n  provideIonicAngular({\n-  navAnimation: isPlatform('ios') ? iosTransitionAnimation : undefined,\n-  popoverEnter: isPlatform('ios') ? popoverEnterAnimation : undefined,\n-  popoverLeave: isPlatform('ios') ? popoverLeaveAnimation : undefined,\n+  ...(isPlatform('ios') ? loadIOSAnimations() : {}),\n  });\n

The iOS 27 page transition and popover animations serve both styled generations. Older browsers keep Ionic's defaults. The example uses Angular; pass the same options to React's setupIonicReact or Vue's IonicVue.

\n

4. Update customizations

\n

Rename theme variables and opt-out classes used by your app. For example:

\n
  ion-content {\n-  --ios26-content-box-shadow-rgb: 0, 0, 0;\n+  --ios-theme-content-box-shadow-rgb: 0, 0, 0;\n  }\n\n- <ion-button class=\"ios26-disabled\">Standard Ionic button</ion-button>\n+ <ion-button class=\"ios-theme-disabled\">Standard Ionic button</ion-button>\n

The old names remain as deprecated fallbacks. Check the resulting screens in light and dark modes on the browsers you support.

\n

iOS 27 only

\n

To switch entirely to iOS 27, remove the iOS 26 package and replace its stylesheet and animation imports. The stylesheet changes are:

\n
- @use '@rdlabo/ionic-theme-ios26/src/styles/default-variables.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/default-variables.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27-dark-class.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/md-remove-ios-class-effect.scss';\n

Change the animation import from @rdlabo/ionic-theme-ios26 to @rdlabo/ionic-theme-ios27; the existing isPlatform('ios') configuration can stay. See the README's iOS 27-only setup. Unconditional imports apply the new styles on every browser using Ionic iOS mode.

\n

iOS 27 naming

\n

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.

\n

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.

\n

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.

\n

See Special markup and classes and Default variables for the current names.

\n

Earlier migration notes are preserved in the iOS 26 migration guide.

\n

Submit button appearance

\n

Submit buttons now use each Ionic color's standard contrast value and an iOS 27 directional edge treatment. Remove the theme-specific brightness variables.

\n
  :root {\n-  --ion-color-primary-brightness-rgb: 130, 255, 255;\n-  --ion-color-primary-brightness: #96feff;\n  }\n
", + "html": "

Native UI Shell transition adapter

\n

Use withNativeUIShellTransition() to keep your existing Ionic navigation animation while coordinating Native UI Shell controls.

\n
    \n
  • If you already import iosTransitionAnimation from @rdlabo/ionic-theme-ios27, keep the package animation setup; no configuration change is needed. It now uses the shared adapter internally; do not add another wrapper.
  • \n
  • If you use Ionic's default animation without a navAnimation option, follow Keep Ionic's default animation. The example selects Ionic's standard iOS or MD builder from the transition mode.
  • \n
  • If you use a custom navigation animation with Native UI Shell or the standalone Vertical Control Area, wrap your existing builder when configuring Ionic:
  • \n
\n
+ import { withNativeUIShellTransition } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\n  const ionicConfig = {\n-   navAnimation: existingTransition,\n+   navAnimation: withNativeUIShellTransition(existingTransition),\n  };\n

Merge this option into your existing Ionic configuration before initialization. The adapter preserves the animation's effects, duration, and easing while coordinating native retirement, swipe progress, and cancellation. Keep your existing theme stylesheet imports and Native UI Shell or Vertical Control Area startup.

\n

Use the adapter only for navigation; leave modal and popover animations unchanged. Your builder must create a fresh Animation for each navigation because Ionic destroys it afterward. Keep lifecycle events for control registration and transitions without animation. If the custom builder animates a horizontal back button separately, exclude that effect while .ios-theme-vertical-bars is active.

\n

See Connect your navigation animation for the setup and supported scope.

\n

From the iOS 26 theme

\n

For an app using @rdlabo/ionic-theme-ios26, the recommended migration keeps that package and adds @rdlabo/ionic-theme-ios27. The README setup selects iOS 27 or iOS 26 styles by browser capability and leaves Ionic's default iOS appearance on older browsers.

\n

1. Add the new package

\n

Keep the iOS 26 package and add iOS 27. The new theme requires @ionic/core 8.8.1 or later (Ionic 8 or 9).

\n
npm install @rdlabo/ionic-theme-ios27\n

2. Make the styles adaptive

\n

Replace unconditional iOS 26 imports in your global Sass stylesheet with two mutually exclusive branches. This example uses class-based dark mode:

\n
+ @use 'sass:meta';\n+\n- @use '@rdlabo/ionic-theme-ios26/src/styles/default-variables.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect.scss';\n+ @supports (overflow-anchor: auto) {\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/default-variables');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27-dark-class');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/md-remove-ios-class-effect');\n+ }\n+\n+ @supports (text-wrap: pretty) and (not (overflow-anchor: auto)) {\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/default-variables');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect');\n+ }\n

Keep Ionic's matching dark palette. For system or always-dark mode, replace both -dark-class imports with the matching variant. If you use md-ion-list-inset, load the corresponding package's stylesheet inside each branch. Browsers without either feature retain Ionic's default styling.

\n

3. Switch the animations

\n

Replace the iOS 26 animation import and gate the iOS 27 animations on the same browser features. Resolve the options before Ionic initializes:

\n
- import { iosTransitionAnimation, popoverEnterAnimation, popoverLeaveAnimation } from '@rdlabo/ionic-theme-ios26';\n+ import { iosTransitionAnimation, popoverEnterAnimation, popoverLeaveAnimation } from '@rdlabo/ionic-theme-ios27';\n+\n+ function loadIOSAnimations() {\n+  if (typeof CSS === 'undefined') return {};\n+  if (!CSS.supports('overflow-anchor: auto') && !CSS.supports('text-wrap: pretty')) return {};\n+\n+  return {\n+    navAnimation: iosTransitionAnimation,\n+    popoverEnter: popoverEnterAnimation,\n+    popoverLeave: popoverLeaveAnimation,\n+  };\n+ }\n\n  provideIonicAngular({\n-  navAnimation: isPlatform('ios') ? iosTransitionAnimation : undefined,\n-  popoverEnter: isPlatform('ios') ? popoverEnterAnimation : undefined,\n-  popoverLeave: isPlatform('ios') ? popoverLeaveAnimation : undefined,\n+  ...(isPlatform('ios') ? loadIOSAnimations() : {}),\n  });\n

The iOS 27 page transition and popover animations serve both styled generations. Older browsers keep Ionic's defaults. The example uses Angular; pass the same options to React's setupIonicReact or Vue's IonicVue.

\n

4. Update customizations

\n

Rename theme variables and opt-out classes used by your app. For example:

\n
  ion-content {\n-  --ios26-content-box-shadow-rgb: 0, 0, 0;\n+  --ios-theme-content-box-shadow-rgb: 0, 0, 0;\n  }\n\n- <ion-button class=\"ios26-disabled\">Standard Ionic button</ion-button>\n+ <ion-button class=\"ios-theme-disabled\">Standard Ionic button</ion-button>\n

The old names remain as deprecated fallbacks. Check the resulting screens in light and dark modes on the browsers you support.

\n

iOS 27 only

\n

To switch entirely to iOS 27, remove the iOS 26 package and replace its stylesheet and animation imports. The stylesheet changes are:

\n
- @use '@rdlabo/ionic-theme-ios26/src/styles/default-variables.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/default-variables.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27-dark-class.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/md-remove-ios-class-effect.scss';\n

Change the animation import from @rdlabo/ionic-theme-ios26 to @rdlabo/ionic-theme-ios27; the existing isPlatform('ios') configuration can stay. See the README's iOS 27-only setup. Unconditional imports apply the new styles on every browser using Ionic iOS mode.

\n

iOS 27 naming

\n

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.

\n

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.

\n

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.

\n

See Special markup and classes and Default variables for the current names.

\n

Earlier migration notes are preserved in the iOS 26 migration guide.

\n

Submit button appearance

\n

Submit buttons now use each Ionic color's standard contrast value and an iOS 27 directional edge treatment. Remove the theme-specific brightness variables.

\n
  :root {\n-  --ion-color-primary-brightness-rgb: 130, 255, 255;\n-  --ion-color-primary-brightness: #96feff;\n  }\n
", "headings": [ { "id": "native-ui-shell-transition-adapter", diff --git a/projects/docs/src/app/generated/projects/ionic-theme-ios27.ja.generated.ts b/projects/docs/src/app/generated/projects/ionic-theme-ios27.ja.generated.ts index a0e1a5e..50ab3d3 100644 --- a/projects/docs/src/app/generated/projects/ionic-theme-ios27.ja.generated.ts +++ b/projects/docs/src/app/generated/projects/ionic-theme-ios27.ja.generated.ts @@ -425,7 +425,7 @@ export const PROJECT = { "file": "iphone-duo-with-original-theme.md", "section": "ガイド", "path": "/projects/ionic-theme-ios27/docs/iphone-duo-with-original-theme", - "html": "

既存のテーマを維持したまま、Ionicアプリに縦のナビゲーション領域を追加します。タブと対応するtoolbarの操作部品は画面の側面へ移り、コンテンツと水平の操作部品は現在の外観を保ちます。Ionicの ios と md の両modeに対応します。

\n

まずChromeで試せます。 iPhone DuoやiOSビルドを準備する前に、Webの操作部品でレイアウトを確認できます。対応するCapacitor iOSでは、同じIonicマークアップからシステム操作領域にSwiftUIのネイティブ部品を表示します。

\n

1.2.0-0 で利用できる 実験的機能 です。APIと対応する動作は変更される可能性があります。

\n

既存のIonicアプリで試す

\n

1. インストールして専用スタイルシートを読み込む

\n

Ionic >=8.8.1 <10 とCapacitor Core >=8 <9 を使う既存アプリを前提とします。既存のCapacitor 8は再インストール不要です。別majorを使う場合は、Core・CLI・platformパッケージをまとめて移行してから進めてください。Capacitorを使わないWeb専用アプリでは @capacitor/core@^8 もインストールします。JavaScriptのentry pointはChromeでもこの依存を必要とします。

\n
npm install @rdlabo/ionic-theme-ios27@1.2.0-1\n

既存テーマのimportを維持し、グローバルSassファイルに次を追加します。

\n
@use '@rdlabo/ionic-theme-ios27/dist/css/vertical-bars.css';\n

独立したJavaScriptのentry pointはChromeでも @capacitor/core を必要とします。iOS 27テーマのスタイルシートは不要です。

\n

2. アプリを側面のレイアウトへ切り替える

\n

既存のapp rootにクラスを追加し、その中のコンテンツを維持します。

\n
<ion-app class=\"ios-theme-vertical-bars\">\n  <!-- 既存のページ、タブ、toolbarの操作部品をここに維持します。 -->\n</ion-app>\n

プレビューは物理的な右側に 80px を確保します。左側を試すには ios-theme-vertical-bars-left も追加します。

\n

3. 画面遷移アニメーションを接続する

\n

Ionicの初期化前に navAnimation を設定します。操作領域のruntimeを起動するだけでは、この設定は登録されません。アダプターは既存アニメーションを維持しながらネイティブ部品の退避を待ち、スワイプの進捗とキャンセルを連携します。

\n

Ionicの標準アニメーションを維持する

\n

navAnimation を設定していない場合は、Ionic標準のbuilderをラップします。Ionicが遷移時に渡す mode でbuilderを選び、ios と md のどちらも通常のアニメーションを維持します。

\n
import { iosTransitionAnimation, mdTransitionAnimation, type AnimationBuilder } from '@ionic/core';\nimport { withNativeUIShellTransition } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\nconst defaultTransition: AnimationBuilder = (baseEl, opts) =>\n  (opts.mode === 'ios' ? iosTransitionAnimation : mdTransitionAnimation)(baseEl, opts);\n\nconst ionicConfig = {\n  navAnimation: withNativeUIShellTransition(defaultTransition),\n};\n

初期化前に、既存のIonic設定へこのオプションを統合します。Angularでは provideIonicAngular()、Reactでは setupIonicReact()、Vueでは IonicVue pluginのオプションへ渡してください。既存テーマのスタイルシートのimportは維持します。iOS 27テーマのスタイルシートは不要です。

\n

独自アニメーションを維持する

\n

既に navAnimation を設定している場合は、そのbuilderをラップします。

\n
import type { AnimationBuilder } from '@ionic/core';\nimport { withNativeUIShellTransition } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\n// アプリで既に使っているanimation builderを渡します。\nconst configureNavigation = (existingTransition: AnimationBuilder) => ({\n  navAnimation: withNativeUIShellTransition(existingTransition),\n});\n

アダプターは元の Animation を返し、効果、duration、easingを維持します。画面遷移だけに使い、modalやpopoverのアニメーションには使いません。Ionicが遷移後に破棄するため、builderは遷移ごとに新しい Animation を返してください。部品登録とアニメーションなしの遷移には、引き続きlifecycle eventを使います。

\n

アダプターは、水平の戻るボタンへの効果を含むbuilderのアニメーション対象を維持します。縦レイアウトでその効果を除外するiOS 27の画面遷移が必要なら、@rdlabo/ionic-theme-ios27 の iosTransitionAnimation を navAnimation に使ってください。こちらにはアダプターが組み込まれているため、ラップは不要です。

\n

withNativeUIShellTransition() は 1.2.0-1 以降で利用できます。

\n

4. App rootのマウント後に操作部品を起動する

\n

ion-app がDOMに存在してから、アプリの起動処理で一度呼びます。

\n
import { enableVerticalControlArea } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\nconst rail = await enableVerticalControlArea();\n

確認できる結果: 既存のタブバーが側面へ移り、対応するアイコン付きの固定toolbarの操作部品も表示されます。コンテンツは既存テーマを維持し、操作部品の領域を空けます。Webのタブはアイコンを表示し、押したままドラッグするとラベルが現れます。

\n

既存のIonic click handlerとルーティングを使います。文字だけのtoolbar操作は水平に残ります。ion-buttons グループまたは個別の ion-button に .ios-theme-horizontal-only を追加すると、水平toolbarに残せます。

\n

アプリ内の利用元を破棄するときは await rail.destroy() を呼び、元の部品を復元してruntimeを解放します。既に enableNativeUIShell() を使う場合はそのruntimeを維持し、共通の配置ガイドに従ってください。

\n

プレビューが表示されない場合

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
表示の状態確認すること
側面に空きができないvertical-bars.css を読み込み、ion-app にクラスを追加します。
空きはできるが部品が水平に残るApp rootのマウント後に enableVerticalControlArea() を起動します。既存のタブや、固定toolbarの対応するアイコン付き操作を使います。
一部の操作だけ水平に残る文字だけの操作、独自のfill、明示的に除外した部品は元の表示を維持します。操作部品の対応条件を参照してください。
\n

iPhone Duoと接続する

\n

Capacitor iOSでは npx cap sync ios を実行します。実際の操作領域の端、safe areaのinset、ヒンジの状態を取得するには、Xcode 27.1以降でビルドし、iOS 27.1以降のSDKとリンクしてください。プラグインはSwift Package Managerを使います。既存のCocoaPodsアプリはNative UI Shellの導入を参照してください。

\n

ブラウザ用の起動コードを、ion-app のマウント後に実行する次のコードへ置き換えます。

\n
import { Capacitor, type PluginListenerHandle } from '@capacitor/core';\nimport { enableVerticalControlArea, IonicNativeUIShell } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\nconst rail = await enableVerticalControlArea();\nlet layoutListener: PluginListenerHandle | undefined;\n\nif (Capacitor.getPlatform() === 'ios') {\n  // Runtimeが既にレイアウトを監視しているため、購読だけを行います。\n  layoutListener = await IonicNativeUIShell.addListener('deviceLayoutChange', ({ placement }) =>\n    rail.setPlacement(placement),\n  );\n  rail.setPlacement((await IonicNativeUIShell.getDeviceLayout()).placement);\n}\n\n// アプリ内の利用元を破棄するときに呼びます。\nconst stopVerticalArea = async () => {\n  await layoutListener?.remove();\n  await rail.destroy();\n};\n

setPlacement() は実測insetを適用し、論理方向をdocumentの文字方向で解決します。null の端は通常のレイアウトを復元します。操作領域のない端末や古いSDKでビルドしたアプリは null を返すため、この例では通常のレイアウトへ戻ります。そのようなiOSビルドで意図的にDOMの操作領域を試す場合は、nullの配置を適用する代わりにアプリが rail.setPlacement('trailing') で固定の端を選んでください。レイアウトのシミュレーションであり、実際のシステム操作領域やヒンジの計測値は得られません。

\n

対応するiOSでは操作領域の部品はシステムのSwiftUIの外観を使い、通常のコンテンツと水平の操作部品は独自のWebスタイルを維持します。WebとAndroidはWebクローンを使います。

\n

操作部品を描画せずヒンジの状態だけを使う

\n

開閉状態に応じたsplit paneやレイアウト切り替えだけが必要なら、描画runtimeを起動せず、.ios-theme-vertical-bars も追加しません。getDeviceLayout() と deviceLayoutChange を直接使い、startDeviceLayoutMonitoring() と stopDeviceLayoutMonitoring() を対応させ、終了時にlistenerを削除します。

\n

購読例、null値、監視の寿命はデバイスのレイアウトを取得する、opt-inの幅指定と半開き状態はSplit paneを開閉状態に合わせるを参照してください。

\n

共通のレイアウト規則とAPI

\n

safe area、overlay、RTL、操作部品の対応条件、Webでのシミュレーション、handleのAPIはiPhone Duo対応に記載しています。独立した構成にも同じ規則が適用されます。

\n", + "html": "

既存のテーマを維持したまま、Ionicアプリに縦のナビゲーション領域を追加します。タブと対応するtoolbarの操作部品は画面の側面へ移り、コンテンツと水平の操作部品は現在の外観を保ちます。Ionicの ios と md の両modeに対応します。

\n

まずChromeで試せます。 iPhone DuoやiOSビルドを準備する前に、Webの操作部品でレイアウトを確認できます。対応するCapacitor iOSでは、同じIonicマークアップからシステム操作領域にSwiftUIのネイティブ部品を表示します。

\n

1.2.0-0 で利用できる 実験的機能 です。APIと対応する動作は変更される可能性があります。

\n

既存のIonicアプリで試す

\n

1. インストールして専用スタイルシートを読み込む

\n

Ionic >=8.8.1 <10 とCapacitor Core >=8 <9 を使う既存アプリを前提とします。既存のCapacitor 8は再インストール不要です。別majorを使う場合は、Core・CLI・platformパッケージをまとめて移行してから進めてください。Capacitorを使わないWeb専用アプリでは @capacitor/core@^8 もインストールします。JavaScriptのentry pointはChromeでもこの依存を必要とします。

\n
npm install @rdlabo/ionic-theme-ios27@1.2.0-1\n

既存テーマのimportを維持し、グローバルSassファイルに次を追加します。

\n
@use '@rdlabo/ionic-theme-ios27/dist/css/vertical-bars.css';\n

独立したJavaScriptのentry pointはChromeでも @capacitor/core を必要とします。iOS 27テーマのスタイルシートは不要です。

\n

2. アプリを側面のレイアウトへ切り替える

\n

既存のapp rootにクラスを追加し、その中のコンテンツを維持します。

\n
<ion-app class=\"ios-theme-vertical-bars\">\n  <!-- 既存のページ、タブ、toolbarの操作部品をここに維持します。 -->\n</ion-app>\n

プレビューは物理的な右側に 80px を確保します。左側を試すには ios-theme-vertical-bars-left も追加します。

\n

3. 画面遷移アニメーションを接続する

\n

Ionicの初期化前に navAnimation を設定します。操作領域のruntimeを起動するだけでは、この設定は登録されません。アダプターは既存アニメーションを維持しながらネイティブ部品の退避を待ち、スワイプの進捗とキャンセルを連携します。

\n

Ionicの標準アニメーションを維持する

\n

navAnimation を設定していない場合は、Ionic標準のbuilderをラップします。Ionicが遷移時に渡す mode でbuilderを選び、ios と md のどちらも通常のアニメーションを維持します。

\n
import { iosTransitionAnimation, mdTransitionAnimation, type AnimationBuilder } from '@ionic/core';\nimport { withNativeUIShellTransition } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\nconst defaultTransition: AnimationBuilder = (baseEl, opts) =>\n  (opts.mode === 'ios' ? iosTransitionAnimation : mdTransitionAnimation)(baseEl, opts);\n\nconst ionicConfig = {\n  navAnimation: withNativeUIShellTransition(defaultTransition),\n};\n

初期化前に、既存のIonic設定へこのオプションを統合します。Angularでは provideIonicAngular()、Reactでは setupIonicReact()、Vueでは IonicVue pluginのオプションへ渡してください。既存テーマのスタイルシートのimportは維持します。iOS 27テーマのスタイルシートは不要です。

\n

本パッケージのiOSアニメーションを使う

\n

既にiOS 27の画面遷移を使っている場合は、この設定を維持します。ネイティブ連携のアダプターを含み、縦レイアウトでは水平の戻るボタンの効果を対象から外すため、追加のラップは不要です。このJavaScriptのentry pointをimportしても、テーマのスタイルシートは読み込まれません。

\n
import { iosTransitionAnimation } from '@rdlabo/ionic-theme-ios27';\n\nconst ionicConfig = {\n  navAnimation: iosTransitionAnimation,\n};\n

既存のiOS mode用設定へこのオプションを適用し、MD用設定は維持してください。

\n

独自アニメーションを維持する

\n

navAnimation に他のbuilderを使っている場合は、それをラップします。

\n
import type { AnimationBuilder } from '@ionic/core';\nimport { withNativeUIShellTransition } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\n// アプリで既に使っているanimation builderを渡します。\nconst configureNavigation = (existingTransition: AnimationBuilder) => ({\n  navAnimation: withNativeUIShellTransition(existingTransition),\n});\n

アダプターは元の Animation を返し、効果、duration、easingを維持します。画面遷移だけに使い、modalやpopoverのアニメーションには使いません。Ionicが遷移後に破棄するため、builderは遷移ごとに新しい Animation を返してください。部品登録とアニメーションなしの遷移には、引き続きlifecycle eventを使います。

\n

アダプターは、水平の戻るボタンへの効果を含むbuilderのアニメーション対象を維持します。縦レイアウトでその効果を除外するiOS 27の画面遷移が必要なら、@rdlabo/ionic-theme-ios27 の iosTransitionAnimation を navAnimation に使ってください。こちらにはアダプターが組み込まれているため、ラップは不要です。

\n

withNativeUIShellTransition() は 1.2.0-1 以降で利用できます。

\n

4. App rootのマウント後に操作部品を起動する

\n

ion-app がDOMに存在してから、アプリの起動処理で一度呼びます。

\n
import { enableVerticalControlArea } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\nconst rail = await enableVerticalControlArea();\n

確認できる結果: 既存のタブバーが側面へ移り、対応するアイコン付きの固定toolbarの操作部品も表示されます。コンテンツは既存テーマを維持し、操作部品の領域を空けます。Webのタブはアイコンを表示し、押したままドラッグするとラベルが現れます。

\n

既存のIonic click handlerとルーティングを使います。文字だけのtoolbar操作は水平に残ります。ion-buttons グループまたは個別の ion-button に .ios-theme-horizontal-only を追加すると、水平toolbarに残せます。

\n

アプリ内の利用元を破棄するときは await rail.destroy() を呼び、元の部品を復元してruntimeを解放します。既に enableNativeUIShell() を使う場合はそのruntimeを維持し、共通の配置ガイドに従ってください。

\n

プレビューが表示されない場合

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
表示の状態確認すること
側面に空きができないvertical-bars.css を読み込み、ion-app にクラスを追加します。
空きはできるが部品が水平に残るApp rootのマウント後に enableVerticalControlArea() を起動します。既存のタブや、固定toolbarの対応するアイコン付き操作を使います。
一部の操作だけ水平に残る文字だけの操作、独自のfill、明示的に除外した部品は元の表示を維持します。操作部品の対応条件を参照してください。
\n

iPhone Duoと接続する

\n

Capacitor iOSでは npx cap sync ios を実行します。実際の操作領域の端、safe areaのinset、ヒンジの状態を取得するには、Xcode 27.1以降でビルドし、iOS 27.1以降のSDKとリンクしてください。プラグインはSwift Package Managerを使います。既存のCocoaPodsアプリはNative UI Shellの導入を参照してください。

\n

ブラウザ用の起動コードを、ion-app のマウント後に実行する次のコードへ置き換えます。

\n
import { Capacitor, type PluginListenerHandle } from '@capacitor/core';\nimport { enableVerticalControlArea, IonicNativeUIShell } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\nconst rail = await enableVerticalControlArea();\nlet layoutListener: PluginListenerHandle | undefined;\n\nif (Capacitor.getPlatform() === 'ios') {\n  // Runtimeが既にレイアウトを監視しているため、購読だけを行います。\n  layoutListener = await IonicNativeUIShell.addListener('deviceLayoutChange', ({ placement }) =>\n    rail.setPlacement(placement),\n  );\n  rail.setPlacement((await IonicNativeUIShell.getDeviceLayout()).placement);\n}\n\n// アプリ内の利用元を破棄するときに呼びます。\nconst stopVerticalArea = async () => {\n  await layoutListener?.remove();\n  await rail.destroy();\n};\n

setPlacement() は実測insetを適用し、論理方向をdocumentの文字方向で解決します。null の端は通常のレイアウトを復元します。操作領域のない端末や古いSDKでビルドしたアプリは null を返すため、この例では通常のレイアウトへ戻ります。そのようなiOSビルドで意図的にDOMの操作領域を試す場合は、nullの配置を適用する代わりにアプリが rail.setPlacement('trailing') で固定の端を選んでください。レイアウトのシミュレーションであり、実際のシステム操作領域やヒンジの計測値は得られません。

\n

対応するiOSでは操作領域の部品はシステムのSwiftUIの外観を使い、通常のコンテンツと水平の操作部品は独自のWebスタイルを維持します。WebとAndroidはWebクローンを使います。

\n

操作部品を描画せずヒンジの状態だけを使う

\n

開閉状態に応じたsplit paneやレイアウト切り替えだけが必要なら、描画runtimeを起動せず、.ios-theme-vertical-bars も追加しません。getDeviceLayout() と deviceLayoutChange を直接使い、startDeviceLayoutMonitoring() と stopDeviceLayoutMonitoring() を対応させ、終了時にlistenerを削除します。

\n

購読例、null値、監視の寿命はデバイスのレイアウトを取得する、opt-inの幅指定と半開き状態はSplit paneを開閉状態に合わせるを参照してください。

\n

共通のレイアウト規則とAPI

\n

safe area、overlay、RTL、操作部品の対応条件、Webでのシミュレーション、handleのAPIはiPhone Duo対応に記載しています。独立した構成にも同じ規則が適用されます。

\n", "headings": [ { "id": "%E6%97%A2%E5%AD%98%E3%81%AEionic%E3%82%A2%E3%83%97%E3%83%AA%E3%81%A7%E8%A9%A6%E3%81%99", @@ -452,6 +452,11 @@ export const PROJECT = { "text": "Ionicの標準アニメーションを維持する", "level": 4 }, + { + "id": "%E6%9C%AC%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%81%AEios%E3%82%A2%E3%83%8B%E3%83%A1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E4%BD%BF%E3%81%86", + "text": "本パッケージのiOSアニメーションを使う", + "level": 4 + }, { "id": "%E7%8B%AC%E8%87%AA%E3%82%A2%E3%83%8B%E3%83%A1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E7%B6%AD%E6%8C%81%E3%81%99%E3%82%8B", "text": "独自アニメーションを維持する", @@ -570,7 +575,7 @@ export const PROJECT = { "file": "migration.md", "section": "ガイド", "path": "/projects/ionic-theme-ios27/docs/migration", - "html": "

Native UI Shellの画面遷移アダプター

\n

withNativeUIShellTransition() を使うと、既存のIonic画面遷移アニメーションを維持しながらNative UI Shellの部品を連携できます。

\n
    \n
  • 本パッケージの iosTransitionAnimation を使っている場合、設定変更は不要です。内部で共通アダプターを使うため、追加のラップは不要です。
  • \n
  • navAnimation を設定せずIonic標準のアニメーションを使っている場合は、Ionicの標準アニメーションを維持するを参照してください。遷移modeに応じてIonic標準のiOSまたはMD builderを選ぶ例を掲載しています。
  • \n
  • Native UI Shellまたは独立したVertical Control Areaで独自の画面遷移を使う場合、Ionic設定時に既存のbuilderをラップします。
  • \n
\n
+ import { withNativeUIShellTransition } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\n  const ionicConfig = {\n-   navAnimation: existingTransition,\n+   navAnimation: withNativeUIShellTransition(existingTransition),\n  };\n

Ionicの初期化前に、既存設定へこのオプションを統合します。アダプターはアニメーションの効果、duration、easingを維持しながら、ネイティブ部品の退避、スワイプの進捗、キャンセルを連携します。既存テーマのスタイルシートのimportと、Native UI ShellまたはVertical Control Areaの起動処理は維持します。

\n

アダプターは画面遷移だけに使い、modalやpopoverのアニメーションは維持します。Ionicが遷移後に破棄するため、builderは遷移ごとに新しい Animation を作成してください。部品登録とアニメーションなしの遷移には、引き続きlifecycle eventを使います。独自builderが水平の戻るボタンを別途アニメーションする場合、.ios-theme-vertical-bars が有効な間はその効果を対象から外してください。

\n

導入手順と対応範囲は画面遷移アニメーションを接続するを参照してください。

\n

iOS 26テーマからの移行

\n

既存アプリが @rdlabo/ionic-theme-ios26 を使っている場合は、iOS 26を残して @rdlabo/ionic-theme-ios27 を追加する方法を推奨します。READMEの導入手順では、ブラウザの機能に応じてiOS 26/27のスタイルを切り替え、どちらにも対応しないブラウザではIonic標準のiOS外観を維持します。

\n

1. 新しいパッケージを追加する

\n

iOS 26パッケージを残したままiOS 27を追加します。新テーマには @ionic/core 8.8.1以降(Ionic 8/9)が必要です。

\n
npm install @rdlabo/ionic-theme-ios27\n

2. スタイルを切り替える

\n

グローバルSassで無条件に読み込んでいたiOS 26のスタイルを、排他的な2つの条件に置き換えます。classベースのダークモードの例です。

\n
+ @use 'sass:meta';\n+\n- @use '@rdlabo/ionic-theme-ios26/src/styles/default-variables.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect.scss';\n+ @supports (overflow-anchor: auto) {\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/default-variables');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27-dark-class');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/md-remove-ios-class-effect');\n+ }\n+\n+ @supports (text-wrap: pretty) and (not (overflow-anchor: auto)) {\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/default-variables');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect');\n+ }\n

Ionic側の対応するダークパレットも維持してください。system/alwaysの場合は両方の -dark-class を対応するvariantに変えます。md-ion-list-inset を使う場合は各条件内で対応するパッケージから読み込みます。どちらの条件も満たさないブラウザはIonic標準のスタイルを維持します。

\n

3. アニメーションを切り替える

\n

iOS 26からのimportをiOS 27に変更し、スタイルと同じブラウザ機能で有効化します。Ionicの初期化前に設定してください。

\n
- import { iosTransitionAnimation, popoverEnterAnimation, popoverLeaveAnimation } from '@rdlabo/ionic-theme-ios26';\n+ import { iosTransitionAnimation, popoverEnterAnimation, popoverLeaveAnimation } from '@rdlabo/ionic-theme-ios27';\n+\n+ function loadIOSAnimations() {\n+  if (typeof CSS === 'undefined') return {};\n+  if (!CSS.supports('overflow-anchor: auto') && !CSS.supports('text-wrap: pretty')) return {};\n+\n+  return {\n+    navAnimation: iosTransitionAnimation,\n+    popoverEnter: popoverEnterAnimation,\n+    popoverLeave: popoverLeaveAnimation,\n+  };\n+ }\n\n  provideIonicAngular({\n-  navAnimation: isPlatform('ios') ? iosTransitionAnimation : undefined,\n-  popoverEnter: isPlatform('ios') ? popoverEnterAnimation : undefined,\n-  popoverLeave: isPlatform('ios') ? popoverLeaveAnimation : undefined,\n+  ...(isPlatform('ios') ? loadIOSAnimations() : {}),\n  });\n

両方の世代でiOS 27の画面遷移とpopoverアニメーションを使います。古いブラウザはIonic標準のアニメーションを維持します。例はAngularです。Reactでは setupIonicReact、Vueでは IonicVue に同じオプションを渡します。

\n

4. カスタマイズ名を更新する

\n

アプリで使っているテーマ変数と除外クラスを更新します。

\n
  ion-content {\n-  --ios26-content-box-shadow-rgb: 0, 0, 0;\n+  --ios-theme-content-box-shadow-rgb: 0, 0, 0;\n  }\n\n- <ion-button class=\"ios26-disabled\">Standard Ionic button</ion-button>\n+ <ion-button class=\"ios-theme-disabled\">Standard Ionic button</ion-button>\n

旧名はdeprecatedのフォールバックとして残ります。対応ブラウザでライト・ダーク両モードの画面を確認してください。

\n

iOS 27だけに切り替える場合

\n

iOS 26パッケージを削除し、スタイルシートとアニメーションのimportをiOS 27に変更します。スタイルの差分は次のとおりです。

\n
- @use '@rdlabo/ionic-theme-ios26/src/styles/default-variables.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/default-variables.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27-dark-class.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/md-remove-ios-class-effect.scss';\n

アニメーションのimport元も @rdlabo/ionic-theme-ios26 から @rdlabo/ionic-theme-ios27 に変更します。既存の isPlatform('ios') 設定はそのまま使えます。iOS 27単体の導入手順も参照してください。無条件のimportではIonic iOSモードを使う全ブラウザに新しいスタイルが適用されます。

\n

iOS 27の命名

\n

iOS 27の条件内、またはiOS 27だけを使うアプリでは @rdlabo/ionic-theme-ios27 を読み込みます。スタイルシート名は ionic-theme-ios27.scss または ionic-theme-ios27.css です。dark-always、dark-system、dark-class のvariantも同様です。併用構成のiOS 26側では旧パッケージのスタイルシート名を維持します。

\n

CSS変数にはバージョンに依存しない --ios-theme-* を使います。--ios26-* はdeprecatedのフォールバックとして使え、両方ある場合は新名が優先されます。例えば --ios26-content-box-shadow-rgb を --ios-theme-content-box-shadow-rgb に変更します。

\n

テーマを除外するときは ios-theme-disabled を使います。ios26-disabled はdeprecatedの互換名として残ります。

\n

現在の名称は特別なマークアップとクラスとデフォルト変数を参照してください。以前の移行案内はiOS 26の移行ガイドにあります。

\n

送信ボタンの外観

\n

送信ボタンはIonicの各色の標準コントラスト値と、iOS 27の方向性を持つ縁取りを使います。テーマ専用のbrightness変数を削除してください。

\n
  :root {\n-  --ion-color-primary-brightness-rgb: 130, 255, 255;\n-  --ion-color-primary-brightness: #96feff;\n  }\n
", + "html": "

Native UI Shellの画面遷移アダプター

\n

withNativeUIShellTransition() を使うと、既存のIonic画面遷移アニメーションを維持しながらNative UI Shellの部品を連携できます。

\n
    \n
  • @rdlabo/ionic-theme-ios27 から iosTransitionAnimation をimportしている場合は、本パッケージのアニメーション設定を維持します。設定変更は不要です。内部で共通アダプターを使うため、追加のラップは不要です。
  • \n
  • navAnimation を設定せずIonic標準のアニメーションを使っている場合は、Ionicの標準アニメーションを維持するを参照してください。遷移modeに応じてIonic標準のiOSまたはMD builderを選ぶ例を掲載しています。
  • \n
  • Native UI Shellまたは独立したVertical Control Areaで独自の画面遷移を使う場合、Ionic設定時に既存のbuilderをラップします。
  • \n
\n
+ import { withNativeUIShellTransition } from '@rdlabo/ionic-theme-ios27/vertical-bars';\n\n  const ionicConfig = {\n-   navAnimation: existingTransition,\n+   navAnimation: withNativeUIShellTransition(existingTransition),\n  };\n

Ionicの初期化前に、既存設定へこのオプションを統合します。アダプターはアニメーションの効果、duration、easingを維持しながら、ネイティブ部品の退避、スワイプの進捗、キャンセルを連携します。既存テーマのスタイルシートのimportと、Native UI ShellまたはVertical Control Areaの起動処理は維持します。

\n

アダプターは画面遷移だけに使い、modalやpopoverのアニメーションは維持します。Ionicが遷移後に破棄するため、builderは遷移ごとに新しい Animation を作成してください。部品登録とアニメーションなしの遷移には、引き続きlifecycle eventを使います。独自builderが水平の戻るボタンを別途アニメーションする場合、.ios-theme-vertical-bars が有効な間はその効果を対象から外してください。

\n

導入手順と対応範囲は画面遷移アニメーションを接続するを参照してください。

\n

iOS 26テーマからの移行

\n

既存アプリが @rdlabo/ionic-theme-ios26 を使っている場合は、iOS 26を残して @rdlabo/ionic-theme-ios27 を追加する方法を推奨します。READMEの導入手順では、ブラウザの機能に応じてiOS 26/27のスタイルを切り替え、どちらにも対応しないブラウザではIonic標準のiOS外観を維持します。

\n

1. 新しいパッケージを追加する

\n

iOS 26パッケージを残したままiOS 27を追加します。新テーマには @ionic/core 8.8.1以降(Ionic 8/9)が必要です。

\n
npm install @rdlabo/ionic-theme-ios27\n

2. スタイルを切り替える

\n

グローバルSassで無条件に読み込んでいたiOS 26のスタイルを、排他的な2つの条件に置き換えます。classベースのダークモードの例です。

\n
+ @use 'sass:meta';\n+\n- @use '@rdlabo/ionic-theme-ios26/src/styles/default-variables.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect.scss';\n+ @supports (overflow-anchor: auto) {\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/default-variables');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27-dark-class');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/md-remove-ios-class-effect');\n+ }\n+\n+ @supports (text-wrap: pretty) and (not (overflow-anchor: auto)) {\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/default-variables');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class');\n+  @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect');\n+ }\n

Ionic側の対応するダークパレットも維持してください。system/alwaysの場合は両方の -dark-class を対応するvariantに変えます。md-ion-list-inset を使う場合は各条件内で対応するパッケージから読み込みます。どちらの条件も満たさないブラウザはIonic標準のスタイルを維持します。

\n

3. アニメーションを切り替える

\n

iOS 26からのimportをiOS 27に変更し、スタイルと同じブラウザ機能で有効化します。Ionicの初期化前に設定してください。

\n
- import { iosTransitionAnimation, popoverEnterAnimation, popoverLeaveAnimation } from '@rdlabo/ionic-theme-ios26';\n+ import { iosTransitionAnimation, popoverEnterAnimation, popoverLeaveAnimation } from '@rdlabo/ionic-theme-ios27';\n+\n+ function loadIOSAnimations() {\n+  if (typeof CSS === 'undefined') return {};\n+  if (!CSS.supports('overflow-anchor: auto') && !CSS.supports('text-wrap: pretty')) return {};\n+\n+  return {\n+    navAnimation: iosTransitionAnimation,\n+    popoverEnter: popoverEnterAnimation,\n+    popoverLeave: popoverLeaveAnimation,\n+  };\n+ }\n\n  provideIonicAngular({\n-  navAnimation: isPlatform('ios') ? iosTransitionAnimation : undefined,\n-  popoverEnter: isPlatform('ios') ? popoverEnterAnimation : undefined,\n-  popoverLeave: isPlatform('ios') ? popoverLeaveAnimation : undefined,\n+  ...(isPlatform('ios') ? loadIOSAnimations() : {}),\n  });\n

両方の世代でiOS 27の画面遷移とpopoverアニメーションを使います。古いブラウザはIonic標準のアニメーションを維持します。例はAngularです。Reactでは setupIonicReact、Vueでは IonicVue に同じオプションを渡します。

\n

4. カスタマイズ名を更新する

\n

アプリで使っているテーマ変数と除外クラスを更新します。

\n
  ion-content {\n-  --ios26-content-box-shadow-rgb: 0, 0, 0;\n+  --ios-theme-content-box-shadow-rgb: 0, 0, 0;\n  }\n\n- <ion-button class=\"ios26-disabled\">Standard Ionic button</ion-button>\n+ <ion-button class=\"ios-theme-disabled\">Standard Ionic button</ion-button>\n

旧名はdeprecatedのフォールバックとして残ります。対応ブラウザでライト・ダーク両モードの画面を確認してください。

\n

iOS 27だけに切り替える場合

\n

iOS 26パッケージを削除し、スタイルシートとアニメーションのimportをiOS 27に変更します。スタイルの差分は次のとおりです。

\n
- @use '@rdlabo/ionic-theme-ios26/src/styles/default-variables.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class.scss';\n- @use '@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/default-variables.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27-dark-class.scss';\n+ @use '@rdlabo/ionic-theme-ios27/src/styles/md-remove-ios-class-effect.scss';\n

アニメーションのimport元も @rdlabo/ionic-theme-ios26 から @rdlabo/ionic-theme-ios27 に変更します。既存の isPlatform('ios') 設定はそのまま使えます。iOS 27単体の導入手順も参照してください。無条件のimportではIonic iOSモードを使う全ブラウザに新しいスタイルが適用されます。

\n

iOS 27の命名

\n

iOS 27の条件内、またはiOS 27だけを使うアプリでは @rdlabo/ionic-theme-ios27 を読み込みます。スタイルシート名は ionic-theme-ios27.scss または ionic-theme-ios27.css です。dark-always、dark-system、dark-class のvariantも同様です。併用構成のiOS 26側では旧パッケージのスタイルシート名を維持します。

\n

CSS変数にはバージョンに依存しない --ios-theme-* を使います。--ios26-* はdeprecatedのフォールバックとして使え、両方ある場合は新名が優先されます。例えば --ios26-content-box-shadow-rgb を --ios-theme-content-box-shadow-rgb に変更します。

\n

テーマを除外するときは ios-theme-disabled を使います。ios26-disabled はdeprecatedの互換名として残ります。

\n

現在の名称は特別なマークアップとクラスとデフォルト変数を参照してください。以前の移行案内はiOS 26の移行ガイドにあります。

\n

送信ボタンの外観

\n

送信ボタンはIonicの各色の標準コントラスト値と、iOS 27の方向性を持つ縁取りを使います。テーマ専用のbrightness変数を削除してください。

\n
  :root {\n-  --ion-color-primary-brightness-rgb: 130, 255, 255;\n-  --ion-color-primary-brightness: #96feff;\n  }\n
", "headings": [ { "id": "native-ui-shell%E3%81%AE%E7%94%BB%E9%9D%A2%E9%81%B7%E7%A7%BB%E3%82%A2%E3%83%80%E3%83%97%E3%82%BF%E3%83%BC", diff --git a/projects/docs/src/ionic-theme-ios27/docs/ja/iphone-duo-with-original-theme.md b/projects/docs/src/ionic-theme-ios27/docs/ja/iphone-duo-with-original-theme.md index 7e81890..1c1ecee 100644 --- a/projects/docs/src/ionic-theme-ios27/docs/ja/iphone-duo-with-original-theme.md +++ b/projects/docs/src/ionic-theme-ios27/docs/ja/iphone-duo-with-original-theme.md @@ -60,9 +60,23 @@ const ionicConfig = { 初期化前に、既存のIonic設定へこのオプションを統合します。Angularでは `provideIonicAngular()`、Reactでは `setupIonicReact()`、Vueでは `IonicVue` pluginのオプションへ渡してください。既存テーマのスタイルシートのimportは維持します。iOS 27テーマのスタイルシートは不要です。 +#### 本パッケージのiOSアニメーションを使う + +既にiOS 27の画面遷移を使っている場合は、この設定を維持します。ネイティブ連携のアダプターを含み、縦レイアウトでは水平の戻るボタンの効果を対象から外すため、追加のラップは不要です。このJavaScriptのentry pointをimportしても、テーマのスタイルシートは読み込まれません。 + +```ts +import { iosTransitionAnimation } from '@rdlabo/ionic-theme-ios27'; + +const ionicConfig = { + navAnimation: iosTransitionAnimation, +}; +``` + +既存のiOS mode用設定へこのオプションを適用し、MD用設定は維持してください。 + #### 独自アニメーションを維持する -既に `navAnimation` を設定している場合は、そのbuilderをラップします。 +`navAnimation` に他のbuilderを使っている場合は、それをラップします。 ```ts import type { AnimationBuilder } from '@ionic/core'; diff --git a/projects/docs/src/ionic-theme-ios27/docs/ja/migration.md b/projects/docs/src/ionic-theme-ios27/docs/ja/migration.md index dee013d..af01e93 100644 --- a/projects/docs/src/ionic-theme-ios27/docs/ja/migration.md +++ b/projects/docs/src/ionic-theme-ios27/docs/ja/migration.md @@ -6,7 +6,7 @@ title: 移行 `withNativeUIShellTransition()` を使うと、既存のIonic画面遷移アニメーションを維持しながらNative UI Shellの部品を連携できます。 -- 本パッケージの `iosTransitionAnimation` を使っている場合、設定変更は不要です。内部で共通アダプターを使うため、追加のラップは不要です。 +- `@rdlabo/ionic-theme-ios27` から `iosTransitionAnimation` をimportしている場合は、[本パッケージのアニメーション設定](/docs/iphone-duo-with-original-theme#本パッケージのiosアニメーションを使う)を維持します。設定変更は不要です。内部で共通アダプターを使うため、追加のラップは不要です。 - `navAnimation` を設定せずIonic標準のアニメーションを使っている場合は、[Ionicの標準アニメーションを維持する](/docs/iphone-duo-with-original-theme#ionicの標準アニメーションを維持する)を参照してください。遷移modeに応じてIonic標準のiOSまたはMD builderを選ぶ例を掲載しています。 - Native UI Shellまたは独立したVertical Control Areaで独自の画面遷移を使う場合、Ionic設定時に既存のbuilderをラップします。 diff --git a/scripts/project-manifest.ts b/scripts/project-manifest.ts index f542968..4b59df0 100644 --- a/scripts/project-manifest.ts +++ b/scripts/project-manifest.ts @@ -743,7 +743,7 @@ export const projectDefinitions: readonly ProjectDefinition[] = [ demoUrl: 'https://ionic-theme-ios27.rdlabo.dev/', releaseNotesUrl: 'https://github.com/rdlabo-dev/ionic-theme-ios27/releases/tag/ios27-v1.2.0-1', releaseTagPrefix: 'ios27-v', - englishDocsRef: '7e8a1d50337226b36798bd123497611e627d45ed', + englishDocsRef: '4f043d55bd7b5df98683b74438200713b8c34795', category: 'frontend-tools', icon: 'theme', adapter: 'markdown',