Skip to content

Add separator option to select box#297703

Merged
mrleemurray merged 11 commits intomainfrom
mrleemurray/custom-menu-update
Mar 1, 2026
Merged

Add separator option to select box#297703
mrleemurray merged 11 commits intomainfrom
mrleemurray/custom-menu-update

Conversation

@mrleemurray
Copy link
Copy Markdown
Contributor

@mrleemurray mrleemurray commented Feb 25, 2026

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.

image image image

Copilot AI review requested due to automatic review settings February 25, 2026 15:29
@mrleemurray mrleemurray self-assigned this Feb 25, 2026
@mrleemurray mrleemurray requested a review from bpasero February 25, 2026 15:29
@vs-code-engineering vs-code-engineering bot added this to the February 2026 milestone Feb 25, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 isSeparator property to ISelectOptionItem interface and updated SeparatorSelectOption constant
  • 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

Comment thread src/vs/base/browser/ui/selectBox/selectBoxCustom.ts Outdated
Comment thread src/vs/base/browser/ui/selectBox/selectBoxCustom.ts Outdated
Comment thread src/vs/base/browser/ui/selectBox/selectBoxCustom.ts Outdated
Comment thread src/vs/base/browser/ui/selectBox/selectBoxCustom.ts Outdated
Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 25, 2026

@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.

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 25, 2026

@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.

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 25, 2026

@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.

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 25, 2026

@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.

@bpasero bpasero requested a review from Copilot March 1, 2026 08:24
Copy link
Copy Markdown
Member

@bpasero bpasero left a comment

Choose a reason for hiding this comment

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

@mrleemurray some review feedback from AI, but I am not sure how relevant:

  1. Invalid ARIA role on <option> — selectBoxNative.ts sets role="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.

  2. Menu selection colors silently changed — defaultStyles.ts replaces menuSelectionForeground/menuSelectionBackground with listHoverForeground/listHoverBackground. This breaks theme customizations for menu.selectionForeground and menu.selectionBackground. Should be reverted or split into a separate intentional change.

  3. 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.

  4. onPointerUp doesn't guard against disabled/separator items — Mouse-over now correctly skips disabled items, but clicking directly on a separator row could still trigger selection. onPointerUp needs the same guard.

  5. Separator ::after needs position: relative on parent — In selectBoxCustom.css, the absolute-positioned ::after line relies on the list widget setting position on .monaco-list-row. Add position: relative to .option-separator to make it self-contained.

  6. getComputedStyle called on every dropdown render — selectBoxCustom.ts forces a style recalc each time. Use font-size: inherit in CSS instead, or cache the value.

  7. isSeparator should imply isDisabled — The renderer in selectBoxCustom.ts manually adds option-disabled for separators, but this invariant isn't enforced. Either enforce it at the type/setter level, or document it clearly. As-is, a consumer could set isSeparator: true without isDisabled: true and get inconsistent behavior (CSS shows disabled, but navigation doesn't skip it).

  8. Unrelated visual changes should be split out — Menu border-radius, item height (2em24px), focus outline style (1.6px dotted1px solid), dropdown corner radius (smalllarge), 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.

@mrleemurray mrleemurray merged commit e46f6f2 into main Mar 1, 2026
22 checks passed
@mrleemurray mrleemurray deleted the mrleemurray/custom-menu-update branch March 1, 2026 08:27
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Comment on lines +1049 to +1057
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;
}
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines 246 to +248
backgroundColor: asCssVariable(menuBackground),
selectionForegroundColor: asCssVariable(menuSelectionForeground),
selectionBackgroundColor: asCssVariable(menuSelectionBackground),
selectionForegroundColor: asCssVariable(listHoverForeground),
selectionBackgroundColor: asCssVariable(listHoverBackground),
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +188 to 191
if (isSeparator) {
option.setAttribute('role', 'separator');
}

Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
if (isSeparator) {
option.setAttribute('role', 'separator');
}

Copilot uses AI. Check for mistakes.
Comment on lines +1026 to 1034
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;
}
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
@vs-code-engineering vs-code-engineering bot locked and limited conversation to collaborators Apr 15, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants