Skip to content

fix(select): support floating labels with slotted content - #31326

Open
brandyscarney wants to merge 53 commits into
major-9.0from
FW-6471-select
Open

fix(select): support floating labels with slotted content#31326
brandyscarney wants to merge 53 commits into
major-9.0from
FW-6471-select

Conversation

@brandyscarney

@brandyscarney brandyscarney commented Aug 4, 2026

Copy link
Copy Markdown
Member

Issue number: resolves #30402


What is the current behavior?

Selects with a floating label and a start or end slot always display the label in the floated state, regardless of whether the select contains a value:

without value with value
without-value with-value

What is the new behavior?

  • The floating label now behaves consistently regardless of whether start or end slots are present:
    • It overlays the select when the field is empty.
    • It floats when the select is expanded or contains a value.
    • It displays the placeholder when it is focused or expanded but does not contain a value.
  • Updates the placeholder opacity when floating so it will use the correct value of --placeholder-opacity instead of 1, matching the other select label placements.
  • Start slot content is now always positioned to the left of both the label and the select.
  • Additional screenshot tests have been added to verify these behavior and layout changes.
  • A follow-up ticket has been created to address the remaining UI differences between our leading/trailing content implementation and the md specification.

Does this introduce a breaking change?

  • Yes
  • No

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.

Other information

Preview

@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ionic-framework Ready Ready Preview Aug 13, 2026 11:58pm

Request Review

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 renamed these screenshots from select-slots to select-slot to match the folder name.


configs().forEach(({ title, screenshot, config }) => {
test.describe(title('select: start and end slots (visual checks)'), () => {
test.describe(title('select: slot'), () => {

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 was updated to match the folder name, following how we title other tests.

test('should not have visual regressions with a floating label when expanded', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/30402',

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 noticed this bug and fixed it while I was cleaning up the styles so I added a test for it here.

@brandyscarney brandyscarney Aug 5, 2026

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.

After updating all of the screenshots I found that this is technically covered by this one: https://github.com/ionic-team/ionic-framework/pull/31326/changes?#diff-5a03488c7650116b1b96323c5b1ec6fb3d652e44fd7c78d9a162a22076fcafe2

I could remove this test if desired and add the issue number on that test.

test('should not have visual regressions with a floating label when expanded', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/30402',

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 noticed this bug and fixed it while I was cleaning up the styles so I added a test for it here.

@ShaneK ShaneK left a comment

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.

This seems like an issue, but if you want to defer it to later I'd understand. Just let me know!

Comment on lines +248 to +256
.select-control {
display: flex;

align-items: center;
flex: 1;

overflow: hidden;
}
flex-direction: inherit;

align-items: center;
justify-content: inherit;

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

package: core @ionic/core package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants