Skip to content

Conversation

@LFDanLu
Copy link
Member

@LFDanLu LFDanLu commented Jan 30, 2026

As a alternative for isTriggerUpWhenOpen from #8971, we've decided to add isExpanded alongside isPressed so that users can decide style their ComboBox/DatePicker/Menu etc trigger button states to be independent of the open state.

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added/updated unit tests and storybook for this change (for new code or code which already has tests).
  • Filled out test instructions.
  • Updated documentation (if it already exists for this component).
  • Looked at the Accessibility Practices for this feature - Aria Practices

📝 Test Instructions:

Tests should cover this, but you can sanity check ComboBox/DatePicker/etc and make sure the RAC button has the proper data attribute for expanded. Additionally, double check the equivalent S2 components (and Picker/TabsPicker) and make sure the press state doesn't get stuck when opening the dropdowns/menus.

🧢 Your Project:

RSP

@rspbot
Copy link

rspbot commented Jan 31, 2026

@rspbot
Copy link

rspbot commented Jan 31, 2026

Copy link
Member

@snowystinger snowystinger left a comment

Choose a reason for hiding this comment

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

can we run chromatic against this?

reidbarber
reidbarber previously approved these changes Feb 2, 2026
@rspbot
Copy link

rspbot commented Feb 2, 2026

@LFDanLu
Copy link
Member Author

LFDanLu commented Feb 2, 2026

https://www.chromatic.com/build?appId=5f0dd5ad2b5fc10022a2e320&number=1107, changes seem unrelated to my updates

@devongovett
Copy link
Member

We should change lines like this (e.g. S2 ActionButton/Button) to use the isExpanded state instead:

// Retain hover styles when an overlay is open.
isHovered: renderProps.isHovered || overlayTriggerState?.isOpen || false,

@devongovett
Copy link
Member

Looks like the RAC starters should also be updated to use data-expanded. For example Select now loses it's darkened color when opened.

} = props;
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/s2');

// For mouse interactions, pickers open on press start. When the popover underlay appears
Copy link
Member

Choose a reason for hiding this comment

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

Looks like this code is in S2 MenuTrigger too, so we should move that into RAC too I guess. The RAC Menu examples don't have any press scaling anymore.

@rspbot
Copy link

rspbot commented Feb 2, 2026

@rspbot
Copy link

rspbot commented Feb 2, 2026

## API Changes

react-aria-components

/react-aria-components:ComboBox

 ComboBox <T extends {}> {
   allowsCustomValue?: boolean
   allowsEmptyCollection?: boolean
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean
   children?: ChildrenOrFunction<ComboBoxRenderProps>
   className?: ClassNameOrFunction<ComboBoxRenderProps> = 'react-aria-ComboBox'
   defaultFilter?: (string, string) => boolean
   defaultInputValue?: string
   defaultItems?: Iterable<T>
   defaultSelectedKey?: Key
   disabledKeys?: Iterable<Key>
   form?: string
   formValue?: 'text' | 'key' = 'key'
   id?: string
   inputValue?: string
   isDisabled?: boolean
   isInvalid?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
-  isTriggerUpWhenOpen?: boolean
   items?: Iterable<T>
   menuTrigger?: MenuTriggerAction = 'input'
   name?: string
   onBlur?: (FocusEvent<HTMLInputElement>) => void
   onFocusChange?: (boolean) => void
   onInputChange?: (string) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onOpenChange?: (boolean, MenuTriggerAction) => void
   onSelectionChange?: (Key | null) => void
   render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, ComboBoxRenderProps>
   selectedKey?: Key | null
   shouldFocusWrap?: boolean
   slot?: string | null
   style?: StyleOrFunction<ComboBoxRenderProps>
   validate?: (ComboBoxValidationValue) => ValidationError | boolean | null | undefined
   validationBehavior?: 'native' | 'aria' = 'native'
 }

