design-philosophy
VS Code tasarım felsefesi — UI hakkında ham pikseller yerine tasarım terimleriyle akıl yürütmek için paylaşılan bir Değerler→İlkeler→Hareketler sözlüğü. Tasarım yaparken kullanın,…
npx skills add https://github.com/microsoft/vscode --skill design-philosophyVS Code Design Philosophy
This skill is the canonical VS Code design philosophy — the single source of truth for how we reason about UI, for both developers and agents.
As more and more of the UI is implemented via agents and tooling, the pixels increasingly take care of themselves, and the scarce, human part becomes the design judgment behind them.
This is about shifting left - talking about UI in design terms, not pixels, and having that conversation early. When something looks off, "this is 8px, should be 6px" fixes one number; "this surface is rounded at the wrong elevation tier" names the rule, so the fix lands everywhere and we catch it on a mockup or screenshot rather than in code review. The aim is to agree on what the UI should be and why, in language a designer and an engineer can share - the implementation (which token, which file) tends to follow once the design is settled.
How to read this: three layers
The hardest part of any design language is getting from "this feels off" to a clear, shared fix everyone agrees on. This bridges that in three steps, each explaining the one below it:
| Layer | What it is | Example |
|---|---|---|
| Values | what we want people to feel, and why it's at stake | Focused |
| Principles | evaluative rules you can hold a design up against, phrased as a question | "One thing leads; the rest supports" - where does the eye go first? |
| Moves | the concrete mechanics, each with a rule for which one and why | demote the secondary panel to a recessed background token / a quieter type role |
Read top-down when you're learning the system, and bottom-up when you're fixing a bug: name the feeling, find the principle it breaks, then reach for the move that restores it.
Worked example - a busy panel
- See: a panel feels busy and keeps pulling your eye off the editor.
- Feeling: not Calm, not Focused.
- Principle: breaks Quiet at rest (it carries a permanent fill it shouldn't) and One thing leads (secondary chrome competing with the editor).
- Move: drop the panel to a recessed/transparent background token, reveal its border only on hover, and demote its labels to a quieter type role.
The values: what we design for
These four values name what we want people to feel using VS Code - they're the top layer, the thing every principle and move below is ultimately in service of. Learn them and you can describe almost any bug. Each is a feeling the UI should give; each is upheld by one or more principles below. Reach for the value first, the principle second, the move last.
| Value | Means | When it's broken it feels… | Principles |
|---|---|---|---|
| Calm | quiet at rest, uncluttered; room to breathe; soft, not sharp; plain-spoken and human | busy, noisy, cramped, jagged, cryptic, shouty | 1, 2, 3 |
| Focused | clear hierarchy; one thing leads, the rest supports | flat, samey, the eye has nowhere to go | 4, 5 |
| Consistent | everything sits on the same ramps and tiers | random, off-grid, drifting | 5, 6 |
| Delightful | small moments that do a job - guide, confirm, orient | lifeless, abrupt, or gimmicky | 7 |
Say "this feels noisy, it's not Calm" before "the background should be transparent." The value points everyone at the same principle.
Why these four?
It's fair to ask: who would ever want a UI that isn't calm, focused, consistent, and delightful? That's exactly the point. These aren't differentiators we're claiming over other products - they're the qualities most under threat in a tool like ours, and naming them is how we defend them. We chose them because they're the values VS Code is structurally inclined to lose:
- Calm is the first casualty of an IDE. VS Code is information-dense and lived in for hours; every feature team has a good reason to add one more affordance, and the sum is noise. Calm is the value that pushes back on our own gravity.
- Focused matters because our surfaces are deep. A screen full of equally-weighted, equally-valid controls is the default failure mode of a power tool. Naming focus forces us to decide what leads.
- Consistent has the largest surface area to defend. VS Code is built by many hands over many years; drift is the natural state. Consistency is less an aspiration than a maintenance discipline.
- Delightful is the one we deliberately ration. It earns its place only by doing a job (Principle 7). We name it not to add more polish but to keep it honest, so delight guides and confirms rather than decorates.
And each value names a real tension, not a free win - which is what makes it a choice worth stating rather than a platitude:
- Calm trades against density - how much do we surface at rest?
- Focused trades against equal access - what do we demote so one thing can lead?
- Consistent trades against the genuinely special case - when is a difference earned rather than drift?
- Delightful trades against restraint - when does a flourish cross into noise?
So the useful question isn't "would we ever not want these?" but "which one is this surface trading away, and did we mean to?" When a value really must yield - say, a critical warning that should break calm to seize attention - that's a deliberate, named exception, not a reason to drop the value.
Principles
Principles are evaluative: each one is a question you can ask of any surface, and the answer tells you whether the design is right - independent of any pixel value. They're grouped by the value they uphold.
Calm
1. Quiet at rest, present on intent
Ask: is anything shouting when nothing is happening?
Chrome should recede until you engage it. A surface carrying a permanent fill, border, or focus ring it hasn't earned is competing with the user's content for attention it doesn't need. The modern look is restrained: things reveal themselves on intent - hover, focus, interaction - and sit quietly otherwise.
Quiet is not hidden. A control can recede without becoming a guessing game: you should still be able to find it, reach it, and see where keyboard focus has landed. Judge calm by how the surface feels to use, not just how little it draws.
When this is wrong it sounds like "this is loud at rest / should be quiet until hover" or "the focus ring is firing on click" - a behavioral description, not a color value.
Carried out by: reveal-on-intent behaviors, one stroke, theme color.
2. Room to breathe
Ask: does this feel cramped, tight, or jagged?
Calm UI is generous and soft. Space isn't wasted; it's what lets the eye rest and groups things without drawing a single line. Edges are rounded rather than sharp. When a surface feels boxed-in or busy, the fix is usually more breathing room on the spacing ramp, not smaller content.
Breathing room is there to serve the work, not squeeze it out. More padding isn't always more calm - especially in a narrow pane or a dense layout. Give the content room to breathe as well as the chrome.
Carried out by: the spacing ramp, elevation tiers (rounded, not sharp).
3. The interface explains itself, plainly and kindly
Ask: could someone understand this at a glance, without guessing?
The UI should read like a person talking. Prefer a word to a mystery glyph; write in sentence case; keep the tone human. A lone unlabeled icon, or a Title-Cased Marketing Headline, makes the user work harder than they should - never cryptic, never shouting.
An explanation should last as long as it's needed. If a field needs a label, its purpose shouldn't disappear when you start typing or wait behind a hover. A clear screen-reader name matters too; neither replaces the other. Gestures need the same kindness: a visible invitation and a keyboard alternative, not a blank patch you have to know to drag.
And an invitation should be honest. A grab hand says "you can move this" - does that move make sense in the layout the user can see? Moving a pane beside another, detaching it, and moving the window are different promises. Make the intended one clear, and make sure the gesture delivers it.
Carried out by: words & casing.
Make unavailable features understandable
Users look for features they have seen in release notes, documentation, or promotion. Silently removing an otherwise relevant option when policy, account entitlement, setup, or version requirements prevent its use makes the product feel broken. Prefer keeping it discoverable but disabled, with a concise, truthful reason and a useful next step.
- Distinguish blocked by organization policy, requires sign-in/upgrade/update, not supported here, and still loading using authoritative state; absence from a list alone is not evidence of policy.
- Reuse existing disabled-row styling and the briefcase indicator for managed policy. Provide a visible reason or independently actionable Learn why link; do not make hover the only way to discover the explanation.
- Presentation must not grant access: block pointer, keyboard, and stale-menu activation while keeping explanatory links operable. Follow the accessibility guidance.
- This is not a blanket rule to expose everything. Preserve explicit feature-hiding choices (including disabled AI), unsupported provider capabilities, and unreleased feature gates. Explain unavailable features where they are relevant without inventing support or exposing sensitive policy values.
Reuse these patterns rather than copying entire implementations:
| Surface | Example |
|---|---|
| Settings | Managed-setting indicator and accessible policy text |
| Model picker | Unavailable models with distinct admin, upgrade, and update explanations |
| Harness picker | Availability descriptions and policy help links |
| Permissions picker | Shared permission-level policy presentation and picker integration, with runtime policy boundaries |
Worked example - a boxed-in toolbar
- See: a toolbar sits on a solid filled bar, separator lines between every button, the buttons pushed up against each other.
- Feeling: not Calm - busy and boxed-in.
- Principle: breaks Quiet at rest (the fill and separators are permanent chrome that hasn't earned its place) and Room to breathe (buttons jammed together).
- Move: let the bar read transparent at rest, drop the separators (one stroke - and here the answer is no), and space the buttons out on the ramp.
Focused
4. One thing leads; the rest supports
Ask: where does the eye go first - and is anything secondary competing with it?
Every view should have a clear center of attention - the one thing the eye should land on first. Keep secondary information out of that spot: demote it with a quieter type role, a recessed surface, or a smaller icon, so the thing that matters leads and everything else supports it. A view where everything has equal weight is flat - the eye has nowhere to go.
Quiet can yield when something genuinely needs you. A blocked task should catch your attention even while you're working elsewhere, without making it harder to see what's selected or where focus is. The message should still come through when motion is turned off.
An explanation shouldn't hide the thing it explains. A review card that covers the code it's discussing has put the supporting detail in the lead. Give it room or let the user reveal it when needed - don't make them scroll around it just to read their work.
Carried out by: type roles, elevation tiers, icon size by context.
5. Elevation is encoded, not eyeballed
Ask: does this surface's roundness match how far it floats above what's beneath it?
How rounded - and how raised - a surface is means something: it tells you where the surface sits in the stack. A floating overlay reads as more rounded than a control sitting flat inside a pane. Radius is chosen by the surface's role in the hierarchy, never by taste. (This serves both Focused and Consistent.)
Carried out by: elevation tiers, reveal-on-intent (scroll shadows).
Worked example - a flat settings row
- See: a settings row shows its label, its help text, and a "Reset" action all at the same size and weight.
- Feeling: not Focused - flat; the eye doesn't know where to land.
- Principle: breaks One thing leads - nothing is the center of attention.
- Move: lift the label to the leading type role, demote the help text to
body2, and make "Reset" a quiet secondary action so the row reads top-down.
Consistent
6. Sameness signals sameness
Ask: do two like things look identical - and is any difference here intentional?
Equivalent elements must look equivalent, because they pull from the same named scale. When two similar things differ, that difference should mean something; accidental drift is the enemy. This is why every size, radius, weight, and color is a named token with a role, not a literal - the literal is only ever a stand-in for the token it lands on.
Sameness includes how something responds, not just its resting shape. The same icon in two different-sized buttons still feels like two different controls. Compare the whole thing - the space around it, the hover, the focus, the area you can click. A difference in role or density can earn a difference in treatment; an accident can't.
Carried out by: every move below - they are the shared scales. See especially design tokens, the spacing ramp, the type ramp, icon sizes, one stroke.
Worked example - two mismatched dialogs
- See: two dialogs open side by side; one has 8px corners, the other 6px - both are floating overlays.
- Feeling: not Consistent - the UI feels like it's drifting.
- Principle: breaks Sameness signals sameness (two equivalent surfaces look different for no reason) and Elevation is encoded (an overlay belongs on the Outer tier).
- Move: point both at the Outer radius token; the 6px was a literal someone typed instead of naming the tier.
Delightful
7. Delight earns its keep
Ask: what does this help the user do?
Motion, reveals, and finishing touches are welcome - but each must do a job: guide the eye, confirm an action, orient the user, or smooth a jump. If there's no answer to the question, it's decoration, and decoration reads as noise (back to Calm). Keep it subtle, short, and interruptible; honor prefers-reduced-motion; never animate for its own sake.
A delight bug sounds like "this transition is doing nothing for me" (cut it) or "this jump is disorienting - it needs a functional transition" (add one) - not a duration or easing value to fiddle with.
Watch the journey, not just the landing. Swapping one view for another shouldn't make a stable layout seem to bounce or resize. Ask "what is this movement helping me understand?" of each surface, not the window as a whole. Cut the motion that has no job, keep the motion that does, and check the experience with motion both on and off.
Carried out by: reveal-on-intent behaviors.
Worked example - a bouncing panel
- See: a side panel slides in with a 600ms bounce every time you toggle it.
- Feeling: not Delightful (gimmicky) and not Calm (slow and shouty).
- Principle: breaks Delight earns its keep - the bounce isn't doing a job, and the toggle is already obvious.
- Move: if nothing needs orienting, cut the animation; if the panel's arrival genuinely needs explaining, replace it with a short, subtle, interruptible fade that honors
prefers-reduced-motion.
The moves: the mechanical toolbox
Moves are the concrete mechanics - the tokens, ramps, and tiers. On their own they're just lists of allowed values; what makes each one usable is the decision rule that says which value, and why, plus the principle it serves. When you reach for a move, lead with the rule, not the literal.
This is the one section that touches implementation - and even here, the goal is to keep the conversation about design. Treat the Moves as the shared vocabulary that lets an agreed design be built consistently, not as the opening move in a review. Reach for them after you've named the feeling and the principle, never instead of it.
The size and font tokens live in baseSizes.ts; the full reference is in design-tokens.instructions.md.
Tokens are the source of truth, not the pixel
Design-system spacing, radii, type roles, and colors should use a named token or ramp where one exists. Raw literals remain valid for structural measurements.
- Decision rule: when something looks wrong, find the token it should be expressing and reason about that - not the literal.
- Serves: Sameness signals sameness (6).
An
8pxcorner isn't "8px." It's an Outer surface radius that someone typed as a literal. Name the role and the fix follows.
Elevation tiers - round by role, not by literal
Corner radius encodes how far a surface floats above the one beneath it. Every rounded thing belongs to exactly one tier:
| Tier | Radius | Token | What it is |
|---|---|---|---|
| Control | 4px | --vscode-cornerRadius-small | interactible elements - buttons, inputs, list rows, tabs |
| Inner | 6px | --vscode-cornerRadius-medium | non-control containers sitting inside a surface |
| Outer | 8px | --vscode-cornerRadius-large | floating / overlay surfaces - menus, hovers, dialogs, toasts |
Pills (radius ≈ half the height) are fully round (--vscode-cornerRadius-circle), not "a big radius."
- Decision rule: pick the tier by the surface's role in the stack, not by how the corner looks. A bug here sounds like "this overlay is rounded at the control tier," never "this needs more border-radius."
- Serves: Elevation is encoded (5), Room to breathe (2).
The spacing ramp - on-scale or off-scale
Padding, margin, and gap come from the spacing ramp (0, 2, 4, 6, 8, 10, 12, 16, 20, 24, 28, 32, 36, 40).
- Decision rule: a value either lands on the ramp or it doesn't. Off-scale values (3, 5, 7, 14, 26…) break the rhythm; snap to the nearest step, ties rounding up. Report rhythm bugs as "this is off the spacing ramp," not "add a couple of pixels."
- Serves: Room to breathe (2), Sameness signals sameness (6).
The type ramp - roles and two weights, not free sizes
Text styles are roles, not arbitrary sizes: heading1–3, body1–2, label1–3. There are exactly two weights - regular (400) and semiBold (600). There is no medium (500). "Strong" is not a bigger size; it is the same role token at semiBold.
- Decision rule: choose the role by the text's rank in the hierarchy, not by eyeballing a size. A heading that looks weak is "the wrong type role" or "missing the semiBold weight," not "bump the font to 14." A
500anywhere is off the ramp. - Serves: One thing leads (4), Sameness signals sameness (6).
Icon sizes - two sizes, chosen by context
Codicons are 16px (base) or 12px (compact) - nothing in between; 14px is always a bug. At the compact size, also swap to the *Compact glyph (Codicon.close → Codicon.closeCompact) so the icon is optically tuned, not just scaled.
- Decision rule: size tracks the density and rank of the context. Use base (16px) for standalone or primary actions and comfortable click targets; use compact (12px) for dense rows, inline glyphs, and secondary chrome where the icon rides alongside text. So the answer to "16 or 12?" is "what is this icon's role here?" - not a taste call. Say "this icon should be compact," not "shrink it a bit."
- Serves: One thing leads (4), Sameness signals sameness (6).
One stroke
The standard border/separator stroke is 1px (--vscode-strokeThickness).
- Decision rule: whether an ordinary border is present is a yes/no decision. Preserve thicker semantic or accessibility strokes, such as focus indicators.
- Read it as one edge. Two thin borders on top of each other can still look like one heavy border; a second curve inside the first reads as a double frame. Judge the outline you see, not the strokes each element claims to draw. The layout guidance covers how to keep those edges aligned.
- Serves: Quiet at rest (1), Sameness signals sameness (6).
Color comes from the theme
Every color is a --vscode-* theme token, so the UI tracks the active theme and high-contrast modes.
- Decision rule: a hardcoded hex is a bug by construction. Color bugs are "wrong theme token" (or "a token that disappears in light/HC"), never a hex value to nudge.
- Serves: Quiet at rest (1), Sameness signals sameness (6).
Reveal-on-intent behaviors
Chrome that used to carry a permanent fill now reads transparent at rest and only reveals on interaction. Each behavior also does a job, so they pull double duty:
-
commandCenter- transparent at rest; shows its background/border on hover, so a pointer confirms the thing it's over. -
statusBar- neutralized to the window; only the background changes on hover, the foreground stays put. -
keyboardFocusOnly- the focus ring appears for keyboard focus (:focus-visible), not on mouse click, so the affordance shows exactly when it's useful. -
scrollShadows- content fades under a surface edge instead of being cut at a hard line, so you feel there's more beyond the fold. -
Serves: Quiet at rest (1), Delight earns its keep (7), and (scroll shadows) Elevation is encoded (5).
Words & casing
Prefer a text label to an icon. A word is read; an icon is guessed. Reach for a glyph only when its meaning is near-universal (close, search) or space is genuinely scarce - otherwise label the thing, or pair icon + label when you need both recognition and recall. A lone mystery glyph is the bug, not a badly-chosen one: "this action is unlabeled - give it a word," not "find a clearer icon."
Write in sentence case. Labels, buttons, menus, and messages capitalize the first word and proper nouns only - "Start new session," not "Start New Session." It's calmer, friendlier, and quicker to scan. Report it as "this is Title Case - make it sentence case," a tone fix, not a reword.
⚠️ Known conflict - reconcile later. This sentence-case guidance is the intended modern-UI direction, but it contradicts the repo-wide coding guideline that mandates title-style capitalization for command labels, buttons, and menu items (see coding-guidelines.instructions.md, "UI labels"). The two conventions have not yet been reconciled. Until they are, follow the existing title-case rule for shipped, non-experimental UI, and apply sentence case only within surfaces that have explicitly adopted the modern UI direction. TODO: agree a single casing convention and update whichever doc loses.
- Serves: The interface explains itself (3).
The phrasebook: describe the bug, name the principle
Lead with the role / tier / ramp, not the number - then name the principle so the fix lands everywhere, not just here. The number is a symptom.
| ❌ Don't say | ✅ Do say | Principle |
|---|---|---|
| "border-radius should be 6, not 8" | "this is an Inner surface rounded at the Outer tier" | 5 · Elevation |
| "this menu corner is too sharp" | "this overlay should be on the Outer radius tier" | 5 · Elevation |
| "add 2px of padding" | "this content is off the spacing ramp / needs ramp breathing room" | 2 · Room to breathe |
| "make this 14px" | "this should use the label1 / body1 type role" | 4 · One thing leads |
| "the title looks thin" | "the heading is missing the semiBold weight" | 4 · One thing leads |
| "font-weight 500 here" | "500 is off the ramp - snap to semiBold (600)" | 6 · Sameness |
| "shrink this icon a touch" | "this icon should be the compact (12px) size + *Compact glyph" | 4 · One thing leads |
| "this icon is 14px" | "codicons are 16 or 12 only - pick base or compact" | 6 · Sameness |
| "this ordinary border is too thick" | "standard borders are one stroke (1px) - this should/shouldn't have one; preserve thicker focus/semantic strokes" | 1 · Quiet at rest |
| "change this grey hex" | "this is the wrong theme token / it vanishes in light/HC" | 6 · Sameness |
| "the command center has a box around it" | "chrome should be quiet at rest, reveal on hover" | 1 · Quiet at rest |
| "the focus outline shows when I click" | "the ring is keyboard-focus only (:focus-visible)" | 1 · Quiet at rest |
| "the list is cut off at the bottom" | "the surface is missing its scroll-shadow fade" | 7 · Delight / 5 · Elevation |
| "the pane header has a line / hard edge" | "pane headers are rounded, separator-less section titles" | 1 · Quiet at rest |
Giving and receiving feedback
Describe the design, not the pixel. When you name the rule that's broken, the fix lands everywhere it's broken - not just in the one spot you happened to notice.
When something looks wrong, say it in this order:
- The feeling - which of the four values is it missing? Calm, Focused, Consistent, Delightful.
- The surface - what and where? "the sidebar pane header," "the Save dialog."
- The principle it breaks - the rule behind the feeling.
- The move that would fix it - the mechanic that restores it.
- Only then, if useful, the number - "shows 4px, looks like it wants 6px."
Example "The activity bar doesn't feel Calm - it's loud at rest. The icons carry a permanent fill that's competing with the editor (Quiet at rest, present on intent). Could the fill reveal on hover instead? It reads about 2px too tight as well, but the fill is the real thing."
Lead with the feeling, not the fix
The word points everyone at the same principle, even before anyone agrees on a pixel:
| Say this first | Not this |
|---|---|
| "this doesn't feel Calm - it's noisy at rest" | "make the background transparent" |
| "this isn't Focused - the eye has nowhere to land" | "make the title bigger" |
| "this feels inconsistent - these two dialogs don't match" | "change the radius to 6" |
| "this transition isn't Delightful, it's just decoration" | "make the animation faster" |
What makes feedback hard to act on
These aren't wrong, they're just incomplete - they fix one spot and drift back out of sync:
- A bare number. "This should be 6px." Which token? Why? Name the role and the fix lands everywhere.
- A taste claim with no rule. "I don't like this corner." Tie it to a value or principle so it's a shared standard, not a preference.
- A fix with no feeling. "Make it transparent." Maybe - but say what's wrong first ("it's loud at rest"), so we fix the cause, not just this instance.
- Several things at once. Split "this panel is busy, off-grid, and the icon's wrong" into three, each with its own value.
Practical notes
- Attach the surface. A screenshot or a short clip (for motion) plus the name of the view beats a paragraph of description. Circle the spot.
- One observation per thread. Keep each piece of feedback to a single surface + value, so it can be discussed and resolved on its own.
- Light vs. dark vs. high-contrast. If something only breaks in one theme, say so - that usually points at a theme token problem, not a color choice.
- Reduced motion. For animation feedback, note whether you have
prefers-reduced-motionon; a transition should still make sense without it. - Same intent, same treatment. A shared fix should reach the places that share the problem - not make every window or dialog behave like the one you're looking at.
- Reuse the familiar interaction. A hover or link shouldn't become a special case just because it lives on a new surface. Keep the shared behavior, including keyboard access and safe content handling, rather than recreating only its appearance.
- Respect the user's choices. A new default changes an experience someone already knows. Make that change deliberately, preserve explicit preferences and rollout controls, and check both the old and new experience.
Does the fix feel right in use?
A fix isn't finished when one screenshot looks right. The principle should hold as the user moves through the experience, not just in the frame we happened to capture.
- Name what should get better. Start with the feeling, surface, and principle. Then find what owns the problem before changing it everywhere. Space between panels and space inside a panel do different jobs, even when they happen to measure the same.
- Follow the change. Hover, then use the keyboard. Open, close, pin, unpin, move, and resize the same surface. Check the themes, densities, and window types the change touches - not every possible combination, but the ones that could tell a different story. The layout and interaction-state guidance cover the mechanics.
- Try the thing, not its description. A declared radius doesn't prove the corner looks right; a focused element doesn't prove you can see focus. Look at the painted result and use the control. Tests should catch the original problem, not merely confirm the new implementation's description of itself.
- Make sure you're seeing the state you mean. A screenshot called "hover" should actually show hover; a keyboard check should follow the keyboard path. Simulating an event isn't always the same as making the interaction happen. Use the rendered-state guidance to keep that evidence honest.
- Understand feedback before chasing it. Several comments may describe one problem; a green check may miss it entirely. Find out whether a failure is in the experience, the test's expectations, or the setup before deciding what to change. A passing rerun isn't an explanation, and an unrelated failure isn't a reason to reshape the UI.
- Show the fix you actually shipped. Capture the final version, look beyond the intended surface for surprises, and check again after a refinement changes the result. Exercise the affected interactions before accepting new screenshot baselines. Reuse useful checks rather than repeating everything, and say what was tested and what wasn't - a browser capture can't speak for a native window you haven't tried.