Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
f7b1a82
chore(): remove old snapshots
brandyscarney Aug 4, 2026
5821084
fix(select): support floating labels with slotted content
brandyscarney Aug 4, 2026
8a3df75
style: lint
brandyscarney Aug 4, 2026
108222a
refactor(select): remove inner part, add parts for new wrapper divs
brandyscarney Aug 5, 2026
5a47f9f
chore: build
brandyscarney Aug 5, 2026
939b029
copy editing
brandyscarney Aug 5, 2026
657ba03
test(select): rename tests and add fill and rtl coverage
brandyscarney Aug 5, 2026
9fdfc74
chore(): add updated snapshots
brandyscarney Aug 5, 2026
d5ecc50
fix(select): prevent the label click from propagating
brandyscarney Aug 5, 2026
4459f48
test(select): update to verify new parts can be customized
brandyscarney Aug 5, 2026
7a6f3ef
chore(): add updated snapshots
Ionitron Aug 5, 2026
feafbf2
chore: remove unrelated snapshot change
brandyscarney Aug 5, 2026
ecbe120
fix(select): properly group icon with the native select for justify
brandyscarney Aug 5, 2026
531ff9b
chore(): add updated snapshots
Ionitron Aug 5, 2026
b7de667
test(select): fix duplicate screenshot name
brandyscarney Aug 5, 2026
64c9cfb
chore(): revert unchanged snapshots
brandyscarney Aug 5, 2026
c822f1d
chore(): revert unrelated snapshots
brandyscarney Aug 5, 2026
a4ec7e8
test(select): add an example with dynamic slot content
brandyscarney Aug 5, 2026
b21f895
Merge branch 'major-9.0' into FW-6471-select
brandyscarney Aug 5, 2026
8610c19
chore(): add updated snapshots
brandyscarney Aug 5, 2026
a27ba41
style: remove extra function call
brandyscarney Aug 5, 2026
be5fdbb
chore(): add updated snapshots
Ionitron Aug 5, 2026
e9d9c26
chore(): add updated snapshots
Ionitron Aug 6, 2026
4575687
chore(): add updated snapshots
Ionitron Aug 6, 2026
d106957
fix(select): disable the label transition when start or end slots are…
brandyscarney Aug 6, 2026
3205851
trigger actions after outage
brandyscarney Aug 7, 2026
b5a1d65
fix(select): allow click propagation for slotted content
brandyscarney Aug 7, 2026
ffe8326
refactor(select): mark slot var as internal and add fallback
brandyscarney Aug 7, 2026
9a7b423
refactor(select): call raf from utils and cancel it in disconnectedCa…
brandyscarney Aug 7, 2026
ed00d80
Update core/src/components/select/select.scss
brandyscarney Aug 7, 2026
3b31ce8
Update core/src/components/select/select.scss
brandyscarney Aug 7, 2026
898363a
test: rename solid id to outline
brandyscarney Aug 7, 2026
c1e8eeb
refactor(select): move the start container width logic to a controller
brandyscarney Aug 10, 2026
627adcd
test(select): add tests for label-floating and skip-label-transition …
brandyscarney Aug 11, 2026
2ad8063
test(select): add back slotted button click check
brandyscarney Aug 11, 2026
a3eb13d
chore(): add updated snapshots
Ionitron Aug 11, 2026
f448a13
refactor(select): remove outdated skipLabelTransition State
brandyscarney Aug 11, 2026
50cbaa5
refactor(select): update outline function to include wrapper
brandyscarney Aug 11, 2026
bc9aba8
docs(select): update breaking changes to clarify structural changes
brandyscarney Aug 12, 2026
497c29e
test(select): add missing awaits
brandyscarney Aug 12, 2026
27a0ccb
test(select): split functional checks and improve comparisons
brandyscarney Aug 12, 2026
eb1387a
fix(select): recalculate start container width on render
brandyscarney Aug 12, 2026
a4900bf
fix(select): position the label relative to select-wrapper to avoid t…
brandyscarney Aug 12, 2026
9b800b1
fix(select): do not propagate clicks for non-slotted content
brandyscarney Aug 13, 2026
9ffbb9f
test(select): add coverage for click events when select is slotted
brandyscarney Aug 13, 2026
22afd8e
fix(select): properly ellipsis labels when slot content is wide
brandyscarney Aug 13, 2026
6e7a770
test(select): add test and screenshots for overflow behavior
brandyscarney Aug 13, 2026
c83ddb8
chore(): add updated snapshots
Ionitron Aug 13, 2026
90cdaa7
Revert "chore(): add updated snapshots"
brandyscarney Aug 13, 2026
e490f50
fix(select): fix broken justify and make sure icon doesn't shrink
brandyscarney Aug 13, 2026
bfbedc9
chore(): add updated snapshots
Ionitron Aug 13, 2026
275e0e5
Revert "chore(): add updated snapshots"
brandyscarney Aug 13, 2026
79fb5a7
fix(select): properly collapse select text before label
brandyscarney Aug 13, 2026
05937e1
style: add code comments
brandyscarney Aug 14, 2026
d2ded42
docs(shadow-guidelines): update select to reflect new structure
brandyscarney Aug 14, 2026
4d4ca45
Update BREAKING.md
brandyscarney Aug 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions BREAKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,39 @@ 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**

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

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"`

