Conversation
…able
A disabled `Select.Trigger` rendered with `nativeButton={false}` could not be
selected with the mouse, so its value could not be read out and copied, while a
read-only trigger, `Combobox` and every other input could.
`useButton` canceled the `pointerdown` default action for a disabled button to
keep focus off it. Focus, starting a text selection and starting a drag are one
default action, so the cancel always took the selection with it. It was needed
because the element stayed focusable: `useFocusableWhenDisabled` gave a disabled
non-native button `tabIndex: -1` and `Select.Trigger` set
`tabIndex: disabled ? -1 : 0` itself, and `-1` keeps an element focusable by a
click.
A disabled non-native button is now unfocusable rather than focusable and
canceled, which is what a disabled `<button>` is: no `tabIndex` is assigned while
disabled unless `focusableWhenDisabled` asks for it, and `pointerdown` is left
alone. The tab order is unchanged, because an element carrying no `tabindex` is
not tabbable either.
vikuscz
requested review from
atomiks,
colmtuite,
flaviendelangle,
jjenzz and
michaldudak
as code owners
September 18, 2026 12:31
commit: |
Bundle size
PerformanceTotal duration: 1,003.48 ms -96.80 ms(-8.8%) | Renders: 76 (+0) | Paint: 1,704.19 ms -132.99 ms(-7.2%)
13 tests within noise — details Check out the code infra dashboard for more information about this PR. |
✅ Deploy Preview for base-ui ready!Built without sensitive environment variables
To edit notification comments on pull requests, go to your Netlify project configuration. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5736
A double click on the value of a disabled
Select.Triggerselects nothing, while a selection dragged in from outside the trigger passes straight through and includes it, so the text is selectable in principle and only a press starting inside is refused. The same trigger withoutdisabledselects the value, and so doComboboxand every other input, which leaves the value of a disabled field as the one value in the family that cannot be read out and copied.Cause
useButtoncancels thepointerdowndefault action for a disabled button:Focus, starting a text selection and starting a drag are one default action, so canceling it to keep focus off the button also makes the text unselectable. The cancel is needed because the element stays focusable:
useFocusableWhenDisabledgives a disabled non-native buttontabIndex: -1, andSelect.TriggersetstabIndex: disabled ? -1 : 0itself.-1takes it out of the tab order but keeps it focusable by a click.Measured with a library-free repro, identical in chromium, firefox and webkit:
<button disabled>divwitharia-disabledand notabindextabindex="-1"tabindex="-1"and a canceledpointerdownThe two are therefore not separable on one element. While
-1is present, canceling the default action is the only way to keep focus off, and it always costs the selection.Fix
Make a disabled non-native button unfocusable instead of focusable and canceled, which is what a disabled
<button>is:useFocusableWhenDisabledassigns notabIndexat all when the button is disabled, non-native and notfocusableWhenDisabled, instead of-1.Select.Triggerlikewise carries notabIndexwhile disabled.useButtonno longer cancelspointerdown. The early return stays, so no external handler runs.The tab order does not change: a
<span>or<div>carrying notabindexis not tabbable either.focusableWhenDisabledis untouched, and so is every composite item, whosetabIndexthis hook never set.Relationship to #5607
#5607 landed this behavior a day before this PR, and its changelog note says non-native disabled triggers "receive
tabindex="-1"". Everything that PR set out to do is kept: disabled triggers leave the tab order, native ones carrydisabled, non-native ones carryaria-disabled="true"and nodisabledattribute. Only the mechanism changes, so that one line of the note becomes "carry notabindex".The three tests that asserted the attribute now assert its absence. Their names and their behavioral assertions are unchanged:
aria-disabledis still set, Tab still does not focus the trigger, no handler still fires.If you would rather keep
tabindex="-1", the trade-off is the one in the table above: the value of a disabled field stays unselectable. I could not find a third option. No arrangement of handlers separates focus from text selection on the same element, and reverting focus from afocushandler would fire focus and blur and runSelect.Trigger's ownonFocuswork.Changelog note
A disabled non-native button no longer renders
tabindex="-1"; it renders notabindexat all. The tab order is unchanged andaria-disabled/data-disabledare unchanged, so a[tabindex="-1"]selector is the only thing that stops matching. In exchange, the text of a disabled trigger can be selected and copied.Tests
useButton: a disabled non-native button carries notabindex, is focused by neither Tab nor a click, and does not cancelpointerdown.Select.Trigger: the value of a disabled non-native trigger can be selected by a real double click, driven over CDP so the selection is the browser's own default action rather than a synthetic event, and the trigger is still not focused by it. Blink only, like the other CDP tests in the repo.All three fail on
masterand pass here.pnpm test:jsdomandpnpm test:chromiumare green locally, with one exception I could not tie to this change:ScrollAreaViewport'sdata-scrollingtest failed once in a loaded run and passed three times out of three on its own, and that file references neitheruseButtonnortabIndexnordisabled.Beyond the suite, I patched the change into an installed
@base-ui/react@1.8.0and measured the component that the report came from, aSelectand aMultiSelectwrapper rendering the trigger withnativeButton={false}, across the eleven state rows of their state matrices. One forced double click per row, readingwindow.getSelection()back:tabindex="0", selectstabindex="0", selectstabindex="-1", selects nothingtabindex, selectsNothing else in those matrices moved, in either component.