Skip to content

fix: fix isPressed state to be separate from overlay state#9563

Open
LFDanLu wants to merge 13 commits intomainfrom
8971_followup
Open

fix: fix isPressed state to be separate from overlay state#9563
LFDanLu wants to merge 13 commits intomainfrom
8971_followup

Conversation

@LFDanLu
Copy link
Member

@LFDanLu LFDanLu commented Jan 30, 2026

Fixes #8339

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.

The original behavior of tying the isPressed state to the expanded state was a mistake due to assumptions in behavior made for S2 which should not of propagated to React Aria Components.

✅ 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

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

@LFDanLu LFDanLu dismissed stale reviews from snowystinger and reidbarber via 1edfef2 February 2, 2026 20:53

&[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

@rspbot
Copy link

rspbot commented Feb 3, 2026

snowystinger
snowystinger previously approved these changes Feb 4, 2026
@LFDanLu LFDanLu changed the title feat: Add isExpanded renderProp to Button fix: Add isExpanded renderProp to Button Feb 5, 2026
@LFDanLu LFDanLu changed the title fix: Add isExpanded renderProp to Button fix: fix isPressed state to be separate from overlay state Feb 5, 2026
@rspbot
Copy link

rspbot commented Feb 6, 2026

@rspbot
Copy link

rspbot commented Feb 6, 2026

## API Changes

react-aria-components

/react-aria-components:ButtonRenderProps

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

/react-aria-components:HeaderProps

-HeaderProps {
-  render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, undefined>
-}

/react-aria-components:NumberFieldState

 NumberFieldState {
   canDecrement: boolean
   canIncrement: boolean
-  commit: (string) => void
+  commit: () => void
   commitValidation: () => void
   decrement: () => void
   decrementToMin: () => void
   defaultNumberValue: number
   increment: () => void
   incrementToMax: () => void
   inputValue: string
   maxValue?: number
   minValue?: number
   numberValue: number
   realtimeValidation: ValidationResult
   resetValidation: () => void
   setInputValue: (string) => void
   setNumberValue: (number) => void
   updateValidation: (ValidationResult) => void
   validate: (string) => boolean
 }

@react-stately/color

/@react-stately/color:ColorChannelFieldState

 ColorChannelFieldState {
   canDecrement: boolean
   canIncrement: boolean
   colorValue: Color
-  commit: (string) => void
+  commit: () => void
   commitValidation: () => void
   decrement: () => void
   decrementToMin: () => void
   defaultColorValue: Color | null
   displayValidation: ValidationResult
   increment: () => void
   incrementToMax: () => void
   inputValue: string
   maxValue?: number
   minValue?: number
   numberValue: number
   realtimeValidation: ValidationResult
   resetValidation: () => void
   setColorValue: (Color | null) => void
   setInputValue: (string) => void
   setNumberValue: (number) => void
   updateValidation: (ValidationResult) => void
   validate: (string) => boolean
 }

@react-stately/numberfield

/@react-stately/numberfield:NumberFieldState

 NumberFieldState {
   canDecrement: boolean
   canIncrement: boolean
-  commit: (string) => void
+  commit: () => void
   commitValidation: () => void
   decrement: () => void
   decrementToMin: () => void
   defaultNumberValue: number
   increment: () => void
   incrementToMax: () => void
   inputValue: string
   maxValue?: number
   minValue?: number
   numberValue: number
   realtimeValidation: ValidationResult
   resetValidation: () => void
   setInputValue: (string) => void
   setNumberValue: (number) => void
   updateValidation: (ValidationResult) => void
   validate: (string) => boolean
 }


&[data-pressed] {
&[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 think this one isn't quite right. In DatePicker, the button remains scaled down when the popover is open. It should only change the background.

Copy link
Member Author

Choose a reason for hiding this comment

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

yep, I had added it for visual parity with what was on prod for combobox/datepicker/other field button styling but forgot that was actually what we didn't want to have

style: {'--trigger-width': buttonWidth} as React.CSSProperties
}]
}],
[ButtonContext, {isExpanded: state.isOpen}]
Copy link
Member

Choose a reason for hiding this comment

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

Should we only do this for popovers and not modals somehow? I think this will still cause #8339

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.

Button remains in pressed state when opening a modal

5 participants