Restructured:
- `.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 `.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:

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.

<h2 id="version-9x-framework-specific">Framework Specific</h2>

<h4 id="version-9x-angular">Angular</h4>
Expand Down
4 changes: 3 additions & 1 deletion core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1758,12 +1758,14 @@ ion-select,css-prop,--select-text-media-width,ios
ion-select,css-prop,--select-text-media-width,md
ion-select,part,bottom
ion-select,part,container
ion-select,part,control
ion-select,part,end
ion-select,part,error-text
ion-select,part,helper-text
ion-select,part,icon
ion-select,part,inner
ion-select,part,label
ion-select,part,placeholder
ion-select,part,start
ion-select,part,supporting-text
ion-select,part,text
ion-select,part,wrapper
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 0 additions & 8 deletions core/src/components/select/select.ios.scss
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,6 @@
color: #{$text-color-step-350};
}

// Select Inner Wrapper
// ----------------------------------------------------------------

:host(.select-label-placement-stacked) .select-wrapper-inner,
:host(.select-label-placement-floating) .select-wrapper-inner {
width: calc(100% - $select-ios-icon-size - $select-icon-margin-start);
}
Comment on lines -22 to -28

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could not find anywhere this is necessary after changing the icon structure.


// Select: Disabled
// ----------------------------------------------------------------
// The select and label should use the
Expand Down
37 changes: 23 additions & 14 deletions core/src/components/select/select.md.outline.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@
--padding-start: 16px;
--padding-end: 16px;

/**
* The measured width of the start container, as a negative offset
* in LTR and a positive one in RTL. Written to the host by the
* start container controller, so renaming it here requires renaming
* it there too. Declared with a default so the label is positioned
* correctly before the first measurement runs.
*/
--internal-start-container-adjustment: 0px;

min-height: 56px;
}

Expand Down Expand Up @@ -82,10 +91,11 @@
position: absolute;

/**
* Label text should not extend
* beyond the bounds of the select.
* While resting this is the width of the select, which already
* excludes the start and end slots. Once the label floats the
* containing block becomes the wrapper and the rule below takes over.
*/
max-width: calc(100% - var(--padding-start) - var(--padding-end));
max-width: 100%;
}

/**
Expand All @@ -99,11 +109,20 @@
z-index: 1;
}

/**
* The resting label is positioned relative to the control so it can size to
* the space between the start and end slots. When floating, the label needs
* to escape the control and position itself relative to the wrapper instead.
*/
:host(.select-fill-outline:not(.label-floating)) .select-control {
position: relative;
}