/react-aria-components:DatePicker

 DatePicker <T extends DateValue> {
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoComplete?: string
   autoFocus?: boolean
   children?: ChildrenOrFunction<DatePickerRenderProps>
   className?: ClassNameOrFunction<DatePickerRenderProps> = 'react-aria-DatePicker'
   defaultOpen?: boolean
   defaultValue?: DateValue | null
   firstDayOfWeek?: 'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat'
   form?: string
   granularity?: Granularity
   hideTimeZone?: boolean = false
   hourCycle?: number | number
   id?: string
   isDateUnavailable?: (DateValue) => boolean
   isDisabled?: boolean
   isInvalid?: boolean
   isOpen?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
-  isTriggerUpWhenOpen?: boolean
   maxValue?: DateValue | null
   minValue?: DateValue | null
   name?: string
   onBlur?: (FocusEvent<Target>) => void
   onFocus?: (FocusEvent<Target>) => void
   onFocusChange?: (boolean) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onOpenChange?: (boolean) => void
   pageBehavior?: PageBehavior = visible
   placeholderValue?: DateValue | null
   render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, DatePickerRenderProps>
   shouldCloseOnSelect?: boolean | () => boolean = true
   shouldForceLeadingZeros?: boolean
   slot?: string | null
   style?: StyleOrFunction<DatePickerRenderProps>
   validate?: (MappedDateValue<DateValue>) => ValidationError | boolean | null | undefined
   validationBehavior?: 'native' | 'aria' = 'native'
   value?: DateValue | null
 }

/react-aria-components:DateRangePicker

 DateRangePicker <T extends DateValue> {
   allowsNonContiguousRanges?: boolean
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean
   children?: ChildrenOrFunction<DateRangePickerRenderProps>
   className?: ClassNameOrFunction<DateRangePickerRenderProps> = 'react-aria-DateRangePicker'
   defaultOpen?: boolean
   defaultValue?: RangeValue<DateValue> | null
   endName?: string
   firstDayOfWeek?: 'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat'
   form?: string
   granularity?: Granularity
   hideTimeZone?: boolean = false
   hourCycle?: number | number
   id?: string
   isDateUnavailable?: (DateValue) => boolean
   isDisabled?: boolean
   isInvalid?: boolean
   isOpen?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
-  isTriggerUpWhenOpen?: boolean
   maxValue?: DateValue | null
   minValue?: DateValue | null
   onBlur?: (FocusEvent<Target>) => void
   onChange?: (RangeValue<MappedDateValue<DateValue>> | null) => void
   onFocusChange?: (boolean) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onOpenChange?: (boolean) => void
   pageBehavior?: PageBehavior = visible
   placeholderValue?: DateValue | null
   render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, DateRangePickerRenderProps>
   shouldCloseOnSelect?: boolean | () => boolean = true
   shouldForceLeadingZeros?: boolean
   slot?: string | null
   startName?: string
   style?: StyleOrFunction<DateRangePickerRenderProps>
   validate?: (RangeValue<MappedDateValue<DateValue>>) => ValidationError | boolean | null | undefined
   validationBehavior?: 'native' | 'aria' = 'native'
   value?: RangeValue<DateValue> | null
 }

/react-aria-components:DialogTrigger

 DialogTrigger {
   children: ReactNode
   defaultOpen?: boolean
   isOpen?: boolean
-  isTriggerUpWhenOpen?: boolean
   onOpenChange?: (boolean) => void
 }

/react-aria-components:MenuTrigger

 MenuTrigger {
   children: ReactNode
   defaultOpen?: boolean
   isOpen?: boolean
-  isTriggerUpWhenOpen?: boolean
   onOpenChange?: (boolean) => void
   trigger?: MenuTriggerType = 'press'
 }

/react-aria-components:Select

 Select <M extends SelectionMode = 'single', T extends {} = {
   
 }> {
   allowsEmptyCollection?: boolean
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoComplete?: string
   autoFocus?: boolean
   children?: ChildrenOrFunction<SelectRenderProps>
   className?: ClassNameOrFunction<SelectRenderProps> = 'react-aria-Select'
   defaultOpen?: boolean
   defaultValue?: ValueType<SelectionMode>
   disabledKeys?: Iterable<Key>
   excludeFromTabOrder?: boolean
   form?: string
   id?: string
   isDisabled?: boolean
   isInvalid?: boolean
   isOpen?: boolean
   isRequired?: boolean
-  isTriggerUpWhenOpen?: boolean
   name?: string
   onBlur?: (FocusEvent<Target>) => void
   onChange?: (ChangeValueType<SelectionMode>) => void
   onFocus?: (FocusEvent<Target>) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onOpenChange?: (boolean) => void
   placeholder?: string = 'Select an item' (localized)
   render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, SelectRenderProps>
   selectionMode?: SelectionMode = 'single'
   slot?: string | null
   style?: StyleOrFunction<SelectRenderProps>
   validate?: (ValidationType<SelectionMode>) => ValidationError | boolean | null | undefined
   validationBehavior?: 'native' | 'aria' = 'native'
   value?: ValueType<SelectionMode>
 }

