markdown: Fix heading sizes and spacing in preview (#54374)

saberoueslati created

## Context

Heading sizes in the markdown preview were nearly identical to body
text, making `# H1`, `## H2`, and `### H3` visually indistinguishable.
The root cause was in `MarkdownStyle::themed()`: it set a
`heading_level_styles` override with font sizes of `rems(1.05–1.15)`,
which silently replaced the correct sizes applied by
`apply_heading_style` (via GPUI's `text_3xl`/`text_2xl`/`text_xl`
utilities `rems(1.875/1.5/1.25)`). Removing that override restores the
intended hierarchy. A `mt_4()` top margin was also added so consecutive
headings have visual breathing room.

Closes #54358

Video of manual test below :

[Screencast from 2026-04-21
02-22-12.webm](https://github.com/user-attachments/assets/8dd815f9-6f9b-4e88-bebb-28c79f019427)

## How to Review

`crates/markdown/src/markdown.rs` Three changes:
- (1) removed the `heading_level_styles` block from
`MarkdownStyle::themed()` that was overriding heading font sizes with
nearly-body-text values;
- (2) added `mt_4()` to the heading div in `push_markdown_heading` for
better vertical spacing;
- (3) added `test_heading_font_sizes_are_distinct` which renders H1–H3
and a paragraph then asserts that line heights strictly decrease from H1
down to body text.

## Self-Review Checklist

- [x] I've reviewed my own diff for quality, security, and reliability
- [ ] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Release Notes:

- Fixed heading sizes in the markdown preview to correctly reflect the
H1–H6 size hierarchy, matching standard markdown renderers

Change summary

crates/markdown/src/markdown.rs | 57 ++++++++++++++++++----------------
1 file changed, 30 insertions(+), 27 deletions(-)

Detailed changes

crates/markdown/src/markdown.rs 🔗

@@ -157,32 +157,6 @@ impl MarkdownStyle {
             rule_color: colors.border,
             block_quote_border_color: colors.border,
             code_block_overflow_x_scroll: true,
-            heading_level_styles: Some(HeadingLevelStyles {
-                h1: Some(TextStyleRefinement {
-                    font_size: Some(rems(1.15).into()),
-                    ..Default::default()
-                }),
-                h2: Some(TextStyleRefinement {
-                    font_size: Some(rems(1.1).into()),
-                    ..Default::default()
-                }),
-                h3: Some(TextStyleRefinement {
-                    font_size: Some(rems(1.05).into()),
-                    ..Default::default()
-                }),
-                h4: Some(TextStyleRefinement {
-                    font_size: Some(rems(1.).into()),
-                    ..Default::default()
-                }),
-                h5: Some(TextStyleRefinement {
-                    font_size: Some(rems(0.95).into()),
-                    ..Default::default()
-                }),
-                h6: Some(TextStyleRefinement {
-                    font_size: Some(rems(0.875).into()),
-                    ..Default::default()
-                }),
-            }),
             code_block: StyleRefinement {
                 padding: EdgesRefinement {
                     top: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(8.)))),
@@ -1124,7 +1098,7 @@ impl MarkdownElement {
         text_align_override: Option<TextAlign>,
     ) {
         let align = text_align_override.unwrap_or(self.style.base_text_style.text_align);
-        let mut heading = div().mb_2();
+        let mut heading = div().mt_4().mb_2();
         heading = apply_heading_style(heading, level, self.style.heading_level_styles.as_ref());
 
         heading = match align {
@@ -3650,4 +3624,33 @@ mod tests {
             }
         }
     }
+
+    #[gpui::test]
+    fn test_heading_font_sizes_are_distinct(cx: &mut TestAppContext) {
+        let rendered = render_markdown("# H1\n\n## H2\n\n### H3\n\nBody text", cx);
+
+        assert!(
+            rendered.lines.len() >= 4,
+            "expected at least 4 rendered lines, got {}",
+            rendered.lines.len()
+        );
+
+        let h1_line_height = rendered.lines[0].layout.line_height();
+        let h2_line_height = rendered.lines[1].layout.line_height();
+        let h3_line_height = rendered.lines[2].layout.line_height();
+        let body_line_height = rendered.lines[3].layout.line_height();
+
+        assert!(
+            h1_line_height > h2_line_height,
+            "H1 line height ({h1_line_height:?}) should be greater than H2 ({h2_line_height:?})"
+        );
+        assert!(
+            h2_line_height > h3_line_height,
+            "H2 line height ({h2_line_height:?}) should be greater than H3 ({h3_line_height:?})"
+        );
+        assert!(
+            h3_line_height > body_line_height,
+            "H3 line height ({h3_line_height:?}) should be greater than body text ({body_line_height:?})"
+        );
+    }
 }