Skip to content

fix(textarea): support floating labels with slotted content - #31321

Open
brandyscarney wants to merge 36 commits into
major-9.0from
FW-6471-textarea
Open

fix(textarea): support floating labels with slotted content#31321
brandyscarney wants to merge 36 commits into
major-9.0from
FW-6471-textarea

Conversation

@brandyscarney

@brandyscarney brandyscarney commented Aug 3, 2026

Copy link
Copy Markdown
Member

Issue number: N/A


What is the current behavior?

Textareas with a floating label and a start or end slot always display the label in the floated state, regardless of whether the textarea 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 textarea when the field is empty.
    • It floats when the textarea is focused or contains a value.
  • The height of textareas has been increased to ensure consistent sizing among all textareas.
  • Start slot content is now always positioned to the left of both the label and the input.
  • 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

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

The internal DOM structure has been reorganized to support floating labels with slotted content.

Removed: .textarea-wrapper-inner

Added: .textarea-control

Renamed:

  • .start-slot-wrapper.textarea-start
  • .end-slot-wrapper.textarea-end

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:

-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

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:

/* Add a custom class to the textarea */
ion-textarea.custom {
  min-height: 44px;
}

Other information

Preview

@vercel

vercel Bot commented Aug 3, 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 14, 2026 2:00pm

Request Review

@brandyscarney
brandyscarney changed the base branch from main to major-9.0 August 3, 2026 21:17
@github-actions github-actions Bot added package: core @ionic/core package package: angular @ionic/angular package package: vue @ionic/vue package package: react @ionic/react package labels Aug 3, 2026
<slot name="start"></slot>
</div>
{hasOutlineFill && <div class="textarea-outline-container">{this.renderOutlineDecorations()}</div>}
<div class="textarea-start">

@brandyscarney brandyscarney Aug 3, 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.

I renamed the slot wrapper divs from the following:

  • start-slot-wrappertextarea-start
  • end-slot-wrappertextarea-end

The goal was to align the naming with the wrapper elements added to Input.

This also matches our existing naming pattern with elements like input-bottom and textarea-bottom.

@github-actions github-actions Bot removed package: angular @ionic/angular package package: vue @ionic/vue package package: react @ionic/react package labels Aug 3, 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.

I renamed these screenshots from textarea-slots to textarea-slot to match the folder name.


configs().forEach(({ title, screenshot, config }) => {
test.describe(title('textarea: start and end slots (visual checks)'), () => {
test.describe(title('textarea: 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.

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

Awesome work! These two issues are mostly nits and they assume this branch gets the fixes from the input branch (which it looks like they did while I was reviewing this)

@@ -16,6 +16,10 @@
--padding-start: 0px;
--highlight-height: 2px;

// Setting the min-height to 72px enforces consistent heights
// across all label placements and fills.
min-height: 72px;

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.

The rule this replaced set min-height: 56px for floating and stacked in the shared stylesheet, so putting the replacement only in the md file drops ios back to the base 44px.

Computed min-height on ios floating is 56px on major-9.0 and 44px here, and with rows="1" an ios floating textarea goes 98px to 74px. Should this live in the shared stylesheet instead? Either way it probably warrants a line in the breaking changes doc.

image

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 added the min-height: 56px back for ios: 014db57

I tried generating screenshots locally but saw no changes. Will see if the CI yells about it.

Comment thread BREAKING.md Outdated

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

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.

The 56px was only the floor for solid, outline and stacked/floating. Default fill with start, end or fixed sat on the shared 44px, so it's 44 to 72 there, and overriding back to 56px like this suggests leaves you taller than before.

Worth a word on rows too. On md start, rows="1" goes 44px to 72px and rows="2" 62px to 72px, so those two look the same now. ios changed as well and isn't mentioned.

image

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.

Updated breaking changes to mention the rows change: b5f498b

Also created a follow-up ticket to revisit this later.

@thetaPC thetaPC left a comment

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.

LGTM, just minor docs: .textarea-wrapper-inner is still mentioned in docs/shadow-parts-guidelines.md.

@@ -167,11 +240,147 @@ <h2>Outline / Autogrow</h2>
</div>
</div>

<h1>Overflowing Slots</h1>

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.

Same weird label rendering as input. Again, it can be a follow up.

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.

The label issue exists on main but I added it as a note for the follow-up ticket FW-7683.

For the shadow parts guidelines I made the update on Select since this seems to have been copied over from next where textarea is Shadow. I removed the notes about Textarea parts because it is still scoped on this branch: d2ded42

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.

4 participants