/react-aria-components:ButtonRenderProps

 ButtonRenderProps {
   isDisabled: boolean
+  isExpanded?: boolean
   isFocusVisible: boolean
   isFocused: boolean
   isHovered: boolean
   isPending: boolean
 }

/react-aria-components:ComboBoxProps

 ComboBoxProps <T extends {}> {
   allowsCustomValue?: boolean
   allowsEmptyCollection?: boolean
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean
   children?: ChildrenOrFunction<ComboBoxRenderProps>
   className?: ClassNameOrFunction<ComboBoxRenderProps> = 'react-aria-ComboBox'
   defaultFilter?: (string, string) => boolean
   defaultInputValue?: string
   defaultItems?: Iterable<T>
   defaultSelectedKey?: Key
   disabledKeys?: Iterable<Key>
   form?: string
   formValue?: 'text' | 'key' = 'key'
   id?: string
   inputValue?: string
   isDisabled?: boolean
   isInvalid?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
-  isTriggerUpWhenOpen?: boolean
   items?: Iterable<T>
   menuTrigger?: MenuTriggerAction = 'input'
   name?: string
   onBlur?: (FocusEvent<HTMLInputElement>) => void
   onFocusChange?: (boolean) => void
   onInputChange?: (string) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onOpenChange?: (boolean, MenuTriggerAction) => void
   onSelectionChange?: (Key | null) => void
   render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, ComboBoxRenderProps>
   selectedKey?: Key | null
   shouldFocusWrap?: boolean
   slot?: string | null
   style?: StyleOrFunction<ComboBoxRenderProps>
   validate?: (ComboBoxValidationValue) => ValidationError | boolean | null | undefined
   validationBehavior?: 'native' | 'aria' = 'native'
 }

/react-aria-components:DatePickerProps

 DatePickerProps <T extends DateValue> {
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoComplete?: string
   autoFocus?: boolean
   children?: ChildrenOrFunction<DatePickerRenderProps>
   className?: ClassNameOrFunction<DatePickerRenderProps> = 'react-aria-DatePicker'
   defaultOpen?: boolean
   defaultValue?: DateValue | null
   firstDayOfWeek?: 'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat'
   form?: string
   granularity?: Granularity
   hideTimeZone?: boolean = false
   hourCycle?: number | number
   id?: string
   isDateUnavailable?: (DateValue) => boolean
   isDisabled?: boolean
   isInvalid?: boolean
   isOpen?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
-  isTriggerUpWhenOpen?: boolean
   maxValue?: DateValue | null
   minValue?: DateValue | null
   name?: string
   onBlur?: (FocusEvent<Target>) => void
   onFocus?: (FocusEvent<Target>) => void
   onFocusChange?: (boolean) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onOpenChange?: (boolean) => void
   pageBehavior?: PageBehavior = visible
   placeholderValue?: DateValue | null
   render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, DatePickerRenderProps>
   shouldCloseOnSelect?: boolean | () => boolean = true
   shouldForceLeadingZeros?: boolean
   slot?: string | null
   style?: StyleOrFunction<DatePickerRenderProps>
   validate?: (MappedDateValue<DateValue>) => ValidationError | boolean | null | undefined
   validationBehavior?: 'native' | 'aria' = 'native'
   value?: DateValue | null
 }

/react-aria-components:DateRangePickerProps

 DateRangePickerProps <T extends DateValue> {
   allowsNonContiguousRanges?: boolean
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean
   children?: ChildrenOrFunction<DateRangePickerRenderProps>
   className?: ClassNameOrFunction<DateRangePickerRenderProps> = 'react-aria-DateRangePicker'
   defaultOpen?: boolean
   defaultValue?: RangeValue<DateValue> | null
   endName?: string
   firstDayOfWeek?: 'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat'
   form?: string
   granularity?: Granularity
   hideTimeZone?: boolean = false
   hourCycle?: number | number
   id?: string
   isDateUnavailable?: (DateValue) => boolean
   isDisabled?: boolean
   isInvalid?: boolean
   isOpen?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
-  isTriggerUpWhenOpen?: boolean
   maxValue?: DateValue | null
   minValue?: DateValue | null
   onBlur?: (FocusEvent<Target>) => void
   onChange?: (RangeValue<MappedDateValue<DateValue>> | null) => void
   onFocusChange?: (boolean) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onOpenChange?: (boolean) => void
   pageBehavior?: PageBehavior = visible
   placeholderValue?: DateValue | null
   render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, DateRangePickerRenderProps>
   shouldCloseOnSelect?: boolean | () => boolean = true
   shouldForceLeadingZeros?: boolean
   slot?: string | null
   startName?: string
   style?: StyleOrFunction<DateRangePickerRenderProps>
   validate?: (RangeValue<MappedDateValue<DateValue>>) => ValidationError | boolean | null | undefined
   validationBehavior?: 'native' | 'aria' = 'native'
   value?: RangeValue<DateValue> | null
 }

