diff --git a/Configurations.md b/Configurations.md
index 58d7aac52ef..10fed5b4c1e 100644
--- a/Configurations.md
+++ b/Configurations.md
@@ -180,6 +180,56 @@ fn bar() {
 
 See also: [`blank_lines_lower_bound`](#blank_lines_lower_bound)
 
+
+## `indent_blank_lines`
+
+Empty lines are indented like items in the same block.
+
+- **Default value**: `false`
+- **Possible values**: `true`, `false`
+- **Stable**: No (tracking issue: TBD)
+
+### Example
+
+#### `false` (default):
+```rust
+fn foo() {
+    println!("a");
+
+    if true {
+        println!("b");
+
+        let Some(x) = Some(x) else {
+            println!("c");
+
+            println!("d");
+        };
+
+        println!("e");
+    }
+}
+```
+
+#### `true`:
+```rust
+fn foo() {
+    println!("a");
+    
+    if true {
+        println!("b");
+        
+        let Some(x) = Some(x) else {
+            println!("c");
+            
+            println!("d");
+        };
+        
+        println!("e");
+    }
+}
+```
+
+
 ## `brace_style`
 
 Brace style for items
diff --git a/src/config/mod.rs b/src/config/mod.rs
index b03674b6b3c..5f5cd24fbef 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -152,6 +152,8 @@ create_config! {
         "Maximum number of blank lines which can be put between items";
     blank_lines_lower_bound: BlankLinesLowerBound, false,
         "Minimum number of blank lines which must be put between items";
+    indent_blank_lines: IndentBlankLines, false,
+        "Indent blank lines";
     edition: EditionConfig, true, "The edition of the parser (RFC 2052)";
     style_edition: StyleEditionConfig, true, "The edition of the Style Guide (RFC 3338)";
     version: VersionConfig, false, "Version of formatting rules";
@@ -815,6 +817,7 @@ trailing_comma = "Vertical"
 match_block_trailing_comma = false
 blank_lines_upper_bound = 1
 blank_lines_lower_bound = 0
+indent_blank_lines = false
 edition = "2015"
 style_edition = "2015"
 version = "One"
@@ -907,6 +910,7 @@ trailing_comma = "Vertical"
 match_block_trailing_comma = false
 blank_lines_upper_bound = 1
 blank_lines_lower_bound = 0
+indent_blank_lines = false
 edition = "2015"
 style_edition = "2024"
 version = "Two"
diff --git a/src/config/options.rs b/src/config/options.rs
index 00f9c3f7ec1..69a3c8b8ff7 100644
--- a/src/config/options.rs
+++ b/src/config/options.rs
@@ -676,6 +676,7 @@ config_option_with_style_edition_default!(
     MatchBlockTrailingComma, bool, _ => false;
     BlankLinesUpperBound, usize, _ => 1;
     BlankLinesLowerBound, usize, _ => 0;
+    IndentBlankLines, bool, _ => false;
     EditionConfig, Edition, _ => Edition::Edition2015;
     StyleEditionConfig, StyleEdition,
         Edition2024 => StyleEdition::Edition2024, _ => StyleEdition::Edition2015;
diff --git a/src/formatting.rs b/src/formatting.rs
index 1e1e329f624..4edae7ca17b 100644
--- a/src/formatting.rs
+++ b/src/formatting.rs
@@ -494,6 +494,7 @@ struct FormatLines<'a> {
     name: &'a FileName,
     skipped_range: &'a [(usize, usize)],
     last_was_space: bool,
+    line_all_space: bool,
     line_len: usize,
     cur_line: usize,
     newline_count: usize,
@@ -514,6 +515,7 @@ impl<'a> FormatLines<'a> {
             name,
             skipped_range,
             last_was_space: false,
+            line_all_space: true,
             line_len: 0,
             cur_line: 1,
             newline_count: 0,
@@ -546,6 +548,7 @@ impl<'a> FormatLines<'a> {
             if self.last_was_space {
                 if self.should_report_error(kind, &ErrorKind::TrailingWhitespace)
                     && !self.is_skipped_line()
+                    && !(self.line_all_space && self.config.indent_blank_lines())
                 {
                     self.push_err(
                         ErrorKind::TrailingWhitespace,
@@ -575,6 +578,7 @@ impl<'a> FormatLines<'a> {
             .contains_line(self.name, self.cur_line);
         self.newline_count += 1;
         self.last_was_space = false;
+        self.line_all_space = true;
         self.line_buffer.clear();
         self.current_line_contains_string_literal = false;
     }
@@ -587,6 +591,7 @@ impl<'a> FormatLines<'a> {
             1
         };
         self.last_was_space = c.is_whitespace();
+        self.line_all_space &= self.last_was_space;
         self.line_buffer.push(c);
         if kind.is_string() {
             self.current_line_contains_string_literal = true;
diff --git a/src/missed_spans.rs b/src/missed_spans.rs
index 384de1ce9ae..ce01b9d6ed2 100644
--- a/src/missed_spans.rs
+++ b/src/missed_spans.rs
@@ -114,25 +114,36 @@ impl<'a> FmtVisitor<'a> {
     }
 
     fn push_vertical_spaces(&mut self, mut newline_count: usize) {
-        let offset = self.buffer.chars().rev().take_while(|c| *c == '\n').count();
-        let newline_upper_bound = self.config.blank_lines_upper_bound() + 1;
-        let newline_lower_bound = self.config.blank_lines_lower_bound() + 1;
-
-        if newline_count + offset > newline_upper_bound {
-            if offset >= newline_upper_bound {
-                newline_count = 0;
-            } else {
-                newline_count = newline_upper_bound - offset;
-            }
-        } else if newline_count + offset < newline_lower_bound {
-            if offset >= newline_lower_bound {
-                newline_count = 0;
-            } else {
-                newline_count = newline_lower_bound - offset;
-            }
+        if let [] | [.., b'\n'] = self.buffer.as_bytes() {
+            /* Already at the start of a new line */
+        } else {
+            self.push_str("\n");
+            newline_count = newline_count.saturating_sub(1);
         }
 
-        let blank_lines = "\n".repeat(newline_count);
+        let indent = if self.config.indent_blank_lines() {
+            // FIXME: Should this include the alignment as well?
+            &*self.block_indent.block_only().to_string(self.config)
+        } else {
+            ""
+        };
+
+        let existing_blanks = self
+            .buffer
+            .rsplit_terminator('\n')
+            .take_while(|&line| line == indent)
+            .count();
+
+        // Clamp new total count to configuration.
+        let total = (existing_blanks + newline_count).clamp(
+            self.config.blank_lines_lower_bound(),
+            self.config.blank_lines_upper_bound(),
+        );
+
+        let blank_lines = std::iter::repeat(indent)
+            .take(total.saturating_sub(existing_blanks))
+            .fold(String::new(), |s, indent| s + indent + "\n");
+
         self.push_str(&blank_lines);
     }
 
diff --git a/tests/source/configs/indent_blank_lines/true.rs b/tests/source/configs/indent_blank_lines/true.rs
new file mode 100644
index 00000000000..91aa11708d6
--- /dev/null
+++ b/tests/source/configs/indent_blank_lines/true.rs
@@ -0,0 +1,40 @@
+// rustfmt-indent_blank_lines: true
+
+fn foo() {
+    if true {
+        println!("a");
+
+        match 4 {
+            0 => {
+                println!("b");
+
+                println!("c");
+                // FIXME: empty lines between match arms currently ignore this configuration:
+            }
+        
+            x => {
+                x = x
+                    .wrapping_add({
+                        // inner block
+                        let a = 4;
+
+                        a + 5
+                    })
+                    .unwrap();
+					  	 	 	 	    	 
+                if x > 10 {
+                    println!("{x}");
+            
+                    println!("{x}");
+                }
+            }
+        }
+    }
+}
+
+fn bar()
+where
+ 	u32: Sized, // FIXME: empty lines between where bounds ignores it
+					  	 	 	 	    	 
+    i32: Sized,
+{}
\ No newline at end of file
diff --git a/tests/target/configs/indent_blank_lines/true.rs b/tests/target/configs/indent_blank_lines/true.rs
new file mode 100644
index 00000000000..614b956f0a4
--- /dev/null
+++ b/tests/target/configs/indent_blank_lines/true.rs
@@ -0,0 +1,41 @@
+// rustfmt-indent_blank_lines: true
+
+fn foo() {
+    if true {
+        println!("a");
+        
+        match 4 {
+            0 => {
+                println!("b");
+                
+                println!("c");
+                // FIXME: empty lines between match arms currently ignore this configuration:
+            }
+
+            x => {
+                x = x
+                    .wrapping_add({
+                        // inner block
+                        let a = 4;
+                        
+                        a + 5
+                    })
+                    .unwrap();
+                
+                if x > 10 {
+                    println!("{x}");
+                    
+                    println!("{x}");
+                }
+            }
+        }
+    }
+}
+
+fn bar()
+where
+    u32: Sized, // FIXME: empty lines between where bounds ignores it
+
+    i32: Sized,
+{
+}