microsoft / microsoft/vscode

Markdown preview: code block copy button scrolls out of view on verticaly tall code blocks

Open
#330,385 3 comments 0 reactions 1 assignee Claimed by @hediet View on GitHub
Dominant language
TypeScript
Stars
193k
Forks
42.4k
PR merge metrics
PR metrics pending

Description

Does this issue occur when all extensions are disabled?: Yes (built-in extension — `markdown-language-features`)

- VS Code Version: 1.132.0
- OS Version: macOS 15.6

**Steps to Reproduce:**

1. Create a `.md` file with a fenced code block of ~200 lines, so the block is taller than the preview window.
2. Open the Markdown preview (`Cmd+K V` / `Ctrl+K V`).
3. Hover the code block. The copy button shows up in the top-right corner ✅
4. Scroll down through the block.

**Expected:**
The copy button stays reachable while any part of the code block is on screen.

**Actual:**
The button scrolls off the top along with the first line of the block. From that point on there is no way to copy without scrolling all the way back up to where the block starts. On a 200 line block that means scrolling up a full screen or more, copying, then finding your place again.

**Root Cause:**

The button is `position: absolute` inside the `pre`, anchored to the top of the block, so it leaves the viewport as soon as the top of the block does.

The obvious fix is `position: sticky`, but it does not work on its own, because of this rule in `extensions/markdown-language-features/media/markdown.css`:

```css
pre:not(.hljs), pre.hljs code > div {
padding: 16px;
border-radius: 3px;
overflow: auto;
}
```

`overflow: auto` makes the `pre` a scroll container. A sticky element only reacts to its nearest scrollable ancestor, so a sticky button inside the `pre` sticks to the `pre`'s own scrollport and ignores the page scroll entirely. I tried it first and the button still scrolled off the top.

**Suggested Fix:**

Move the scrolling from the `pre` to the `code` inside it. The `pre` stops being a scroll container, the page becomes the button's scrollport, and sticky starts working. Long lines still scroll, just on the `code` element instead.

`extensions/markdown-language-features/media/markdown.css`:

```css
pre:not(.hljs) {
overflow: visible;
}

pre:not(.hljs) > code {
display: block;
overflow: auto;
}

.code-block-copy-button {
position: sticky;
top: 8px;
margin: -8px -8px -20px auto; /* was: position: absolute; top: 8px; right: 8px */
...
}
```

The negative margins cancel the button's own box (-8 + 28 - 20 = 0), so it takes up no layout space and the code is not pushed down. `margin-left: auto` keeps it right aligned.

`extensions/markdown-language-features/preview-src/index.ts`:

```diff
- pre.appendChild(button);
+ pre.prepend(button);
```

A sticky element is in flow, so DOM order now decides where it sits. Left as the last child it lays out after the code and lands at the bottom of the block.

**One small thing while I was in there:** the button's fill is `textCodeBlock.background`, which is `#0a0a0a66` / `#dcdcdc66`, and the hover state replaces that fill with `toolbar.hoverBackground` (`#5a5d5e50`). Both are semi transparent. Today this mostly goes unnoticed because the button sits over an empty corner, but with the button traveling over code the text reads straight through it, worst of all on hover. `editorWidget.background` is the opaque equivalent, and the toolbar tint can be layered over it with `background-image` instead of replacing it. Happy to split this into its own issue if you'd rather keep them separate.

The button is still hover-only, so nothing changes for anyone who is not pointing at the block.

I have the change working locally and can open a PR.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.