ux-css-layout

$npx mdskill add microsoft/vscode/ux-css-layout

This skill covers CSS file organization, naming, standard sizes, programmatic layout (SplitView, Grid, scrollable), responsive patterns, and text overflow handling.

SKILL.md

.github/skills/ux-css-layoutView on GitHub ↗
---
name: ux-css-layout
description: VS Code CSS conventions, file organization, class naming, standard sizes, SplitView/Grid layout, scrollable content, responsive layout, and text overflow/ellipsis patterns. Use when writing CSS, building layouts, or fixing text truncation issues.
---

This skill covers CSS file organization, naming, standard sizes, programmatic layout (SplitView, Grid, scrollable), responsive patterns, and text overflow handling.

---

## 1. File Organization

CSS files are **co-located** with their TypeScript components:

```
src/vs/base/browser/ui/button/
    button.ts
    button.css
src/vs/workbench/contrib/myFeature/browser/
    myFeature.ts
    media/
        myFeature.css
```

Import CSS directly in the TS file:
```typescript
import './media/myFeature.css';
// or for base widgets:
import './button.css';
```

Workbench-level global styles live in `src/vs/workbench/browser/media/`.

## 2. Class Naming

- **`monaco-` prefix** for all major components: `.monaco-workbench`, `.monaco-split-view2`, `.monaco-scrollable-element`
- **Modifier classes**: `.monaco-split-view2.vertical`, `.monaco-split-view2.horizontal`
- **State classes**: `.visible`, `.focused`, `.active`, `.highlight`
- Feature-specific classes use kebab-case without prefix: `.my-feature`, `.outline-pane`, `.welcome-view-content`

## 3. Standard Sizes

| Element | Size |
|---------|------|
| Part title height | 35px |
| Title padding (horizontal) | 8px |
| Title label inner padding | 12px |
| Action area padding | 5px |
| Action icon size | 16px |
| Body font-size | 13px (workbench), 11px (HTML body) |
| Line height | 1.4em |
| Validation message font-size | 12px (line-height: 17px) |

> For `padding`/`margin`/`gap`, `border-radius`, `font-size`/`font-weight`, codicon size and border width, prefer the design-system **size tokens** over raw px — see [§10 Design-System Size Tokens](#10-design-system-size-tokens-spacing-radius-font-codicon-stroke). Canonical reference: `.github/instructions/design-tokens.instructions.md` (auto-injected for `src/vs/**/*.css`).


## 4. CSS Selector Quality

| Anti-pattern (flagged) | Correct pattern |
|------------------------|-----------------|
| ID selectors for styling (`#my-widget`) | Class selectors (`.my-widget`) |
| Overly specific selectors | Minimal specificity needed |
| Styles in the wrong file | Co-located with the component |
| Missing `min-width: 0` on flex children | Prevents truncation issues |
| Forgetting `pointer-events: none` on hidden overlays | Prevents click-through bugs |

### Never Add New `!important`

Do not introduce `!important` in new or modified CSS. When a declaration loses the cascade, inspect the competing selector and increase specificity with the smallest appropriate component, workbench, or state-class prefix instead. Existing `!important` declarations may be preserved and must not be removed mechanically during unrelated edits. Do not copy them, add new ones, or use them to avoid understanding selector ownership.

The narrow exception is shared focus/active-outline suppression, where `outline: 0 !important` is intentionally used to override native or global focus indicators and prevent flashing outlines during pointer activation. Keep this exception scoped to focus-indicator behavior; feature styling must still resolve cascade conflicts through selector specificity.

## 5. SplitView Layout

**File**: `src/vs/base/browser/ui/splitview/splitview.ts`

For splitting views with draggable sashes (either horizontal or vertical):

```typescript
const splitView = new SplitView(container, {
    orientation: Orientation.VERTICAL,
    proportionalLayout: true,
    styles: { separatorBorder: asCssVariable(sashBorder) }
});

// Each view implements IView:
const myView: IView = {
    element: myDomNode,
    minimumSize: 100,
    maximumSize: Number.POSITIVE_INFINITY,
    onDidChange: Event.None,
    layout(size, offset) { /* resize content */ }
};

splitView.addView(myView, Sizing.Distribute);
```

Use `LayoutPriority.High` / `.Low` to control which views resize first when space is constrained. Use `snap: true` to allow views to snap closed.

## 6. Grid Layout

**File**: `src/vs/base/browser/ui/grid/grid.ts`

For 2D layouts (used by editor groups):
```typescript
const grid = new Grid(initialView);
grid.addView(newView, Sizing.Distribute, referenceView, Direction.Right);
```

## 7. Scrollable Content

Three classes for different needs:

| Class | When to Use |
|-------|-------------|
| `SmoothScrollableElement` | Animated scrolling (SplitView, ListView) |
| `DomScrollableElement` | Wrap existing DOM content (hovers, menus, breadcrumbs) |
| `ScrollableElement` | Basic single-direction scrollbar |

```typescript
const scrollable = new DomScrollableElement(contentNode, {
    horizontal: ScrollbarVisibility.Auto,
    vertical: ScrollbarVisibility.Auto
});
this._register(scrollable);
container.appendChild(scrollable.getDomNode());
scrollable.scanDomNode(); // call after content changes
```

## 8. Responsive Layout

VS Code does **not** use CSS media queries. Instead, it uses a **programmatic constraint-based layout system**:

- `IView.minimumSize` / `maximumSize` — views declare their size constraints.
- `SplitView` and `Grid` distribute space according to constraints and `LayoutPriority`.
- `dom.DisposableResizeObserver` (not a raw `ResizeObserver`) is used for container-aware sizing (e.g., editor auto-layout).
- The window is treated as a fixed viewport; space is distributed via sash-based resizing.

When building a responsive component:
1. Set `minimumSize` / `maximumSize` appropriately.
2. Use `LayoutPriority.Low` for panels that should collapse first.
3. Use `snap: true` for panels that should snap closed when too small.
4. Fire `onDidChange` when your constraints change dynamically.

## 9. Text Overflow & Ellipsis

All text labels that can be truncated by a resizable container **must** use the ellipsis pattern. Clipped text without an ellipsis is a visual bug.

### Standard Ellipsis Pattern (CSS)

The three-property combo is required — all three must be present:

```css
.my-label {
	overflow: hidden;
	white-space: nowrap;
	text-overflow: ellipsis;
}
```

### Common Locations That Need Ellipsis

| Element | Why |
|---------|-----|
| Part title labels (`h2`, breadcrumbs) | Sidebar/panel can be resized narrower than the title |
| View pane header titles | View containers can be narrow |
| List/tree row labels | Rows have a fixed width from the list container |
| Tab labels (editor tabs) | Many tabs shrink to fit |
| Button labels in welcome views | Buttons have `max-width` constraints |
| Status bar items | Many items compete for horizontal space |
| Notification message text | Notification toast/center has fixed width |
| Tooltip/hover headings | Hovers have max-width |
| Dropdown/select items | Select boxes have bounded width |
| Badge text / descriptions | Auxiliary text in constrained columns |

### Flex Container Gotchas

Flex children default to `min-width: auto`, which **prevents** `text-overflow: ellipsis` from working because the flex item refuses to shrink below its content width. Fix this by setting `min-width: 0` on the flex child:

```css
/* WRONG — ellipsis will NOT trigger inside a flex container */
.flex-parent {
	display: flex;
}
.flex-parent > .label {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* CORRECT — add min-width: 0 so the flex item can shrink */
.flex-parent > .label {
	min-width: 0;          /* ← this is the fix */
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
```

This pattern is used throughout VS Code — for example, `.monaco-icon-label-container` sets `min-width: 0` and `flex: 1` to allow label text to truncate.

### Fixed vs Flexible Elements

When a row has both fixed-size elements (icons, action buttons) and flexible text:

```css
.row {
	display: flex;
	align-items: center;
}
.row > .icon {
	flex-shrink: 0;        /* icon never shrinks */
	width: 16px;
}
.row > .label {
	flex: 1;               /* label takes remaining space */
	min-width: 0;          /* allows shrinking below content width */
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.row > .actions {
	flex-shrink: 0;        /* action buttons never shrink */
}
```

This is the standard pattern for tree rows, list items, tab labels, and view pane headers.

### Hover for Full Text

When text is truncated with ellipsis, the **full text must be accessible** via hover tooltip. Use `IHoverService.setupDelayedHover()` with the full untruncated text so users can read it:

```typescript
this._register(this.hoverService.setupDelayedHover(labelElement, {
	content: fullLabelText,
}));
```

For `IconLabel` and list/tree renderers, this is handled automatically. For custom DOM, you must add it manually.

### Anti-Patterns (NEVER DO)

- **Never** let text clip without an ellipsis — if `overflow: hidden` is set, `text-overflow: ellipsis` must also be set.
- **Never** rely on a fixed pixel width for text that could be localized — localized strings vary in length.
- **Never** use `text-overflow: ellipsis` without `overflow: hidden` and `white-space: nowrap` — all three are required.
- **Never** forget `min-width: 0` on flex children that need to truncate.
- **Never** truncate text without providing a hover/tooltip for the full string.

---

## 10. Design-System Size Tokens (spacing, radius, font, codicon, stroke)

VS Code ships a design-system **size** ramp, registered in `src/vs/platform/theme/common/sizes/baseSizes.ts` and emitted as `--vscode-*` CSS variables. When writing or editing CSS, prefer the token var over a raw px value wherever a token exists. The full tables + rationale live in the auto-injected `.github/instructions/design-tokens.instructions.md` (canonical source — keep this section in sync with it). This section captures the **decision logic** for deeper styling tasks.

> Every `var()` you reference, except product-icon `--vscode-icon-*-content` and `--vscode-icon-*-font-family` variables, must exist in `build/lib/stylelint/vscode-known-variables.json` or stylelint/hygiene fails. After registering a *new* token in `baseSizes.ts`, run `./scripts/test-documentation.sh` to regenerate the `"sizes"` array; CSS-only custom properties belong in `"others"`.

### Spacing — `padding`, `margin`, `gap`

Scale (px): `0, 2, 4, 6, 8, 10, 12, 16, 20, 24, 28, 32, 36, 40` → `--vscode-spacing-sizeNone`, `--vscode-spacing-size20` … `--vscode-spacing-size400` (token number = px × 10, so `size200` = 20px).

**What matters is the value, not the token.** Adopting the `var()` is optional — a raw px value is fine **as long as it lands on the scale**. What breaks rhythm is an **off-scale** value (3, 5, 7, 14, 26px…). Snap off-scale values to the nearest scale value, **ties round up** (`5px → 6px`, `3px → 4px`, `1px → 2px`, `26px → 28px`). Each length of a shorthand is checked independently (`0 5px → 0 6px`). Leave `auto`, `%`, `em`/`rem`, `var()`/`calc()` untouched.

### Corner radius — `border-radius`

| px | Variable | Use |
|----|----------|-----|
| 2  | `--vscode-cornerRadius-xSmall` | very compact elements |
| 4  | `--vscode-cornerRadius-small` | controls (buttons, inputs) |
| 6  | `--vscode-cornerRadius-medium` | base / inner surfaces |
| 8  | `--vscode-cornerRadius-large` | prominent / outer surfaces |
| 12 | `--vscode-cornerRadius-xLarge` | very prominent surfaces |
| 9999 | `--vscode-cornerRadius-circle` | fully rounded (pills, dots) |

**Snap map** for off-scale literals (ties round **up**): `2→xSmall`, `3,4→small`, `5,6→medium`, `7,8→large`, `10,11,12→xLarge`, `14,16,18,20→xLarge`, `999→circle`.

- **Pills** (radius ≈ half the element height — e.g. `28h`/`14r`, `36h`/`18r`, `22×22`/`11r`) → `--vscode-cornerRadius-circle`, **not** xLarge. The literal-nearest token would square them and lose the fully-rounded intent.
- **Leave untouched:** `50%`, `0`, `0px`, `inherit`, any `calc()`/`var()`. Preserve `!important`.

### Font size & weight

Generic UI ramp — pair a **size** token with a **weight** token ("Strong" = matching size token + `semiBold`, never a separate size):

| px | Size var | Weight |
|----|----------|--------|
| 26 | `--vscode-fontSize-heading1` | semiBold |
| 18 | `--vscode-fontSize-heading2` | semiBold |
| 13 | `--vscode-fontSize-heading3` | semiBold |
| 13 | `--vscode-fontSize-body1` | regular |
| 11 | `--vscode-fontSize-body2` | regular |
| 12 | `--vscode-fontSize-label1` | regular |
| 11 | `--vscode-fontSize-label2` | regular |
| 10 | `--vscode-fontSize-label3` | regular |

Generic weights: `--vscode-fontWeight-regular` (400), `--vscode-fontWeight-semiBold` (600).

**Deprecated** — `--vscode-bodyFontSize` (13) → `--vscode-fontSize-body1`, `--vscode-bodyFontSize-small` (12) → `--vscode-fontSize-label1`, `--vscode-bodyFontSize-xSmall` (11) → `--vscode-fontSize-body2`.

The legacy Agents-specific `--vscode-agents-fontSize-*` and `--vscode-agents-fontWeight-*` tokens are also deprecated; use the matching generic tokens.

- **No medium (500).** `font-weight: 500` is off the ramp — snap to `semiBold`. Likewise `700`/`bold` → round to the nearer of 400/600.
- **"Strong" is not a separate size.** "Body 1 Strong" = the matching `--vscode-fontSize-*` size token + `semiBold`. Never add a strong *size*.
- `normal` ≡ 400 → `regular`. Leave `inherit`, `lighter`, `bolder`, `var()`/`calc()` untouched.

### Codicon size — icon `font-size`

Codicons are **only ever 16px or 12px** — never `14px` or any in-between value.

| px | Variable | Use |
|----|----------|-----|
| 16 | `--vscode-codiconFontSize` (base) | default icon size |
| 12 | `--vscode-codiconFontSize-compact` | dense/inline chrome |

**Compact-glyph convention:** when sizing an icon at the compact 12px size, also swap the registered glyph to its `*Compact` variant (e.g. `Codicon.close` → `Codicon.closeCompact`, `Codicon.add` → `Codicon.addCompact`). CSS `font-size` alone only scales the icon — it does **not** change to the visually-optimized compact glyph; that requires changing the registered icon (Action2 `icon:` / `renderIcon`). **Only swap the glyph when no CSS selector targets the original glyph class** (e.g. `.codicon-close`); selectors keyed on the glyph class (`.codicon-add`, `.codicon-chevron-down`) break when the class becomes `-compact`, so update those selectors too (or size via a glyph-independent wrapper class like `.monaco-button`). Some icons (settings/sliders, agent, vm, info, lock, plus) have **no** compact variant — keep the regular glyph at 12px.

### Stroke — border width

A **single** stroke thickness: `1px` → `--vscode-strokeThickness`. Applies to the `border: 1px solid <color>` shorthand and `border-width: 1px`. Other widths have no token — leave them.

```css
/* prefer */  border: var(--vscode-strokeThickness) solid var(--vscode-widget-border);
/* avoid  */  border: 1px solid var(--vscode-widget-border);
```

---

## Key Files

| Area | File |
|------|------|
| SplitView | `src/vs/base/browser/ui/splitview/splitview.ts` |
| Grid | `src/vs/base/browser/ui/grid/grid.ts` |
| Scrollbar | `src/vs/base/browser/ui/scrollbar/scrollableElement.ts` |
| Global workbench styles | `src/vs/workbench/browser/media/style.css` |

More from microsoft/vscode

SkillDescription
accessibilityPrimary accessibility skill for VS Code. REQUIRED for new feature and contribution work, and also applies to updates of existing UI. Covers accessibility help dialogs, accessible views, verbosity settings, signals, ARIA announcements, keyboard navigation, and ARIA labels/roles.
act-on-feedbackAct on user feedback attached to the current session. Use when the user submits feedback on the session's changes via the Submit Feedback button.
agent-customization**WORKFLOW SKILL** — Create, update, review, fix, or debug VS Code agent customization files (.instructions.md, .prompt.md, .agent.md, SKILL.md, copilot-instructions.md, AGENTS.md). USE FOR: saving coding preferences; troubleshooting why instructions/skills/agents are ignored or not invoked; configuring applyTo patterns; defining tool restrictions; creating custom agent modes or specialized workflows; packaging domain knowledge; fixing YAML frontmatter syntax. DO NOT USE FOR: general coding questions (use default agent); runtime debugging or error diagnosis; MCP server configuration (use MCP docs directly); VS Code extension development. INVOKES: file system tools (read/write customization files), ask-questions tool (interview user for requirements), subagents for codebase exploration. FOR SINGLE OPERATIONS: For quick YAML frontmatter fixes or creating a single file from a known pattern, edit the file directly — no skill needed.
agent-host-chat-contributionsBuild and review cross-cutting agent-host chat behavior through lifecycle contributions. Use when adding turn lifecycle side effects, prompt or context injection, restored-history transformation, protocol-action observation, or when reviewing changes that add code to AgentSideEffects or AgentService.
agent-host-e2e-testsUse when writing, recording, updating, validating, or troubleshooting Agent Host end-to-end and real-provider integration tests under src/vs/platform/agentHost/test/node/e2e and providerIntegration. Covers adding a cross-provider test, re-recording fixtures after an SDK bump, required cross-platform Azure validation, gating non-deterministic or platform-specific tests, and diagnosing replay cache misses.
agent-host-logsAnalyze Agent Host debug log exports. Use when given an ah-logs or ahp-logs zip/folder, an Export Agent Host Debug Logs bundle, events.jsonl, AHP JSONL transport logs, Agent Host.log, remote-agenthost.log, or copilot-logs.
author-contributionsIdentify all files a specific author contributed to on a branch vs its upstream, tracing code through renames. Use when asked who edited what, what code an author contributed, or to audit authorship before a merge. This skill should be run as a subagent — it performs many git operations and returns a concise table.
auto-perf-optimizeRun agent-driven VS Code performance or memory investigations. Use when asked to launch Code OSS, automate a VS Code scenario, run the Chat memory smoke runner, capture renderer heap snapshots, take workflow screenshots, compare run summaries, or drive a repeatable scenario before heap-snapshot analysis.
azure-pipelinesUse when validating Azure DevOps pipeline changes for the VS Code build. Covers queueing builds, checking build status, viewing logs, and iterating on pipeline YAML changes without waiting for full CI runs.
build-champ-triageFind out why a build has failed