Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
caa9d47
editor: restore connected tab border customizations
chryw Sep 29, 2026
f5dc14b
editor: honor customized connected tab separators
chryw Sep 29, 2026
f0ca52e
editor: smooth connected active tab corners
chryw Sep 29, 2026
476efee
editor: cover connected tab border ownership
chryw Sep 29, 2026
52532c1
editor: separate tab dividers from boundaries
chryw Sep 29, 2026
59be6cc
editor: inherit tab divider color from border
chryw Sep 29, 2026
8d1cd66
editor: refine pill tab dividers and accents
chryw Sep 29, 2026
3a4c4c7
editor: isolate tab border continuity fixtures
chryw Sep 29, 2026
80dda2c
editor: expand tab divider fixture coverage
chryw Sep 29, 2026
48859a9
editor: limit connected tab boundaries to active cap
chryw Sep 29, 2026
a3bd39a
editor: clarify connected row boundary ownership
chryw Sep 29, 2026
2b24715
editor: avoid duplicate connected frame edge
chryw Sep 29, 2026
b100230
editor: refine tab divider ownership
chryw Sep 29, 2026
c5965a0
editor: preserve high contrast tab boundaries
chryw Sep 29, 2026
fc29cd9
editor: fix shared connected tab CI regressions
chryw Sep 29, 2026
9521da9
editor: avoid duplicate connected hover borders
chryw Sep 29, 2026
af0937a
editor: restore wrapped connected hover accents
chryw Sep 30, 2026
7ea82c4
editor: complete connected tab color parity
chryw Sep 30, 2026
cb0071d
Merge remote-tracking branch 'origin/main' into dev/cherwan/agents/co…
chryw Oct 1, 2026
ce07532
editor: align merged color fixtures with tab geometry
chryw Oct 1, 2026
ac2fa2b
editor: remove stale tab fixture imports
chryw Oct 1, 2026
f0624de
editor: defer row structure to tab layout
chryw Oct 1, 2026
9f31f83
editor: isolate tab color fixture styling
chryw Oct 1, 2026
c8696e7
editor: preserve connected tab contours and color inheritance
chryw Oct 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions build/lib/stylelint/vscode-known-variables.json
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,7 @@
"--vscode-tab-activeForeground",
"--vscode-tab-activeModifiedBorder",
"--vscode-tab-border",
"--vscode-tab-divider",
"--vscode-tab-dragAndDropBorder",
"--vscode-tab-hoverBackground",
"--vscode-tab-hoverBorder",
Expand Down Expand Up @@ -1079,6 +1080,7 @@
"--tab-sizing-current-width",
"--tab-sizing-fixed-min-width",
"--tab-sizing-fixed-max-width",
"--editor-tab-border-indicator-display",
"--editor-group-tab-height",
"--editor-group-tabs-height",
"--testMessageDecorationFontFamily",
Expand Down Expand Up @@ -1120,11 +1122,19 @@
"--modern-ui-editor-tab-active-foreground",
"--modern-ui-editor-tab-active-hover-background",
"--modern-ui-editor-tab-border",
"--modern-ui-editor-tab-current-border-bottom",
"--modern-ui-editor-tab-current-border-top",
"--modern-ui-editor-tab-custom-action-active-background",
"--modern-ui-editor-tab-custom-action-active-hover-background",
"--modern-ui-editor-tab-custom-action-hover-background",
"--modern-ui-editor-tab-custom-active-background",
"--modern-ui-editor-tab-custom-active-border-top",
"--modern-ui-editor-tab-custom-active-hover-background",
"--modern-ui-editor-tab-custom-border",
"--modern-ui-editor-tab-custom-hover-background",
"--modern-ui-editor-tab-custom-inactive-background",
"--modern-ui-editor-tab-custom-unfocused-active-background",
"--modern-ui-editor-tab-custom-unfocused-active-border-top",
"--modern-ui-editor-tab-custom-unfocused-active-hover-background",
"--modern-ui-editor-tab-custom-unfocused-hover-background",
"--modern-ui-editor-tab-custom-unfocused-inactive-background",
Expand Down
3 changes: 2 additions & 1 deletion src/vs/sessions/test/browser/editorPart.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ suite('Sessions - EditorPart', () => {
const row = appendElement(title, 'tabs-and-actions-container');
const scrollable = appendElement(row, 'monaco-scrollable-element');
const tabs = appendElement(scrollable, 'tabs-container');
const first = appendElement(tabs, 'tab active connected-tab-top-row');
const first = appendElement(tabs, 'tab active connected-tab-top-row first-in-row');
const firstFill = appendElement(first, 'tab-fill');
const second = appendElement(tabs, 'tab connected-tab-top-row');
const secondFill = appendElement(second, 'tab-fill');
Expand Down Expand Up @@ -107,6 +107,7 @@ suite('Sessions - EditorPart', () => {
row.classList.toggle('wrapping', wrapped);
first.classList.toggle('last-in-row', wrapped);
first.classList.toggle('connected-tab-upper-row', wrapped);
second.classList.toggle('first-in-row', wrapped);
second.classList.toggle('connected-tab-top-row', !wrapped);
const cap = mainWindow.getComputedStyle(secondFill);
assert.deepStrictEqual({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,19 @@
display: none;
}

.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-divider {
display: none;
position: absolute;
top: 50%;
left: 0;
z-index: 9;
width: var(--vscode-strokeThickness);
height: var(--vscode-spacing-size160);
background-color: var(--vscode-tab-divider, transparent);
pointer-events: none;
transform: translateY(-50%);
}

.monaco-workbench .part.editor > .content .editor-group-container > .title > .tabs-and-actions-container > .monaco-scrollable-element > .tab-connected-overflow-edge {
display: none;
}
Expand Down Expand Up @@ -316,7 +329,7 @@
.monaco-workbench .part.editor > .content .editor-group-container > .title:not(.two-tab-bars) .tabs-container > .tab.active.tab-border-bottom > .tab-border-bottom-container,
.monaco-workbench .part.editor > .content .editor-group-container > .title.two-tab-bars .tabs-and-actions-container:not(:first-child) .tabs-container > .tab.active.tab-border-bottom > .tab-border-bottom-container,
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty-border-top > .tab-border-top-container {
display: block;
display: var(--editor-tab-border-indicator-display, block);
position: absolute;
left: 0;
pointer-events: none;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -998,6 +998,8 @@ export class MultiEditorTabsControl extends EditorTabsControl {
tabContainer.appendChild(tabFillContainer);
const tabConnectedEdgeContainer = $('.tab-connected-edge', { 'aria-hidden': true });
tabContainer.appendChild(tabConnectedEdgeContainer);
const tabDivider = $('.tab-divider', { 'aria-hidden': true });
tabContainer.appendChild(tabDivider);

// Tab Border Top
const tabBorderTopContainer = $('.tab-border-top-container');
Expand Down Expand Up @@ -2808,7 +2810,7 @@ registerThemingParticipant((theme, collector) => {
if (tabHoverBorder) {
collector.addRule(`
.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab:hover > .tab-border-bottom-container {
display: block;
display: var(--editor-tab-border-indicator-display, block);
position: absolute;
left: 0;
pointer-events: none;
Expand All @@ -2825,7 +2827,7 @@ registerThemingParticipant((theme, collector) => {
if (tabUnfocusedHoverBorder) {
collector.addRule(`
.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab:hover > .tab-border-bottom-container {
display: block;
display: var(--editor-tab-border-indicator-display, block);
position: absolute;
left: 0;
pointer-events: none;
Expand Down
4 changes: 3 additions & 1 deletion src/vs/workbench/common/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ export const TAB_BORDER = registerColor('tab.border', {
light: '#F3F3F3',
hcDark: contrastBorder,
hcLight: contrastBorder,
}, localize('tabBorder', "Border to separate tabs from each other. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
}, localize('tabBorder', "Border around tabs. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));

export const TAB_DIVIDER = registerColor('tab.divider', TAB_BORDER, localize('tabDivider', "Color of dividers between editor tabs."));

export const TAB_LAST_PINNED_BORDER = registerColor('tab.lastPinnedBorder', {
dark: treeIndentGuidesStroke,
Expand Down
10 changes: 6 additions & 4 deletions src/vs/workbench/contrib/modernUI/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,20 @@ The connected root marker is defined with the editor control constants and share

Connected tabs preserve at least the first basename character, an ellipsis, the extension, any decoration badge, and the action column when shrinking. File icons collapse first and return when the editor is widened; full names remain available in the hover and accessible label. Default `fit` sizing remains content-sized: the measured minimum is a compression safeguard, not a preferred tab width. Active close actions and dirty indicators remain visible, while clean inactive close actions appear on hover or keyboard focus. The final tab and the right viewport boundary keep an inset curved shoulder instead of a straight clipped edge. Explicit compact pinned tabs retain their icon-only sizing.

The bottom shoulders and freestanding caps share the same control radius plus the outside stroke (5px with the default tokens), including at clipped viewport edges. The outermost connected row shares the editor's top stroke without squaring its free corners. Only corners adjoining the left frame are straight; the editor's own clipping supplies its outer corner radius. Upper wrapped rows use the original control radius on their free corners and align to the same left edge as the bottom row. Wrapped fills occupy equal-height row hit boxes (28px normally, 24px compact), without horizontal gutters between their painted surfaces, with spacing only between rows and no extra gutter below the final row. Hover does not change their geometry.
The bottom shoulders and freestanding caps share the Inner radius plus the outside stroke (7px with the default tokens), including at clipped viewport edges. Each shoulder paints a complete curved stroke meeting the body separator; the edge mask removes only the cap's straight continuation behind that curve. Theme colors and explicit border customizations do not change these dimensions. The outermost connected row shares the editor's top stroke without squaring its free corners. Only corners adjoining the left frame are straight; the editor's own clipping supplies its outer corner radius. Upper wrapped rows use the Inner radius on their free corners and align to the same left edge as the bottom row. Wrapped rows retain equal-height hit boxes, with spacing only between rows and one shared separator slot below the final row. Hover does not change their geometry.

Close-action clearance is derived from the existing tab height and row gutters, independently of the shoulder radius. The same clearance reserves label space and positions the 20px hover target. Painted borders are excluded from the available content area: a wrapped connected row centers its action below the cap's top stroke, while a single standard row uses the shared frame. Stroke clearance is reserved in inactive tabs too, so selecting a tab never moves its close target. Separate pinned-row pills add their horizontal fill inset to the reservation. Left and right actions use the same centered action surface. High-contrast hover and focus indicators belong to the close target, not a second border around its action container.

Wrapped tabs reserve the same action space before and after row classification; upper-row markers and row-end markers must not trigger another fit-tab reflow. Selection must not change a tab's width or margin. Shoulder space is reserved only after the final tab, independently of selection; intermediate shoulders paint over adjacent tab surfaces without inserting a gap.
Wrapped tabs reserve the upper pill's action space before row classification; assigning upper-row and row-end markers must not change fit-tab widths or trigger another reflow. Close targets share the same trailing clearance on upper and bottom rows, including at compact density. Selection must not change a tab's width or margin. Shoulder space is reserved only after the final tab, independently of selection; intermediate shoulders paint over adjacent tab surfaces without inserting a gap.

Automatic reveal includes the complete shoulder and rounds fractional layout bounds outward so the selected action is not clipped. Manual scrolling can still move part of the selected tab, including its action, offscreen. In that case a stationary cap and shoulder finish the visible outline; their stroke is aligned with the document separator, and the clipping mask falls back to `editor.background` when the theme does not define a tab-strip background.

By default, the connected design uses `editor.background` for the active tab so the selected tab and editor body read as one document well. The strip and inactive tabs use `editorGroupHeader.connectedTabsBackground`, and their hover fill is derived from `foreground` over that background. Explicit tab fill and label color customizations from `tab.*`, `modernTab.*`, and `modernEditorTab.*` override those defaults for connected and pill tabs alike; editor-specific Modern UI colors take precedence over shared Modern UI colors, which take precedence over legacy colors. Connected action surfaces and frame colors retain their connected-specific treatment: the active tab's action container remains transparent, while its outside stroke, shoulders, and strip separator use `editorGroupHeader.tabsBorder`, falling back to `tab.border`. Upper wrapped and separate pinned rows change only the tab shape, not its fill or label color precedence.
By default, the connected design uses `editor.background` for the active tab so the selected tab and editor body read as one document well. The strip and inactive tabs use `editorGroupHeader.connectedTabsBackground`, and their hover fill is derived from `foreground` over that background. Explicit tab fill, label, and action color customizations from `tab.*`, `modernTab.*`, and `modernEditorTab.*` override those defaults for connected and pill tabs alike; editor-specific Modern UI colors take precedence over shared Modern UI colors, which take precedence over legacy colors. The active Connected tab's action container remains transparent unless a corresponding `modernEditorTab.*ActionBackground` color is explicitly customized. Its outside stroke, shoulders, and strip separator use `editorGroupHeader.tabsBorder`, falling back to `tab.border`. Upper wrapped and separate pinned rows change only the tab shape, not its color precedence.

Agents chat tabs use the same connected shape, strip colors, and theme-aware stroke as side-panel tabs, with the selected tab joining the active or inactive session's background instead of `editor.background`. Overflowing chat tabs expose a thin, draggable horizontal scrollbar on hover without increasing the tab-row height. Tab selection, close actions, status indicators, inline renaming, drag and drop, and keyboard navigation retain their existing behavior.

An explicitly customized `tab.selectedBorderTop` remains the active tab's fallback when the theme has no active top-border color for that group's focus state. It does not override an existing active top-border color or enable the theme's uncustomized top accent. Explicit hover-action backgrounds apply equally to bottom connected tabs, upper wrapped pills, and separate pinned rows.

`editorGroupHeader.connectedTabsBackground` defaults to `editorGroupHeader.tabsBackground`, so themes can keep their established strip palette without customization. The bundled connected palettes are Dark 2026 `#202122`, Light 2026 `#EAEAEA`, Dark Modern `#2B2B2B`, Light Modern `#E5E5E5`, the classic dark themes `#303031`, the classic light themes `#E8E8E8`, and Quiet Light `#E4E4E4`. Their original global `editorGroupHeader.tabsBackground` and `tab.inactiveBackground` values remain unchanged. Shared connected theme derivations, including Agents-window tabs, use `editorGroupHeader.tabsBackground` outside Modern UI; only Modern UI editor tabs adopt the dedicated connected-strip palette.

Connected tabs use `tab.inactiveForeground` rather than dimming the general foreground to 50% opacity. The default palettes pair these fills with readable inactive text (at least 4.5:1), including in inactive editor groups. Light Modern and the classic light themes use the existing neutral `#616161`; the classic dark themes use `#A6A6A6`. Explicit legacy foreground customizations still take precedence. HC styling and the original pill label defaults are unchanged.
Expand Down Expand Up @@ -188,7 +190,7 @@ Resize grip dots mark only boundaries between top-level parts, not modal editor

Activity bar items in non-default top or bottom positions use the `modernTab.*` colors because they share the pane tab presentation.

Editor tab fill and label colors have the same precedence in the `connected` and `pill` styles. Existing `tab.*` fill and label customizations remain effective unless the corresponding `modernTab.*` or `modernEditorTab.*` color is explicitly customized. Connected action backgrounds and frame borders continue to use the connected treatment described above.
Editor tab fill, label, and action colors have the same precedence in the `connected` and `pill` styles. Existing `tab.*` fill and label customizations remain effective unless the corresponding `modernTab.*` or `modernEditorTab.*` color is explicitly customized. Connected action backgrounds retain their seamless defaults until an explicit editor action color overrides them; frame borders continue to use the connected treatment described above.

`statusBar.inactiveBackground` only changes the resting background in an inactive window with a workspace or folder open. Debugging and other status bar background overrides take precedence. Empty windows continue to use `statusBar.noFolderBackground`. Leaving the inactive color unset preserves the existing status bar behavior.

Expand Down
Loading
Loading