/react-aria-components:DialogTriggerProps

 DialogTriggerProps {
   children: ReactNode
   defaultOpen?: boolean
   isOpen?: boolean
-  isTriggerUpWhenOpen?: boolean
   onOpenChange?: (boolean) => void
 }

/react-aria-components:MenuTriggerProps

 MenuTriggerProps {
   children: ReactNode
   defaultOpen?: boolean
   isOpen?: boolean
-  isTriggerUpWhenOpen?: boolean
   onOpenChange?: (boolean) => void
   trigger?: MenuTriggerType = 'press'
 }

/react-aria-components:SelectProps

 SelectProps <M extends SelectionMode = 'single', T extends {} = {
   
 }> {
   allowsEmptyCollection?: boolean
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoComplete?: string
   autoFocus?: boolean
   children?: ChildrenOrFunction<SelectRenderProps>
   className?: ClassNameOrFunction<SelectRenderProps> = 'react-aria-Select'
   defaultOpen?: boolean
   defaultValue?: ValueType<SelectionMode>
   disabledKeys?: Iterable<Key>
   excludeFromTabOrder?: boolean
   form?: string
   id?: string
   isDisabled?: boolean
   isInvalid?: boolean
   isOpen?: boolean
   isRequired?: boolean
-  isTriggerUpWhenOpen?: boolean
   name?: string
   onBlur?: (FocusEvent<Target>) => void
   onChange?: (ChangeValueType<SelectionMode>) => void
   onFocus?: (FocusEvent<Target>) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onOpenChange?: (boolean) => void
   placeholder?: string = 'Select an item' (localized)
   render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, SelectRenderProps>
   selectionMode?: SelectionMode = 'single'
   slot?: string | null
   style?: StyleOrFunction<SelectRenderProps>
   validate?: (ValidationType<SelectionMode>) => ValidationError | boolean | null | undefined
   validationBehavior?: 'native' | 'aria' = 'native'
   value?: ValueType<SelectionMode>
 }

/react-aria-components:ToggleButtonRenderProps

 ToggleButtonRenderProps {
   isDisabled: boolean
+  isExpanded?: boolean
   isFocusVisible: boolean
   isFocused: boolean
   isHovered: boolean
   isPressed: boolean
   state: ToggleState
 }

@react-spectrum/s2

/@react-spectrum/s2:DialogTrigger

 DialogTrigger {
-
+  children: ReactNode
+  defaultOpen?: boolean
+  isOpen?: boolean
+  onOpenChange?: (boolean) => void
 }

/@react-spectrum/s2:DialogTriggerProps

 DialogTriggerProps {
-  D: undefined
+  children: ReactNode
+  defaultOpen?: boolean
+  isOpen?: boolean
+  onOpenChange?: (boolean) => void
 }

reidbarber
reidbarber previously approved these changes Feb 2, 2026
Copy link
Member

@snowystinger snowystinger left a comment

Choose a reason for hiding this comment

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

Can a ToggleButton be expanded? it's now in the types

snowystinger
snowystinger previously approved these changes Feb 2, 2026
@LFDanLu
Copy link
Member Author

LFDanLu commented Feb 2, 2026

@snowystinger oh good catch, I suppose not. I'll remove it for now and we can always readd it if people are using the toggle button for opening overlays


&[data-open],
&[data-pressed] {
&[data-expanded] {
Copy link
Member

@devongovett devongovett Feb 2, 2026

Choose a reason for hiding this comment

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

I don't think this is right? This is changing the MenuItem styles, not the button. I think you need to change Button.css to add data-expanded that sets the background to match the pressed state.

Copy link
Member Author

Choose a reason for hiding this comment

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

oh whoops, should've checked my bulk change here, I'll fix

min-width: 0;

&[data-pressed] {
&[data-expanded] {
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is right either. On touch the menu doesn't open until press up, so now it has press scaling when it didn't before.

You need to add a new selector for data-expanded that just adds the background color change so that persists while the popover is open.

Copy link
Member Author

Choose a reason for hiding this comment

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

ah yep, accidentally conflated the press scaling with the background color styles

@devongovett
Copy link
Member

changes needed to tailwind starter as well

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants