diff --git a/packages/@react-spectrum/s2/src/Field.tsx b/packages/@react-spectrum/s2/src/Field.tsx
index 5709090d61f..2f797ee6998 100644
--- a/packages/@react-spectrum/s2/src/Field.tsx
+++ b/packages/@react-spectrum/s2/src/Field.tsx
@@ -305,7 +305,8 @@ export const FieldGroup = forwardRef(function FieldGroup(
styles={style({
color: 'gray-600',
flexShrink: 0,
- marginEnd: 'text-to-visual'
+ marginEnd: 'text-to-visual',
+ minHeight: '[var(--field-height)]'
})}>
{props.prefix}
@@ -480,6 +481,7 @@ export function FieldErrorIcon(props: {isDisabled?: boolean}): ReactNode {
styles: style({
order: 0,
flexShrink: 0,
+ minHeight: '[var(--field-height)]',
'--iconPrimary': {
type: 'fill',
value: {
diff --git a/packages/@react-spectrum/s2/src/TextField.tsx b/packages/@react-spectrum/s2/src/TextField.tsx
index 8e5a578e9cb..685b4483c71 100644
--- a/packages/@react-spectrum/s2/src/TextField.tsx
+++ b/packages/@react-spectrum/s2/src/TextField.tsx
@@ -122,8 +122,13 @@ export const TextArea = forwardRef(function TextArea(
{...props}
ref={ref}
fieldGroupCss={style({
- alignItems: 'baseline',
- height: 'auto'
+ // intentionally avoid using baseline due to an issue in Chrome where an empty textarea height would jump when an overlay was open
+ alignItems: 'start',
+ height: 'auto',
+ paddingTop: {
+ // offset the hidden baseline-anchor ::before down to the textarea's first line
+ '::before': '[calc((var(--field-height) - 1lh) / 2)]'
+ }
})}>
diff --git a/packages/@react-spectrum/s2/stories/TextField.stories.tsx b/packages/@react-spectrum/s2/stories/TextField.stories.tsx
index 7627b43425e..2c4c6ecabb7 100644
--- a/packages/@react-spectrum/s2/stories/TextField.stories.tsx
+++ b/packages/@react-spectrum/s2/stories/TextField.stories.tsx
@@ -110,6 +110,26 @@ export const TextAreaExample: StoryTextArea = {
}
};
+export const TextAreaContextualHelpExample: StoryTextArea = {
+ render: args => (
+