Skip to content

Commit 5ca6eb4

Browse files
docs(updating): update v9 migration guide to add missing breaking changes (#4620)
Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com> Co-authored-by: Shane <shane@shanessite.net>
1 parent 31b2943 commit 5ca6eb4

1 file changed

Lines changed: 223 additions & 29 deletions

File tree

docs/updating/9-0.md

Lines changed: 223 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -374,10 +374,10 @@ For more information on migrating from React Router v5 to v6, refer to the [Reac
374374

375375
### Vue
376376

377-
1. Ionic 9 supports Vue 3.5+ and Vue Router 5. Update Vue and Vue Router:
377+
1. Ionic 9 supports Vue 3.5+. Update to the latest version of Vue:
378378

379379
```shell
380-
npm install vue@latest vue-router@latest
380+
npm install vue@latest
381381
```
382382

383383
2. Update to the latest version of Ionic 9:
@@ -386,21 +386,18 @@ npm install vue@latest vue-router@latest
386386
npm install @ionic/vue@latest @ionic/vue-router@latest
387387
```
388388

389-
#### Vue Router 5 Migration
389+
### Vue Router
390390

391-
`@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`.
392-
393-
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`:
391+
1. Ionic 9 supports Vue Router 5. Update to the latest version of Vue Router:
394392

395-
```diff
396-
"dependencies": {
397-
- "vue": "^3.4.0",
398-
- "vue-router": "^4.0.0"
399-
+ "vue": "^3.5.0",
400-
+ "vue-router": "^5.0.0"
401-
}
393+
```shell
394+
npm install vue-router@5
402395
```
403396

397+
`@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`.
398+
399+
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`.
400+
404401
#### Deprecation Warning for `next()` in Navigation Guards
405402

406403
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:
@@ -476,18 +473,6 @@ Ionic 9 officially supports Capacitor 7 and later. Native platform detection no
476473

477474
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).
478475

479-
### Legacy Picker
480-
481-
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.
482-
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.
483-
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.
484-
485-
### Select
486-
487-
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.
488-
489-
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.
490-
491476
### Img
492477

493478
`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.
@@ -526,20 +511,76 @@ The native `<img>` element does not emit Ionic's custom events. Use the standard
526511

527512
### Input
528513

514+
#### `autocorrect` Property Type Changed to Boolean {#input-autocorrect-property-type-changed-to-boolean}
515+
529516
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.
530517

531518
- Remove the attribute to keep autocorrect disabled (the default).
532519
- Use a property binding to enable it: `[autocorrect]="true"` (Angular), `autocorrect={true}` (React), or `:autocorrect="true"` (Vue).
533520

534-
### Searchbar
521+
#### Floating Label Behavior {#input-floating-label-behavior}
535522

536-
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.
523+
Floating labels no longer automatically float when the input contains slotted content. Labels float only when the input is focused or has a value.
537524

538-
- Remove the attribute to keep autocorrect disabled (the default).
539-
- Use a property binding to enable it: `[autocorrect]="true"` (Angular), `autocorrect={true}` (React), or `:autocorrect="true"` (Vue).
525+
#### Internal DOM Structure Changes {#input-internal-dom-structure-changes}
526+
527+
The internal DOM structure has been reorganized to support floating labels with slotted content.
528+
529+
Added:
530+
531+
- `.input-start`
532+
- `.input-control`
533+
- `.input-end`
534+
535+
Restructured:
536+
537+
- `.label-text-wrapper` moved from `.input-wrapper` into `.input-control`
538+
- `.native-wrapper` moved from `.input-wrapper` into `.input-control`
539+
- Start slot moved from `.native-wrapper` into `.input-start`
540+
- Clear button icon moved from `.native-wrapper` into `.input-end`
541+
- End slot moved from `.native-wrapper` into `.input-end`
542+
- `.input-control` now contains the label text and native `input`, while start/end content is separated into dedicated wrappers
543+
544+
Update your selectors to account for these structural changes:
545+
546+
```diff
547+
-ion-input .input-wrapper .native-wrapper { }
548+
+ion-input .input-control .native-wrapper { }
549+
550+
-ion-input .input-wrapper .native-wrapper [slot="start"] { }
551+
+ion-input .input-start [slot="start"] { }
552+
553+
-ion-input .input-wrapper .native-wrapper .input-clear-icon { }
554+
+ion-input .input-end .input-clear-icon { }
555+
556+
-ion-input .input-wrapper .native-wrapper [slot="end"] { }
557+
+ion-input .input-end [slot="end"] { }
558+
```
559+
560+
### Legacy Picker
561+
562+
The `ion-picker-legacy` and `ion-picker-legacy-column` components have been removed.
563+
564+
- Replace `ion-picker-legacy` with `ion-picker`, and `ion-picker-legacy-column` with `ion-picker-column`. Since `ion-picker` renders inline instead of as an overlay, present it inside an `ion-modal` to keep the previous experience. Review the [Picker in Modal documentation](../api/picker.md#picker-in-modal) for more information.
565+
- 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.
566+
- Remove any usages of the `PickerOptions`, `PickerButton`, `PickerColumn`, and `PickerColumnOption` type exports. These types were associated with the legacy picker and have been removed.
567+
568+
### Modal
569+
570+
#### `handleBehavior` Default Changed
571+
572+
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.
573+
574+
Sheet modals that relied on the handle being inert should set `handleBehavior="none"` to restore the previous behavior:
575+
576+
```html
577+
<ion-modal handle-behavior="none"></ion-modal>
578+
```
540579

541580
### Nav
542581

582+
#### Router Integration Removed
583+
543584
`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`.
544585

545586
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.
@@ -564,6 +605,159 @@ If you relied on `ion-nav` to update the URL, use `ion-router-outlet` for URL-ba
564605

565606
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).
566607

608+
### Router Outlet
609+
610+
`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.
611+
612+
#### `swipeBackEnabled` Config Behavior Change
613+
614+
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.
615+
616+
**React:**
617+
618+
```diff
619+
- setupIonicReact({ swipeBackEnabled: someCondition });
620+
+ <IonRouterOutlet swipeGesture={someCondition} />
621+
```
622+
623+
**Vue:**
624+
625+
```diff
626+
- createApp(App).use(IonicVue, { swipeBackEnabled: someCondition })
627+
+ <ion-router-outlet :swipe-gesture="someCondition" />
628+
```
629+
630+
#### Disabling Swipe-to-Go-Back
631+
632+
To disable the gesture on a specific outlet, set `swipeGesture` to `false`:
633+
634+
```tsx
635+
<IonRouterOutlet swipeGesture={false} />
636+
```
637+
638+
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.
639+
640+
### Searchbar
641+
642+
#### `autocorrect` Property Type Changed to Boolean {#searchbar-autocorrect-property-type-changed-to-boolean}
643+
644+
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.
645+
646+
- Remove the attribute to keep autocorrect disabled (the default).
647+
- Use a property binding to enable it: `[autocorrect]="true"` (Angular), `autocorrect={true}` (React), or `:autocorrect="true"` (Vue).
648+
649+
### Select
650+
651+
#### `ionChange` Only Fires When the Value Changes
652+
653+
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`.
654+
655+
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.
656+
657+
#### Action Sheet Interface `selected` Role Removed
658+
659+
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.
660+
661+
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.
662+
663+
#### Floating Label Behavior {#select-floating-label-behavior}
664+
665+
Floating labels no longer automatically float when the select contains slotted content. Labels float only when the select is focused or has a value. Additionally, when using a floating label, the placeholder is only visible when the select is focused.
666+
667+
#### Internal DOM Structure Changes {#select-internal-dom-structure-changes}
668+
669+
The internal DOM structure has been reorganized to support floating labels with slotted content. This changes the structure and location of several exposed shadow parts.
670+
671+
Added:
672+
673+
- `.select-start``part="start"`
674+
- `.select-control``part="control"`
675+
- `.select-end``part="end"`
676+
677+
Removed:
678+
679+
- `.select-wrapper-inner``part="inner"`
680+
681+
Restructured:
682+
683+
- `.label-text-wrapper` remains `part="label"` but moved from `.select-wrapper` into `.select-control`
684+
- `.native-wrapper` remains `part="container"` but moved from `.select-wrapper-inner` into `.select-control`
685+
- Start slot moved from `.select-wrapper-inner` into `.select-start` (`part="start"`)
686+
- End slot moved from `.select-wrapper-inner` into `.select-end` (`part="end"`)
687+
- `.select-icon` remains `part="icon"` but its location depends on the label state:
688+
- With a start/end label, the icon is inside `.native-wrapper`
689+
- With a floating/stacked label, the icon is inside `.select-end`
690+
691+
Update selectors that target the exposed shadow parts to account for the new structure:
692+
693+
If you currently target `part="inner"`, that part has been removed. Update those styles to target the new parts as appropriate.
694+
695+
If you target `part="label"`, `part="container"`, or `part="icon"`, the part names remain unchanged, but their position in the shadow DOM has changed. This may affect styles that depend on the relationship or layout of these parts.
696+
697+
Use the new `part="start"`, `part="control"`, and `part="end"` parts to target the new structural wrappers.
698+
699+
### Textarea
700+
701+
#### Floating Label Behavior {#textarea-floating-label-behavior}
702+
703+
Floating labels no longer automatically float when the textarea contains slotted content. Labels float only when the textarea is focused or has a value.
704+
705+
#### Internal DOM Structure Changes {#textarea-internal-dom-structure-changes}
706+
707+
The internal DOM structure has been reorganized to support floating labels with slotted content.
708+
709+
Removed: `.textarea-wrapper-inner`
710+
711+
Added: `.textarea-control`
712+
713+
Renamed:
714+
715+
- `.start-slot-wrapper``.textarea-start`
716+
- `.end-slot-wrapper``.textarea-end`
717+
718+
Restructured:
719+
720+
- `.label-text-wrapper` moved from `.textarea-wrapper-inner` into `.textarea-control`
721+
- `.native-wrapper` moved from `.textarea-wrapper-inner` into `.textarea-control`
722+
- `.start-slot-wrapper` moved from `.textarea-wrapper-inner` to `.textarea-wrapper` and was renamed `.textarea-start`
723+
- `.end-slot-wrapper` moved from `.textarea-wrapper-inner` to `.textarea-wrapper` and was renamed `.textarea-end`
724+
725+
Update your selectors to account for these structural changes:
726+
727+
```diff
728+
-ion-textarea .textarea-wrapper-inner .native-wrapper { }
729+
+ion-textarea .textarea-control .native-wrapper { }
730+
731+
-ion-textarea .start-slot-wrapper [slot="start"] { }
732+
+ion-textarea .textarea-start [slot="start"] { }
733+
734+
-ion-textarea .end-slot-wrapper [slot="end"] { }
735+
+ion-textarea .textarea-end [slot="end"] { }
736+
```
737+
738+
#### Minimum Height Change
739+
740+
The minimum height of textarea in Material Design (`md` mode) is now `72px`. At the default number of rows this makes textareas the same height regardless of the `fill` property or `labelPlacement`. Previously the minimum height was:
741+
742+
| Fill | Label placement | Previous minimum height |
743+
| ------------------ | ----------------------- | ----------------------- |
744+
| default | `start`, `end`, `fixed` | `44px` |
745+
| default | `floating`, `stacked` | `56px` |
746+
| `solid`, `outline` | any | `56px` |
747+
748+
These were minimums, not the heights textareas actually rendered at. A textarea with content in the `start` or `end` slots was already taller than its minimum, so the change affects it differently. For example, a `fill="solid"` textarea with slotted icons and buttons previously rendered at `72px` with a `start` label and `81px` with a `floating` label. Both are now `72px`, so that floating label case is `9px` shorter than before rather than taller.
749+
750+
Because `72px` is taller than two rows of text, `rows` values below `3` no longer change the height of the textarea in `md` mode: `rows="1"` and `rows="2"` both render at `72px`.
751+
752+
If you were relying on the previous heights, or you need `rows` to control the height, override the minimum height back. The override has to be more specific than the component's own style, so a bare `ion-textarea` selector will not apply. Add a custom class to the textarea to increase specificity:
753+
754+
```css
755+
/* Add a custom class to the textarea */
756+
ion-textarea.custom {
757+
min-height: 44px;
758+
}
759+
```
760+
567761
## Need Help Upgrading?
568762

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

0 commit comments

Comments
 (0)