react-spectrum
react-spectrum copied to clipboard
`useButton/usePress` should not propagate press events when `isDisabled`
Provide a general summary of the issue here
When a button is disabled, it still allows click events to propagate.
This is a problem when you have a pressable element nested within another pressable element (eg a card with a button in it).
This manifests for example if you disable a button when an action is loading.
๐ค Expected Behavior?
Even if the button is disabled, it still disallows click events to propagate.
๐ฏ Current Behavior
When the button is disabled, click events are propagated.
https://stackblitz.com/edit/vitejs-vite-nyw2s2?file=src%2FApp.tsx
๐ Possible Solution
Disallow press event propagation when disabled
๐ฆ Context
This makes it so users accidentally fire the container element's onPress event if they try to press a disabled button - causing for a frustrating user experience.
๐ฅ๏ธ Steps to Reproduce
- Create a container element that uses
useButton - Create an inner element that uses
useButton - Disable the inner element.
- Watch events propagating through to the container element.
https://stackblitz.com/edit/vitejs-vite-nyw2s2?file=src%2FApp.tsx
Version
What browsers are you seeing the problem on?
Chrome
If other, please specify.
No response
What operating system are you using?
MacOS
๐งข Your Company/Team
No response
๐ท Tracking Issue
No response
Interesting, I'm not entirely sure the direction we'll want to go here since changing this behavior could be considered a breaking change if people were relying on the current behavior. Looks like native clicks aren't propagated to the parent but pointerdown is, so there is some precedence for both propagating/stopping propagation when a disabled element is interacted with. We could potentially make this non-breaking if we make this behavior opt in and keep the current behavior as the default. I'll bring this up with the team to see if there are any additional opinions.
@LFDanLu that makes sense. would you happen to know of any workarounds? i imagine not using isDisabled and adding the condition in the onPress would work
That would probably be your best bet, but you'd need to make sure to make the button non-tabbable and have aria-disabled I imagine to mimic what a button with disabled does, otherwise it would be quite confusing to a screenreader user what that button is for.
A quick aside, but in your repro steps you have:
Create a container element that uses useButton Create an inner element that uses useButton
this would be invalid, you can't have a button inside another button. https://w3c.github.io/aria/#button
Children Presentational: True
@snowystinger ah I see! does this also apply if i use usePress for the container? apologies for my ignorance
No worries!
usePress isn't a problem, it doesn't apply any roles.
useButton is because it applies the role="button"
That said, still be careful nesting interactive components, make sure all your users can access it and check the spec to see if the roles are applying are allowed. Also if you make the container interactive and able to receive focus, then include a label/description and a role yourself.
Not really related to the issue, but I've found that clicking the disabled button with the MacBook built-in trackpad ('Tap to click' enabled) in the example above doesn't trigger the propagation.
Perhaps (I'm superficially speculating without a deeper dive) it's because using trackpad with 'tap to click' enabled results in state.pointerType: 'virtual' in usePress() and the following if-statement (inside onClick()), which looks like it might set shouldStopPropagation to true while executing?
https://github.com/adobe/react-spectrum/blob/3ebcc660dd5abfae62ec9002a938916828ce05aa/packages/%40react-aria/interactions/src/usePress.ts#L314-L332
would there be a quick patch I could put out that would solve this issue in the meantime?
@LFDanLu would happen to know how I could approximate the effect of the isDisabled prop?
If you are looking to patch usePress locally you could perhaps experiment with changing https://github.com/adobe/react-spectrum/blob/cfd890edf73783cbea0b94316d69f4bdced5ed8a/packages/%40react-aria/interactions/src/usePress.ts#L183-L185 so that it returns true if isDisabled=true. As mentioned before this could breaking.
Closing as not changing. The disabled button isn't actually propagating a press/click event. It's inert. This can be demonstrated here in Chrome https://stackblitz.com/edit/vitejs-vite-iwcthtdj?file=package.json,src%2FApp.tsx You'll notice the onClick handler is never called on the disabled Button, and it is called on the parent with the target being the parent, meaning that the click never propagated down to the disabled child.