-
Notifications
You must be signed in to change notification settings - Fork 3.2k
docs(updating): update v9 migration guide to add missing breaking changes #4620
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: major-9.0
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -340,10 +340,10 @@ For more information on migrating from React Router v5 to v6, refer to the [Reac | |||||
|
|
||||||
| ### Vue | ||||||
|
|
||||||
| 1. Ionic 9 supports Vue 3.5+ and Vue Router 5. Update Vue and Vue Router: | ||||||
| 1. Ionic 9 supports Vue 3.5+. Update to the latest version of Vue: | ||||||
|
|
||||||
| ```shell | ||||||
| npm install vue@latest vue-router@latest | ||||||
| npm install vue@latest | ||||||
| ``` | ||||||
|
|
||||||
| 2. Update to the latest version of Ionic 9: | ||||||
|
|
@@ -352,21 +352,18 @@ npm install vue@latest vue-router@latest | |||||
| npm install @ionic/vue@latest @ionic/vue-router@latest | ||||||
| ``` | ||||||
|
|
||||||
| #### Vue Router 5 Migration | ||||||
| ### Vue Router | ||||||
|
|
||||||
| `@ionic/vue-router` now requires Vue Router v5. Vue Router v4 is no longer supported. Vue Router v5 also raises its peer requirement on Vue itself, so the minimum supported Vue version moves to `3.5.0`. | ||||||
|
|
||||||
| Vue Router v5 is a transition release that ships no runtime breaking changes for Vue Router v4 consumers, so no application code changes are required for routes, navigation guards, or `IonRouterOutlet`. Bump the dep ranges in your app's `package.json`: | ||||||
| 1. Ionic 9 supports Vue Router 5. Update to the latest version of Vue Router: | ||||||
|
|
||||||
| ```diff | ||||||
| "dependencies": { | ||||||
| - "vue": "^3.4.0", | ||||||
| - "vue-router": "^4.0.0" | ||||||
| + "vue": "^3.5.0", | ||||||
| + "vue-router": "^5.0.0" | ||||||
| } | ||||||
| ```shell | ||||||
| npm install vue-router@latest | ||||||
| ``` | ||||||
|
|
||||||
| `@ionic/vue-router` now requires Vue Router v5. Vue Router v4 is no longer supported. Vue Router v5 also raises its peer requirement on Vue itself, so the minimum supported Vue version moves to `3.5.0`. | ||||||
|
|
||||||
| Vue Router v5 is a transition release that ships no runtime breaking changes for Vue Router v4 consumers, so no application code changes are required for routes, navigation guards, or `IonRouterOutlet`. | ||||||
|
|
||||||
| #### Deprecation Warning for `next()` in Navigation Guards | ||||||
|
|
||||||
| Vue Router v5 prints a deprecation warning when `next()` is called inside `beforeRouteLeave`, `beforeRouteEnter`, `beforeRouteUpdate`, or `router.beforeEach`. The callback form still works, but Vue Router v6 will remove it. Migrate to the return-value pattern: | ||||||
|
|
@@ -442,18 +439,6 @@ Ionic 9 officially supports Capacitor 7 and later. Native platform detection no | |||||
|
|
||||||
| If your app is still on Capacitor 2, it will no longer be detected as running on a native platform, so `isPlatform('capacitor')`, `isPlatform('hybrid')`, and `getPlatforms()` will report `web` instead of native. Upgrade to Capacitor 7 or later by following the [Capacitor updating guides](https://capacitorjs.com/docs/updating/7-0). | ||||||
|
|
||||||
| ### Legacy Picker | ||||||
|
|
||||||
| 1. Remove any usages of the `ion-picker-legacy` and `ion-picker-legacy-column` components. These components have been removed in Ionic 9. The recommended path forward is to use `ion-picker` inside a modal. Review the [Picker in Modal documentation](../api/picker.md#picker-in-modal) for more information. | ||||||
| 2. Remove any usages of `pickerController`. If using React, remove any usages of the `useIonPicker` hook. These controller-based APIs have been removed in Ionic 9. Use the [Picker](../api/picker.md) component instead. | ||||||
| 3. Remove any usages of the `PickerOptions`, `PickerButton`, `PickerColumn`, and `PickerColumnOption` type exports. These types were associated with the legacy picker and have been removed in Ionic 9. | ||||||
|
|
||||||
| ### Select | ||||||
|
|
||||||
| The `ionChange` event on `ion-select` now only fires when the value changes. Previously, the `alert` and `action-sheet` interfaces emitted `ionChange` whenever the overlay was confirmed, even if the user picked the option that was already selected. This matches the documented behavior and the existing behavior of the `popover` and `modal` interfaces. | ||||||
|
|
||||||
| If your code relied on `ionChange` firing on every confirmation (for example, to detect that the user closed the overlay without changing anything), listen for `ionDismiss` instead, or use the `didDismiss` event on the underlying alert or action sheet. | ||||||
|
|
||||||
| ### Img | ||||||
|
|
||||||
| `ion-img` is deprecated and will be removed in Ionic 10. The component was created to lazy-load images before browsers supported lazy loading natively. Modern browsers now support the [`loading="lazy"`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/img#loading) attribute on the native `<img>` element, so the component is no longer needed. | ||||||
|
|
@@ -492,20 +477,49 @@ The native `<img>` element does not emit Ionic's custom events. Use the standard | |||||
|
|
||||||
| ### Input | ||||||
|
|
||||||
| #### `autocorrect` Property Type Changed to Boolean | ||||||
|
|
||||||
| The `autocorrect` property on `ion-input` is now a `boolean` (default `false`) instead of `'on' | 'off'`. Because the attribute coerces to `true` for any value other than the string `"false"`, `autocorrect="off"` now enables autocorrect. | ||||||
|
|
||||||
| - Remove the attribute to keep autocorrect disabled (the default). | ||||||
| - Use a property binding to enable it: `[autocorrect]="true"` (Angular), `autocorrect={true}` (React), or `:autocorrect="true"` (Vue). | ||||||
|
|
||||||
| ### Searchbar | ||||||
| #### Internal DOM Structure Changes | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This heading text repeats under Select and Textarea, so the built page ends up with |
||||||
|
|
||||||
| The `autocorrect` property on `ion-searchbar` is now a `boolean` (default `false`) instead of `'on' | 'off'`. Because the attribute coerces to `true` for any value other than the string `"false"`, `autocorrect="off"` now enables autocorrect. | ||||||
| New wrapper elements have been added to the component's internal DOM structure to support floating labels with slotted start and end content. Additionally, the structure of the component has been reorganized, with some elements now grouped differently than before. This may introduce breaking changes for developers who rely on the component's internal DOM structure or apply custom styling to internal elements. | ||||||
|
|
||||||
| - Remove the attribute to keep autocorrect disabled (the default). | ||||||
| - Use a property binding to enable it: `[autocorrect]="true"` (Angular), `autocorrect={true}` (React), or `:autocorrect="true"` (Vue). | ||||||
| The following internal wrapper elements have been added: | ||||||
|
|
||||||
| - Added: `<div class="input-start">` wrapper for the start slot | ||||||
| - Added: `<div class="input-control">` wrapper for the label and native control | ||||||
| - Added: `<div class="input-end">` wrapper for the end slot and clear button | ||||||
|
|
||||||
| While the public API has not changed, selectors or style overrides targeting the previous markup may need to be updated to reference the new wrapper elements and their organization. If you have custom CSS targeting the internal structure of input, update your selectors to account for these structural changes. | ||||||
|
|
||||||
| ### Legacy Picker | ||||||
|
|
||||||
| The `ion-picker-legacy` and `ion-picker-legacy-column` components have been removed. | ||||||
|
|
||||||
| - Usages such as `ion-picker-legacy` or `IonPickerLegacy` should be changed to `ion-picker` and `IonPicker`, respectively. Review the [Picker in Modal documentation](../api/picker.md#picker-in-modal) for more information. | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I think we lost something important in this rewrite. The old wording said the path forward was |
||||||
| - Remove any usages of `pickerController`. If using React, remove any usages of the `useIonPicker` hook. These controller-based APIs have been removed. Use the [Picker](../api/picker.md) component instead. | ||||||
| - Remove any usages of the `PickerOptions`, `PickerButton`, `PickerColumn`, and `PickerColumnOption` type exports. These types were associated with the legacy picker and have been removed. | ||||||
|
|
||||||
| ### Modal | ||||||
|
|
||||||
| #### `handleBehavior` Default Changed | ||||||
|
|
||||||
| The `handleBehavior` property on `ion-modal` now defaults to `"cycle"` instead of `"none"`. For sheet modals that display a handle, this means the handle is now focusable and activating it (by click, keyboard, or screen reader) cycles the sheet through its available breakpoints. This matches the native iOS sheet behavior and keeps sheet modals operable for assistive technology users by default. | ||||||
|
|
||||||
| Sheet modals that relied on the handle being inert should set `handleBehavior="none"` to restore the previous behavior: | ||||||
|
|
||||||
| ```html | ||||||
| <ion-modal handle-behavior="none"></ion-modal> | ||||||
| ``` | ||||||
|
|
||||||
| ### Nav | ||||||
|
|
||||||
| #### Router Integration Removed | ||||||
|
|
||||||
| `ion-nav` no longer integrates with `ion-router`. It is now a standalone imperative stack navigation component, driven only through its own API (`root`, `push`, `pop`, `setRoot`, and so on) and `ion-nav-link`. | ||||||
|
|
||||||
| This only affects apps that placed an `ion-nav` inside an `ion-router` (vanilla JavaScript projects) and relied on the router to drive it. If you use `ion-nav` on its own for local, in-page stack navigation, no changes are required. | ||||||
|
|
@@ -530,6 +544,90 @@ If you relied on `ion-nav` to update the URL, use `ion-router-outlet` for URL-ba | |||||
|
|
||||||
| An `ion-nav` can still be nested inside a routed page for local, URL-less stack navigation. It manages its own stack via `root` and `ion-nav-link`, and the URL never changes as you push and pop. For a complete, working example, refer to [Using ion-nav within a Routed Page](../api/router.md#using-ion-nav-within-a-routed-page). | ||||||
|
|
||||||
| ### Router Outlet | ||||||
|
|
||||||
| `ion-router-outlet` now exposes a `swipeGesture` property that controls the swipe-to-go-back gesture per outlet. This property defaults to `true` in `"ios"` mode and `false` in `"md"` mode. | ||||||
|
|
||||||
| #### `swipeBackEnabled` Config Behavior Change | ||||||
|
|
||||||
| In React and Vue, the `swipeBackEnabled` config option is now read once when the outlet mounts. Apps that dynamically toggle this config value at runtime should migrate to the `swipeGesture` property instead. | ||||||
|
|
||||||
| **React:** | ||||||
|
|
||||||
| ```diff | ||||||
| - setupIonicReact({ swipeBackEnabled: someCondition }); | ||||||
| + <IonRouterOutlet swipeGesture={someCondition} /> | ||||||
| ``` | ||||||
|
|
||||||
| **Vue:** | ||||||
|
|
||||||
| ```diff | ||||||
| - createApp(App).use(IonicVue, { swipeBackEnabled: someCondition }) | ||||||
| + <ion-router-outlet :swipe-gesture="someCondition" /> | ||||||
| ``` | ||||||
|
|
||||||
| #### Disabling Swipe-to-Go-Back | ||||||
|
|
||||||
| To disable the gesture on a specific outlet, set `swipeGesture` to `false`: | ||||||
|
|
||||||
| ```tsx | ||||||
| <IonRouterOutlet swipeGesture={false} /> | ||||||
| ``` | ||||||
|
|
||||||
| The `swipeBackEnabled` config option is still respected as the initial default and does not need to change for apps that set it once at startup. | ||||||
|
|
||||||
| ### Searchbar | ||||||
|
|
||||||
| #### `autocorrect` Property Type Changed to Boolean | ||||||
|
|
||||||
| The `autocorrect` property on `ion-searchbar` is now a `boolean` (default `false`) instead of `'on' | 'off'`. Because the attribute coerces to `true` for any value other than the string `"false"`, `autocorrect="off"` now enables autocorrect. | ||||||
|
|
||||||
| - Remove the attribute to keep autocorrect disabled (the default). | ||||||
| - Use a property binding to enable it: `[autocorrect]="true"` (Angular), `autocorrect={true}` (React), or `:autocorrect="true"` (Vue). | ||||||
|
|
||||||
| ### Select | ||||||
|
|
||||||
| #### `ionChange` Only Fires When the Value Changes | ||||||
|
|
||||||
| The `ionChange` event on `ion-select` now only fires when the selected value actually changes. Previously, the `alert` and `action-sheet` interfaces emitted `ionChange` every time the overlay was confirmed, even when the user chose the option that was already selected. This aligns the `alert` and `action-sheet` interfaces with the existing behavior of the `popover` and `modal` interfaces, and with the documented contract of `ionChange`. | ||||||
|
|
||||||
| Apps that relied on `ionChange` firing on every confirmation (for example, to detect overlay dismissal without a value change) should listen for `ionDismiss` instead, or use the `didDismiss` event on the underlying alert or action sheet. | ||||||
|
|
||||||
| #### Action Sheet Interface `selected` Role Removed | ||||||
|
|
||||||
| When using `interface="action-sheet"`, `ion-select` no longer assigns the `selected` role to the action sheet button for the currently selected option. This aligns the `action-sheet` interface with the `alert`, `popover`, and `modal` interfaces, none of which assign this role. This does not change the selected option's styling. | ||||||
|
|
||||||
| Previously, the `selected` role was assigned only to the option matching the select's current value. Because the dismiss role mirrors the tapped button, this surfaced in just one case: re-selecting the already-selected option dismissed the action sheet with `role: "selected"` in `ionActionSheetDidDismiss`. Tapping any other option changed the value and dismissed with `role: ""`. Now that the role is no longer assigned, both cases dismiss with `role: undefined`. Apps that inspected this role to detect that a value was chosen, such as reading `role` from the underlying action sheet's `onDidDismiss` result, should listen for `ion-select`'s `ionChange` event instead, which emits the selected value when the selection changes. | ||||||
|
|
||||||
| #### Internal DOM Structure Changes | ||||||
|
|
||||||
| The component's internal DOM structure has been restructured to support floating labels with slotted start and end content. Additionally, the structure of the component has been reorganized, with some elements now grouped differently than before. The `inner` wrapper element has been removed, and its content has been split across separate wrapper elements for the start slot, control, and end slot. This may introduce breaking changes for developers who rely on the component's internal DOM structure or apply custom styling to internal elements. | ||||||
|
|
||||||
| Developers who previously styled `ion-select::part(inner)` should migrate to targeting the updated component structure using the following CSS parts instead: | ||||||
|
|
||||||
| - `ion-select::part(start)` - Target the start slot wrapper | ||||||
| - `ion-select::part(control)` - Target the control wrapper containing the label and native select. When the label is not floating or stacked, this part also contains the dropdown icon. | ||||||
| - `ion-select::part(end)` - Target the end slot wrapper. When the label is floating or stacked, this part also contains the dropdown icon. | ||||||
|
|
||||||
| ### Textarea | ||||||
|
|
||||||
| #### Internal DOM Structure Changes | ||||||
|
|
||||||
| The internal DOM structure has been modified to support floating labels with slotted start and end content. Additionally, the structure of the component has been reorganized, with some elements now grouped differently than before. This may introduce breaking changes for developers who rely on the component's internal DOM structure or apply custom styling to internal elements. | ||||||
|
|
||||||
| The following internal elements have been modified: | ||||||
|
|
||||||
| - Removed: `<div class="textarea-wrapper-inner">` | ||||||
| - Renamed: `<div class="start-slot-wrapper">` is now `<div class="textarea-start">` | ||||||
| - Added: `<div class="textarea-control">` wrapper for the label and native control | ||||||
| - Renamed: `<div class="end-slot-wrapper">` is now `<div class="textarea-end">` | ||||||
|
|
||||||
| While the public API has not changed, selectors or style overrides targeting the previous markup will need to be updated to reference the new element names and their organization. If you have custom CSS targeting the internal structure of textarea, update your selectors to account for these structural changes. | ||||||
|
|
||||||
| #### Minimum Height Change | ||||||
|
|
||||||
| The minimum height of textarea in Material Design (`md` mode) has been increased from `56px` to `72px`. This change ensures consistent heights across textareas regardless of the `fill` property or `labelPlacement`, providing a more uniform and predictable user experience. If you were relying on textareas being `56px` tall or had custom CSS based on that value, you will need to either update your styles to accommodate the new `72px` height or override it back to `56px` if needed. | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Mostly a wording nit, "ensures" plus "providing a more uniform and predictable user experience" reads like marketing copy next to the rest of the guide. The more useful half: "override it back to |
||||||
|
|
||||||
| ## Need Help Upgrading? | ||||||
|
|
||||||
| Be sure to look at the [Ionic 9 Breaking Changes Guide](https://github.com/ionic-team/ionic-framework/blob/main/BREAKING.md#version-9x) for the complete list of breaking changes. This upgrade guide only covers changes that require action from developers. | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
React Router is pinned to
@6further up for the same reason, and this section says v6 removes thenext()callback form, so@lateststarts installing an unsupported major the day it ships.