/**
* This makes the label sit above the select.
*/
:host(.label-floating.select-fill-outline) .label-text-wrapper {
@include transform(translateY(-32%), scale(#{$form-control-label-stacked-scale}));
@include transform(translate(var(--internal-start-container-adjustment, 0px), -32%), scale(#{$form-control-label-stacked-scale}));
@include margin(0);

/**
Expand Down Expand Up @@ -197,13 +216,8 @@

:host(.select-fill-outline) .select-outline-start {
@include border(null, null, null, var(--border-width) var(--border-style) var(--border-color));
}

:host(.select-fill-outline) .select-outline-start {
@include border-radius(var(--border-radius), 0px, 0px, var(--border-radius));
}

:host(.select-fill-outline) .select-outline-start {
/**
* There should be spacing between the translated text
* and .select-outline-start. However, we can't add this
Expand All @@ -217,13 +231,8 @@

:host(.select-fill-outline) .select-outline-end {
@include border(null, var(--border-width) var(--border-style) var(--border-color), null, null);
}

:host(.select-fill-outline) .select-outline-end {
@include border-radius(0px, var(--border-radius), var(--border-radius), 0px);
}

:host(.select-fill-outline) .select-outline-end {
/**
* The ending outline fragment
* should take up the remaining free space.
Expand Down
8 changes: 0 additions & 8 deletions core/src/components/select/select.md.scss
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,6 @@
--border-radius: 16px;
}

// Select Inner Wrapper
// ----------------------------------------------------------------

:host(.select-label-placement-stacked) .select-wrapper-inner,
:host(.select-label-placement-floating) .select-wrapper-inner {
width: calc(100% - $select-md-icon-size - $select-icon-margin-start);
}
Comment on lines -121 to -127

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could not find anywhere this is necessary after changing the icon structure.


// Select: Disabled
// ----------------------------------------------------------------
// The select and label should use the
Expand Down
137 changes: 72 additions & 65 deletions core/src/components/select/select.scss
Original file line number Diff line number Diff line change
Expand Up @@ -154,36 +154,6 @@ button {
color: inherit;
}

/**
* The select icon should be centered with
* the entire container not just the control
* with floating/stacked labels.
*/
:host(.select-label-placement-stacked) .select-icon,
:host(.select-label-placement-floating) .select-icon {
position: absolute;

height: 100%;
}

/**
* This positions the icon at the correct
* edge of the component with LTR and RTL
* text directions. The position mixin cannot be
* used here because the icon is in the Shadow DOM.
*/
:host(.select-ltr.select-label-placement-stacked) .select-icon,
:host(.select-ltr.select-label-placement-floating) .select-icon {
// stylelint-disable-next-line property-disallowed-list
right: var(--padding-end, 0);
}

:host(.select-rtl.select-label-placement-stacked) .select-icon,
:host(.select-rtl.select-label-placement-floating) .select-icon {
// stylelint-disable-next-line property-disallowed-list
left: var(--padding-start, 0);
}
Comment on lines -157 to -185

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is no longer necessary using flex properties to center the icon vertically.


// Select Text
// --------------------------------------------------
.select-text {
Expand Down Expand Up @@ -237,7 +207,7 @@ button {

flex-grow: 1;

align-items: center;
align-items: stretch;
justify-content: space-between;

height: inherit;
Expand Down Expand Up @@ -270,23 +240,28 @@ button {
transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1);
}

.select-wrapper-inner {
/**
* The control must inherit specific flex properties
* so that it will be placed properly when justify
* or label-placement are set on the host.
*/
.select-control {
display: flex;

align-items: center;
flex: 1;

overflow: hidden;
}
flex-direction: inherit;

align-items: center;
justify-content: inherit;
Comment on lines +248 to +256

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With slots populated, label-placement="end" and justify both come out wrong, and I think both trace back to this block.

image image

The slot containers are siblings of the control now, so the wrapper's row-reverse for label-placement="end" reverses them too. The start slot moves from x=16 to x=308 and the end slot from x=48 to x=16, leaving the barbell jammed against the label at the far end. RTL mirrors it. Reversing .select-control instead of the wrapper fixes that and leaves the no-slot case alone.

The flex: 1 causes the other one. The control eats the free space so justify-content has nothing left, and justify="start" strands the end icon at x=324 instead of x=57. I couldn't find a clean fix, flex-grow: 0 overflows the host since .select-text and .native-wrapper both grow.

No snapshot catches either, they all use a select with no slots. Both seem like issues to me, but I could understand if you want to defer this until later.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turns out I broke it with my previous fix and I just hadn't generated the broken snapshots on this branch yet: c83ddb8

I fixed it here and also fixed an issue with the icon collapsing in the label: e490f50

🤞 the build passes

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay well I broke some stuff trying to keep icon visible and tried about a hundred different approaches but none worked so here is the latest: 79fb5a7

And I made a follow-up (FW-7682) for showing icon when it collapses.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we get a slot screenshot for label-placement="end" and one for a justify value? The existing justify and label-end snapshots all use a select with no slots, so nothing would catch a regression of this fix. This can be a follow up PR.


:host(.select-label-placement-stacked) .select-wrapper-inner,
:host(.select-label-placement-floating) .select-wrapper-inner {
/**
* When using a stacked/floating label, the inner wrapper is
* stacked vertically under the label container. This line
* ensures that the inner wrapper fills all the remaining height
* of the component.
* The min-width is set to 0 to allow flex items to shrink below
* their content size. This enables the select text to collapse
* in width. We avoid overflow: hidden here because it will clip
* the floating label.
*/
flex-grow: 1;
min-width: 0;
}

// Select Highlight
Expand Down Expand Up @@ -390,6 +365,13 @@ button {

align-items: center;

/**
* Allows the label to shrink below its intrinsic content width.
* Without this the label keeps its full width and can push the
* select and end slot outside the wrapper.
*/
min-width: 0;

/**
* Label text should not extend
* beyond the bounds of the select.
Expand All @@ -410,6 +392,18 @@ button {
pointer-events: none;
}

/**
* When start/end slots are added or removed, we temporarily
* disable the label transition to prevent the floating label
* from animating as its position is adjusted.
*
* This class is applied by the start container controller, so
* renaming it here requires renaming it there too.
*/
:host(.skip-label-transition) .label-text-wrapper {
transition: none;
}

/**
* We need to use two elements instead of
* one. The .label-text-wrapper is responsible
Expand Down Expand Up @@ -448,6 +442,14 @@ button {
.native-wrapper {
display: flex;

/**
* Give up space before the label does. Flexbox would otherwise take
* from both in proportion to their size and truncate the shorter
* label first. The factor is this large because the label truncates
* as soon as it loses any width at all, even a fraction of a pixel.
*/
flex-shrink: 100000;

align-items: center;

/**
Expand Down Expand Up @@ -554,9 +556,18 @@ button {
* down when the select is focused or has a value.
*
*/
:host(.select-label-placement-stacked) .select-wrapper,
:host(.select-label-placement-floating) .select-wrapper {
:host(.select-label-placement-stacked) .select-control,
:host(.select-label-placement-floating) .select-control {
flex-direction: column;

/**
* When using a stacked/floating label, the control is
* stacked vertically under the label container. This line
* ensures that the control fills all the remaining height
* of the component.
*/
Comment thread
brandyscarney marked this conversation as resolved.
flex-grow: 1;

align-items: start;
}

Expand All @@ -583,15 +594,11 @@ button {
}

/**
* Ensures the select does not
* overlap the label.
* Also ensure that the native wrapper
* takes up the remaining available height and width.
* Ensures that the native wrapper takes up the
* remaining available height and width.
*/
:host(.select-label-placement-stacked) .native-wrapper,
:host(.select-label-placement-floating) .native-wrapper {
@include margin(1px, 0, 0, 0);

flex-grow: 1;

width: 100%;
Expand All @@ -616,27 +623,16 @@ button {
* The placeholder should be hidden when the label
* is on top of the select. This prevents the label
* from overlapping any placeholder value.
*
* TODO(FW-5592): Remove :not(.label-floating) piece
*/
:host(.select-label-placement-floating:not(.label-floating)) .native-wrapper .select-placeholder {
:host(.select-label-placement-floating) .native-wrapper .select-placeholder {
opacity: 0;
}

/**
* We don't use .label-floating here because that would
* also include the case where the label is floating due
* to content in the start/end slot. We want the opacity
* to remain at the default in this case, since the select
* isn't being actively interacted with.
*
* TODO(FW-5592): Change entire selector to:
* :host(.label-floating.select-label-placement-floating) .native-wrapper .select-placeholder
* Show the placeholder when the label is floating.
*/
:host(.select-expanded.select-label-placement-floating) .native-wrapper .select-placeholder,
:host(.has-focus.select-label-placement-floating) .native-wrapper .select-placeholder,
:host(.has-value.select-label-placement-floating) .native-wrapper .select-placeholder {
opacity: 1;
:host(.label-floating.select-label-placement-floating) .native-wrapper .select-placeholder {
opacity: var(--placeholder-opacity);
}

/**
Expand All @@ -655,6 +651,17 @@ button {
// Start/End Slots
// ----------------------------------------------------------------

.select-start,
.select-end {
display: flex;

position: relative;

flex-shrink: 0;

align-items: center;
}

::slotted([slot="start"]), ::slotted([slot="end"]) {
/**
* Prevent the slots from shrinking when the label and/or
Expand Down
Loading
Loading