Add separator option to select box#297703
Conversation
Co-authored-by: Copilot <copilot@github.com>
…avior Co-authored-by: Copilot <copilot@github.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces separator support for select box components to improve visual organization in dropdowns. The implementation adds an isSeparator property to select options, provides visual styling via CSS pseudo-elements, updates keyboard navigation to properly skip disabled/separator items, and aligns the select box styling with the action widget design pattern.
Changes:
- Added
isSeparatorproperty toISelectOptionIteminterface and updatedSeparatorSelectOptionconstant - Enhanced keyboard navigation (arrow keys, PageUp/PageDown, Home/End) to skip over contiguous disabled/separator options
- Updated visual styling to match action widget patterns: solid outlines, larger border radius, font-size inheritance, and CSS-based separator rendering
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/vs/base/browser/ui/selectBox/selectBox.ts | Added isSeparator property to ISelectOptionItem interface and SeparatorSelectOption constant |
| src/vs/base/browser/ui/selectBox/selectBoxNative.ts | Updated createOption to handle isSeparator flag with role attribute for native select elements |
| src/vs/base/browser/ui/selectBox/selectBoxCustom.ts | Enhanced keyboard navigation for disabled options, updated focus outline styles to match action widget, added font-size inheritance from select button, added accessibility label for separators |
| src/vs/base/browser/ui/selectBox/selectBoxCustom.css | Added separator styling with CSS pseudo-element border, removed list-level focus ring, changed border-radius to cornerRadius-large |
| src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts | Updated debug configuration dropdown to mark separators with isSeparator flag |
|
@mrleemurray I've opened a new pull request, #297709, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@mrleemurray I've opened a new pull request, #297710, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@mrleemurray I've opened a new pull request, #297711, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@mrleemurray I've opened a new pull request, #297712, to work on those changes. Once the pull request is ready, I'll request review from you. |
…igation Co-authored-by: Copilot <copilot@github.com>
There was a problem hiding this comment.
@mrleemurray some review feedback from AI, but I am not sure how relevant:
-
Invalid ARIA role on
<option>— selectBoxNative.ts setsrole="separator"on an<option>element, which is not a valid ARIA role for that element. Remove it or use a different approach for the native path. -
Menu selection colors silently changed — defaultStyles.ts replaces
menuSelectionForeground/menuSelectionBackgroundwithlistHoverForeground/listHoverBackground. This breaks theme customizations formenu.selectionForegroundandmenu.selectionBackground. Should be reverted or split into a separate intentional change. -
No tests — The keyboard navigation refactoring (
onDownArrow,onUpArrow,onHome,onEnd,onPageUp,onPageDown) is non-trivial and needs tests, especially for edge cases: all options disabled, separators at boundaries, multiple contiguous separators. -
onPointerUpdoesn't guard against disabled/separator items — Mouse-over now correctly skips disabled items, but clicking directly on a separator row could still trigger selection.onPointerUpneeds the same guard. -
Separator
::afterneedsposition: relativeon parent — In selectBoxCustom.css, the absolute-positioned::afterline relies on the list widget setting position on.monaco-list-row. Addposition: relativeto.option-separatorto make it self-contained. -
getComputedStylecalled on every dropdown render — selectBoxCustom.ts forces a style recalc each time. Usefont-size: inheritin CSS instead, or cache the value. -
isSeparatorshould implyisDisabled— The renderer in selectBoxCustom.ts manually addsoption-disabledfor separators, but this invariant isn't enforced. Either enforce it at the type/setter level, or document it clearly. As-is, a consumer could setisSeparator: truewithoutisDisabled: trueand get inconsistent behavior (CSS shows disabled, but navigation doesn't skip it). -
Unrelated visual changes should be split out — Menu border-radius, item height (
2em→24px), focus outline style (1.6px dotted→1px solid), dropdown corner radius (small→large), theme-2026 backdrop-filter additions, and dropdown container outline removal are all unrelated to the separator feature and should be in a separate PR for clean bisection.
| let candidate = this.selectList.getFocus()[0]; | ||
|
|
||
| // Shift selection up if we land on a disabled option | ||
| if (this.options[this.selected].isDisabled && this.selected > 0) { | ||
| this.selected--; | ||
| this.selectList.setFocus([this.selected]); | ||
| // Shift selection down if we land on a disabled option | ||
| while (candidate < this.options.length - 1 && this.options[candidate].isDisabled) { | ||
| candidate++; | ||
| } | ||
| if (this.options[candidate].isDisabled) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
In onPageDown, if the focused row lands on a disabled option (e.g. a separator) and there is no enabled option below it, the callback returns without restoring focus. That leaves focus on a disabled row while this.selected remains unchanged, which can confuse focus/selection state.
Consider resetting focus back to the last valid selection (or nearest enabled option in the opposite direction) instead of returning with the list focused on a disabled item.
| backgroundColor: asCssVariable(menuBackground), | ||
| selectionForegroundColor: asCssVariable(menuSelectionForeground), | ||
| selectionBackgroundColor: asCssVariable(menuSelectionBackground), | ||
| selectionForegroundColor: asCssVariable(listHoverForeground), | ||
| selectionBackgroundColor: asCssVariable(listHoverBackground), |
There was a problem hiding this comment.
defaultMenuStyles now uses listHoverForeground/background for the selected (focused) menu item. This breaks theming for menu.selectionForeground / menu.selectionBackground because those color tokens will no longer have any effect on menus and the menubar.
Use the menu-specific tokens (menuSelectionForeground/menuSelectionBackground) for selection colors so menu theming stays consistent and configurable.
| if (isSeparator) { | ||
| option.setAttribute('role', 'separator'); | ||
| } | ||
|
|
There was a problem hiding this comment.
Setting role="separator" on an <option> is not a valid ARIA pattern (an <option> has an implicit option role and many screen readers/browsers ignore or warn on role overrides). This can lead to accessibility tooling warnings without improving separator semantics.
Suggestion: remove the role attribute and rely on disabled + the separator text for native select rendering, or switch to a different native structure (e.g. <optgroup>) if true native separators are required.
| if (isSeparator) { | |
| option.setAttribute('role', 'separator'); | |
| } |
| let candidate = this.selectList.getFocus()[0]; | ||
|
|
||
| // Shift selection down if we land on a disabled option | ||
| if (this.options[this.selected].isDisabled && this.selected < this.options.length - 1) { | ||
| this.selected++; | ||
| this.selectList.setFocus([this.selected]); | ||
| // Shift selection up if we land on a disabled option | ||
| while (candidate > 0 && this.options[candidate].isDisabled) { | ||
| candidate--; | ||
| } | ||
| if (this.options[candidate].isDisabled) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
In onPageUp, if the focused row lands on a disabled option (e.g. a separator) and there is no enabled option above it, the callback returns without restoring focus. This can leave the list focus sitting on a disabled row while this.selected still points elsewhere, which makes subsequent keyboard interactions inconsistent.
Consider falling back to the previous this.selected/_currentSelection (or the nearest enabled option in the other direction) and explicitly resetting list focus before returning.
Introduce a separator option for the select box, enhancing the dropdown's visual organization. Update focus outlines and styling for separators to improve user experience and maintain consistency with the select button's font size.