From 389db811b06a9cb3fbe6b84142dd71b839efb7ec Mon Sep 17 00:00:00 2001 From: Brandy Smith <6577830+brandyscarney@users.noreply.github.com> Date: Thu, 6 Aug 2026 16:26:11 -0400 Subject: [PATCH 1/8] docs(updating): update v9 migration guide to include missing breaking changes --- docs/updating/9-0.md | 156 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 127 insertions(+), 29 deletions(-) diff --git a/docs/updating/9-0.md b/docs/updating/9-0.md index 37e18ebd30..1bab1177eb 100644 --- a/docs/updating/9-0.md +++ b/docs/updating/9-0.md @@ -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 `` element, so the component is no longer needed. @@ -492,20 +477,49 @@ The native `` 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 -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: `
` wrapper for the start slot +- Added: `
` wrapper for the label and native control +- Added: `
` 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. +- 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 + +``` ### 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 }); ++ +``` + +**Vue:** + +```diff +- createApp(App).use(IonicVue, { swipeBackEnabled: someCondition }) ++ +``` + +#### Disabling Swipe-to-Go-Back + +To disable the gesture on a specific outlet, set `swipeGesture` to `false`: + +```tsx + +``` + +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: `
` +- Renamed: `
` is now `
` +- Added: `
` wrapper for the label and native control +- Renamed: `
` is now `
` + +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. + ## 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. From d8e3e86e9f62b6bb5ae5663227c1809c3d7475a8 Mon Sep 17 00:00:00 2001 From: Brandy Smith Date: Tue, 11 Aug 2026 15:54:19 -0400 Subject: [PATCH 2/8] Update docs/updating/9-0.md Co-authored-by: Shane --- docs/updating/9-0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/updating/9-0.md b/docs/updating/9-0.md index 1bab1177eb..5fc9f1c1e3 100644 --- a/docs/updating/9-0.md +++ b/docs/updating/9-0.md @@ -626,7 +626,7 @@ While the public API has not changed, selectors or style overrides targeting the #### 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. +The minimum height of textarea in Material Design (`md` mode) has been increased from `56px` to `72px`. Textareas are now the same height regardless of the `fill` property or `labelPlacement`. If you were relying on textareas being `56px` tall or had custom CSS based on that value, update your styles for the new `72px` height or override it back to `56px`. ## Need Help Upgrading? From 675dffa80b2af5431a9d409722589ea4e9e9932d Mon Sep 17 00:00:00 2001 From: Brandy Smith Date: Tue, 11 Aug 2026 15:54:39 -0400 Subject: [PATCH 3/8] Update docs/updating/9-0.md Co-authored-by: Shane --- docs/updating/9-0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/updating/9-0.md b/docs/updating/9-0.md index 5fc9f1c1e3..e29491ed8a 100644 --- a/docs/updating/9-0.md +++ b/docs/updating/9-0.md @@ -500,7 +500,7 @@ While the public API has not changed, selectors or style overrides targeting the 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. +- 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. - 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. From cfe5f2eb72fd212e80c16f1ee4a4665f2a645fc0 Mon Sep 17 00:00:00 2001 From: Brandy Smith Date: Tue, 11 Aug 2026 15:54:50 -0400 Subject: [PATCH 4/8] Update docs/updating/9-0.md Co-authored-by: Shane --- docs/updating/9-0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/updating/9-0.md b/docs/updating/9-0.md index e29491ed8a..45c4956abe 100644 --- a/docs/updating/9-0.md +++ b/docs/updating/9-0.md @@ -357,7 +357,7 @@ npm install @ionic/vue@latest @ionic/vue-router@latest 1. Ionic 9 supports Vue Router 5. Update to the latest version of Vue Router: ```shell -npm install vue-router@latest +npm install vue-router@5 ``` `@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`. From b0c38a1539a3d7bf49b7a2a36b0d0574a8874d25 Mon Sep 17 00:00:00 2001 From: Brandy Smith <6577830+brandyscarney@users.noreply.github.com> Date: Wed, 12 Aug 2026 09:51:41 -0400 Subject: [PATCH 5/8] docs(updating): update duplicate heading links --- docs/updating/9-0.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/updating/9-0.md b/docs/updating/9-0.md index 45c4956abe..2ae0fcce71 100644 --- a/docs/updating/9-0.md +++ b/docs/updating/9-0.md @@ -477,14 +477,14 @@ The native `` element does not emit Ionic's custom events. Use the standard ### Input -#### `autocorrect` Property Type Changed to Boolean +#### `autocorrect` Property Type Changed to Boolean {#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). -#### Internal DOM Structure Changes +#### Internal DOM Structure Changes {#input-internal-dom-structure-changes} 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. @@ -578,7 +578,7 @@ The `swipeBackEnabled` config option is still respected as the initial default a ### Searchbar -#### `autocorrect` Property Type Changed to Boolean +#### `autocorrect` Property Type Changed to Boolean {#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. @@ -599,7 +599,7 @@ When using `interface="action-sheet"`, `ion-select` no longer assigns the `selec 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 +#### Internal DOM Structure Changes {#select-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. @@ -611,7 +611,7 @@ Developers who previously styled `ion-select::part(inner)` should migrate to tar ### Textarea -#### Internal DOM Structure Changes +#### Internal DOM Structure Changes {#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. From de2a9323b7f85d82e6a0850b276372d62a95702e Mon Sep 17 00:00:00 2001 From: Brandy Smith <6577830+brandyscarney@users.noreply.github.com> Date: Wed, 12 Aug 2026 12:02:52 -0400 Subject: [PATCH 6/8] docs(updating): add more details on structural changes --- docs/updating/9-0.md | 113 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 95 insertions(+), 18 deletions(-) diff --git a/docs/updating/9-0.md b/docs/updating/9-0.md index 2ae0fcce71..199780d838 100644 --- a/docs/updating/9-0.md +++ b/docs/updating/9-0.md @@ -484,17 +484,44 @@ The `autocorrect` property on `ion-input` is now a `boolean` (default `false`) i - 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). +#### Floating Label Behavior {#input-floating-label-behavior} + +Floating labels no longer automatically float when the input contains slotted content. Labels float only when the input is focused or has a value. + #### Internal DOM Structure Changes {#input-internal-dom-structure-changes} -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. +The internal DOM structure has been reorganized to support floating labels with slotted content. + +Added: + +- `.input-start` +- `.input-control` +- `.input-end` + +Restructured: + +- `.label-text-wrapper` moved from `.input-wrapper` into `.input-control` +- `.native-wrapper` moved from `.input-wrapper` into `.input-control` +- Start slot moved from `.native-wrapper` into `.input-start` +- Clear button icon moved from `.native-wrapper` into `.input-end` +- End slot moved from `.native-wrapper` into `.input-end` +- `.input-control` now contains the label text and native `input`, while start/end content is separated into dedicated wrappers + +Update your selectors to account for these structural changes: + +```diff +-ion-input .input-wrapper .native-wrapper { } ++ion-input .input-control .native-wrapper { } -The following internal wrapper elements have been added: +-ion-input .input-wrapper .native-wrapper [slot="start"] { } ++ion-input .input-start [slot="start"] { } -- Added: `
` wrapper for the start slot -- Added: `
` wrapper for the label and native control -- Added: `
` wrapper for the end slot and clear button +-ion-input .input-wrapper .native-wrapper .input-clear-icon { } ++ion-input .input-end .input-clear-icon { } -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. +-ion-input .input-wrapper .native-wrapper [slot="end"] { } ++ion-input .input-end [slot="end"] { } +``` ### Legacy Picker @@ -599,30 +626,80 @@ When using `interface="action-sheet"`, `ion-select` no longer assigns the `selec 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. +#### Floating Label Behavior {#select-floating-label-behavior} + +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. + #### Internal DOM Structure Changes {#select-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. +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. + +Added: + +- `.select-start` — `part="start"` +- `.select-control` — `part="control"` +- `.select-end` — `part="end"` + +Removed: + +- `.select-wrapper-inner` — `part="inner"` -Developers who previously styled `ion-select::part(inner)` should migrate to targeting the updated component structure using the following CSS parts instead: +Restructured: -- `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. +- `.label-text-wrapper` remains `part="label"` but moved from `.select-wrapper` into `.select-control` +- `.native-wrapper` remains `part="container"` but moved from `.select-wrapper-inner` into `.select-control` +- Start slot moved from `.select-wrapper-inner` into `.select-start` (`part="start"`) +- End slot moved from `.select-wrapper-inner` into `.select-end` (`part="end"`) +- `.select-icon` remains `part="icon"` but its location depends on the label state: + - With a start/end label, the icon is inside `.select-control` + - With a floating/stacked label, the icon is inside `.select-end` + +Update selectors that target the exposed shadow parts to account for the new structure: + +If you currently target `part="inner"`, that part has been removed. Update those styles to target the new parts as appropriate. + +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. + +Use the new `part="start"`, `part="control"`, and `part="end"` parts to target the new structural wrappers. ### Textarea +#### Floating Label Behavior {#textarea-floating-label-behavior} + +Floating labels no longer automatically float when the textarea contains slotted content. Labels float only when the textarea is focused or has a value. + #### Internal DOM Structure Changes {#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 internal DOM structure has been reorganized to support floating labels with slotted content. + +Removed: `.textarea-wrapper-inner` + +Added: `.textarea-control` -The following internal elements have been modified: +Renamed: -- Removed: `
` -- Renamed: `
` is now `
` -- Added: `
` wrapper for the label and native control -- Renamed: `
` is now `
` +- `.start-slot-wrapper` → `.textarea-start` +- `.end-slot-wrapper` → `.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. +Restructured: + +- `.label-text-wrapper` moved from `.textarea-wrapper-inner` into `.textarea-control` +- `.native-wrapper` moved from `.textarea-wrapper-inner` into `.textarea-control` +- `.start-slot-wrapper` moved from `.textarea-wrapper-inner` to `.textarea-wrapper` and was renamed `.textarea-start` +- `.end-slot-wrapper` moved from `.textarea-wrapper-inner` to `.textarea-wrapper` and was renamed `.textarea-end` + +Update your selectors to account for these structural changes: + +```diff +-ion-textarea .textarea-wrapper-inner .native-wrapper { } ++ion-textarea .textarea-control .native-wrapper { } + +-ion-textarea .start-slot-wrapper [slot="start"] { } ++ion-textarea .textarea-start [slot="start"] { } + +-ion-textarea .end-slot-wrapper [slot="end"] { } ++ion-textarea .textarea-end [slot="end"] { } +``` #### Minimum Height Change From fed8db6c56e50782eafca007e92ec42c5814f658 Mon Sep 17 00:00:00 2001 From: Brandy Smith <6577830+brandyscarney@users.noreply.github.com> Date: Thu, 13 Aug 2026 18:21:35 -0400 Subject: [PATCH 7/8] docs(updating): add more details to the textarea min-height change --- docs/updating/9-0.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/updating/9-0.md b/docs/updating/9-0.md index 6e48ba719e..4efa24a951 100644 --- a/docs/updating/9-0.md +++ b/docs/updating/9-0.md @@ -737,7 +737,26 @@ 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`. Textareas are now the same height regardless of the `fill` property or `labelPlacement`. If you were relying on textareas being `56px` tall or had custom CSS based on that value, update your styles for the new `72px` height or override it back to `56px`. +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: + +| Fill | Label placement | Previous minimum height | +| ------------------ | ----------------------- | ----------------------- | +| default | `start`, `end`, `fixed` | `44px` | +| default | `floating`, `stacked` | `56px` | +| `solid`, `outline` | any | `56px` | + +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. + +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`. + +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: + +```css +/* Add a custom class to the textarea */ +ion-textarea.custom { + min-height: 44px; +} +``` ## Need Help Upgrading? From 736fd4817e03ba3a5e521fa545b3023511570214 Mon Sep 17 00:00:00 2001 From: Brandy Smith <6577830+brandyscarney@users.noreply.github.com> Date: Fri, 14 Aug 2026 14:34:41 -0400 Subject: [PATCH 8/8] docs: update icon location --- docs/updating/9-0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/updating/9-0.md b/docs/updating/9-0.md index 4efa24a951..6c4081fb71 100644 --- a/docs/updating/9-0.md +++ b/docs/updating/9-0.md @@ -685,7 +685,7 @@ Restructured: - Start slot moved from `.select-wrapper-inner` into `.select-start` (`part="start"`) - End slot moved from `.select-wrapper-inner` into `.select-end` (`part="end"`) - `.select-icon` remains `part="icon"` but its location depends on the label state: - - With a start/end label, the icon is inside `.select-control` + - With a start/end label, the icon is inside `.native-wrapper` - With a floating/stacked label, the icon is inside `.select-end` Update selectors that target the exposed shadow parts to account for the new structure: